Skip to content

Add LangChain/LangGraph agent SDK and langgraph protocol - #115

Draft
dmaniloff wants to merge 7 commits into
mainfrom
langchain-sdk
Draft

Add LangChain/LangGraph agent SDK and langgraph protocol#115
dmaniloff wants to merge 7 commits into
mainfrom
langchain-sdk

Conversation

@dmaniloff

Copy link
Copy Markdown
Member

Summary

Adds a LangChain 1.x agent SDK to midojo alongside the existing MCP SDK, and wires the orchestrator to drive agents served by the native LangGraph Agent Server (langgraph dev / langgraph up) over HTTP. Includes a working weather agent and OpenShift/ROSA deploy manifests.

Authoring uses create_agent (LangChain's "highly customizable harness"); midojo's customization is the man-in-the-middle tool layer (MidojoToolkit) plus the suite's system prompt and model. Because create_agent compiles to a LangGraph graph, it's served with the framework-native Agent Server — no hand-rolled FastAPI.

What's included

  • src/midojo/sdk.py — extracts the shared ControlPlaneClient + ToolContext (previously in mcp_sdk.py) so both SDKs build on one control-plane seam.
  • src/midojo/langchain_sdk.pyMidojoToolkit wraps tools as LangChain tools that record function calls and read/write env via the control plane.
  • src/midojo/mcp_sdk.py — re-exports ControlPlaneClient/ToolContext from sdk (back-compat).
  • agent_client.pyLangGraphAgentClient talks to a LangGraph Agent Server via langgraph_sdk.
  • orchestrator.py — new langgraph protocol + --graph-name.
  • suites/weather/langchain_agent/create_agent-based weather agent (graph.py + langgraph.json) plus a one-shot CLI runner.
  • suites/weather/deploy/ — kustomize manifests: control-plane + agent Deployment/Service + run Job (arbitrary-UID / restricted-SCC friendly).
  • Containerfile — installs the langchain extra.

Testing

  • uv run pytest170 passed (adds tests/test_langchain_sdk.py; adjusts test_mcp_sdk.py for the refactor).
  • Local end-to-end validated against a live LiteLLM/MaaS model (qwen3 thinking model): control plane + langgraph dev + midojo-run --protocol langgraph over the full weather 4×4 matrix — man-in-the-middle injection and grading both work.
  • No new ruff/pyright errors introduced (pre-existing repo issues untouched).

Not included (intentionally)

Cluster deploy of the image + the actual ROSA run are the next step (needs a published image + a registry the cluster can pull from). The LLM creds Secret (weather-llm-creds) is created out-of-band — see agent-secret.example.yaml.

🤖 Generated with Claude Code

dmaniloff and others added 7 commits July 30, 2026 13:48
Introduce a LangChain 1.x agent SDK alongside the existing MCP SDK, and
wire the orchestrator to drive agents served by the native LangGraph
Agent Server.

- sdk.py: extract shared ControlPlaneClient + ToolContext (previously in
  mcp_sdk.py) so both SDKs build on one control-plane seam
- langchain_sdk.py: MidojoToolkit wraps tools as LangChain tools that
  record calls and read/write env via the control plane
- mcp_sdk.py: re-export ControlPlaneClient/ToolContext from sdk
- agent_client.py: LangGraphAgentClient talks to a LangGraph Agent Server
  (langgraph_sdk) over HTTP
- orchestrator.py: add `langgraph` protocol and --graph-name
- suites/weather/langchain_agent: create_agent-based weather agent served
  by `langgraph dev` (graph.py + langgraph.json)
- suites/weather/deploy: kustomize manifests (control plane + agent
  Deployment/Service + run Job) for OpenShift/ROSA
- Containerfile: install the `langchain` extra
- tests: cover the langchain SDK; adjust mcp_sdk tests for the refactor

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Diego Maniloff <diego.maniloff@gmail.com>
In-cluster OpenShift builds pulling `python:3.12-slim` hit Docker Hub's
anonymous pull-rate limit (shared build-node egress IP). Switch to
registry.access.redhat.com/ubi9/python-312, which the build pulls from
Red Hat's registry (no Docker Hub limit) and is the appropriate base for
an OpenShift/ROSA deployment. Run build steps as USER 0, drop back to
1001; the existing chgrp/chmod keeps the tree arbitrary-UID friendly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Diego Maniloff <diego.maniloff@gmail.com>
Plain `pip install ".[suites,langchain]"` ignores uv.lock and backtracks
for many minutes resolving the a2a-sdk -> google-api-core transitive
tree (observed 370+ version probes and still going). Install uv and use
`uv pip install`, whose resolver handles this in seconds. uv targets
UBI's active virtualenv ($VIRTUAL_ENV=/opt/app-root), so console scripts
stay on PATH.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Diego Maniloff <diego.maniloff@gmail.com>
The in-cluster binary Docker-strategy build pushes to the OpenShift
internal registry; reference that ImageStream so pods in the
midojo-weather namespace pull it via the default service account
(no external registry / pull secret needed). Verified end-to-end on
ROSA: control plane + LangGraph agent + orchestrator Job run the full
weather 4x4 matrix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Diego Maniloff <diego.maniloff@gmail.com>
ControlPlaneClient.record_function_call swallowed all httpx errors
(try/except HTTPError: pass) and never checked the response status. A
failing POST — e.g. a stale/unset /current eval — would silently drop
the call from the log while the tool's env mutations still persisted,
producing evals with real side effects but zero recorded function calls.

Let the POST error propagate and add raise_for_status(), matching
get_environment/put_environment. Recording failures now surface instead
of corrupting the function-call log.

The forward-raises LangChain test used a dead control-plane URL and
relied on the swallow; point it at the live test app so the asserted
RuntimeError is no longer masked by the recording POST.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Diego Maniloff <diego.maniloff@gmail.com>
Four tests hand-built the same live-control-plane toolkit
(MidojoToolkit.__new__ + _client/_real_tools/_tools). Collapse that into
a `toolkit` fixture. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Diego Maniloff <diego.maniloff@gmail.com>
MidojoToolkit.__init__ hard-built its own ControlPlaneClient from a URL,
so tests had to bypass __init__ (MidojoToolkit.__new__ + private attrs)
to inject an ASGI-backed client. Accept an optional `client` instead;
`control_plane_url` stays as the convenience path. Tests now construct
the toolkit normally via the fixture, dropping the __new__ hack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Diego Maniloff <diego.maniloff@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant