Summary
JanusLangChainAgent (depth 3 of the LangChain adapter) cannot be constructed
under LangChain 1.x. It imports AgentExecutor and create_tool_calling_agent
from langchain.agents unconditionally, and LangChain 1.0 removed both.
The langchain extra declares langchain>=0.3, which permits 1.x, and
uv sync --extra all --extra dev currently resolves to langchain 1.2.10. So
the adapter's turnkey entry point is broken in the project's own canonical
environment, not just in some unusual user setup.
Depths 1 and 2 are unaffected — see Scope below.
Environment
janus-guard @ 2512be1e6 (main)
- langchain 1.2.10, langchain-core 1.2.16 (as resolved by
uv sync --extra all --extra dev)
- Python 3.13, Windows 11
Steps to reproduce
from janus.adapters.langchain import JanusLangChainAgent
JanusLangChainAgent(model="openai/gpt-4o", tools=[], policy=None)
Actual behaviour
ImportError: cannot import name 'AgentExecutor' from 'langchain.agents'
(.../site-packages/langchain/agents/__init__.py)
The failure is at construction time, before any policy or tool wiring happens.
langchain.agents in 1.2.10 exports only:
['AgentState', 'create_agent', 'factory', 'middleware', 'structured_output']
Expected behaviour
Either the adapter works against the LangChain versions its own extra permits,
or the extra is narrowed so that a resolvable install is a working one.
Root cause
janus/adapters/langchain.py:261-264, inside JanusLangChainAgent.__init__:
from langchain.agents import ( # type: ignore[attr-defined]
AgentExecutor,
create_tool_calling_agent,
)
LangChain 1.0 replaced the AgentExecutor + create_tool_calling_agent pair
with a single LangGraph-backed create_agent. The import is unconditional and
runs on every construction, so there is no version in which depth 3 degrades
gracefully — it either imports or raises.
pyproject.toml:44-48 declares:
langchain = [
"langchain>=0.3",
"langchain-core>=0.3",
"langchain-openai>=0.2",
]
The lower bound is correct for the 0.3 API; there is no upper bound excluding
the 1.x API that removed it.
Scope
Affected
- Depth 3,
JanusLangChainAgent — cannot be constructed at all.
- The docstring example at
janus/adapters/langchain.py:89 and the depth-3
description at lines 20 and 205, which all reference AgentExecutor.
Not affected
- Depth 1,
secure_langchain_tools() — imports only
langchain_core.tools.StructuredTool (line 43).
- Depth 2,
wrap_langchain_tools() — same, plus BaseTool.
So policy enforcement itself is fine on LangChain 1.x. It is only the turnkey
agent wrapper that breaks. No test covers depth 3 construction, which is why CI
is green.
Suggested fix
Option A — version-tolerant branch (recommended). Detect which generation
is installed and build the corresponding loop:
try:
from langchain.agents import create_agent # LangChain >= 1.0
except ImportError:
create_agent = None
if create_agent is not None:
self._executor = create_agent(model=llm, tools=self.lc_tools,
system_prompt=system_prompt)
else:
from langchain.agents import AgentExecutor, create_tool_calling_agent
...
The two generations also differ at invoke time and need matching branches:
|
LangChain 0.3 |
LangChain 1.x |
| invoke |
{"input": task} |
{"messages": [{"role": "user", "content": task}]} |
| result |
result["output"] |
result["messages"][-1].content |
| tool trace |
result["intermediate_steps"] |
tool_calls on the message list |
This keeps the declared langchain>=0.3 honest.
Option B — cap the extra at langchain>=0.3,<1.0. Smaller change, but it
locks users out of LangChain 1.x for a limitation that only affects depth 3, and
depths 1 and 2 work fine there.
Option C — deprecate depth 3 in favour of depth 1 plus a user-built loop,
and document the create_agent pattern.
Whichever is chosen, the chat_model= escape hatch does not currently help:
the AgentExecutor import happens regardless of whether a pre-built model was
passed.
Workaround
demos/demo_2 (PR #6) hits this and works around it by using
secure_langchain_tools() (depth 1) and building the loop itself with a
create_agent / AgentExecutor branch. See
demos/demo_2/agent.py::IssueExplainer for a working implementation of
Option A, including the invoke-shape differences above.
Note for whoever picks this up
The fix touches janus/adapters/, so per CLAUDE.md it needs the
enforcement-review checklist before commit. Worth adding a construction test
for all three depths against whichever LangChain generation CI installs —
the absence of one is what let this ship.
Summary
JanusLangChainAgent(depth 3 of the LangChain adapter) cannot be constructedunder LangChain 1.x. It imports
AgentExecutorandcreate_tool_calling_agentfrom
langchain.agentsunconditionally, and LangChain 1.0 removed both.The
langchainextra declareslangchain>=0.3, which permits 1.x, anduv sync --extra all --extra devcurrently resolves to langchain 1.2.10. Sothe adapter's turnkey entry point is broken in the project's own canonical
environment, not just in some unusual user setup.
Depths 1 and 2 are unaffected — see Scope below.
Environment
janus-guard@2512be1e6(main)uv sync --extra all --extra dev)Steps to reproduce
Actual behaviour
The failure is at construction time, before any policy or tool wiring happens.
langchain.agentsin 1.2.10 exports only:Expected behaviour
Either the adapter works against the LangChain versions its own extra permits,
or the extra is narrowed so that a resolvable install is a working one.
Root cause
janus/adapters/langchain.py:261-264, insideJanusLangChainAgent.__init__:LangChain 1.0 replaced the
AgentExecutor+create_tool_calling_agentpairwith a single LangGraph-backed
create_agent. The import is unconditional andruns on every construction, so there is no version in which depth 3 degrades
gracefully — it either imports or raises.
pyproject.toml:44-48declares:The lower bound is correct for the 0.3 API; there is no upper bound excluding
the 1.x API that removed it.
Scope
Affected
JanusLangChainAgent— cannot be constructed at all.janus/adapters/langchain.py:89and the depth-3description at lines 20 and 205, which all reference
AgentExecutor.Not affected
secure_langchain_tools()— imports onlylangchain_core.tools.StructuredTool(line 43).wrap_langchain_tools()— same, plusBaseTool.So policy enforcement itself is fine on LangChain 1.x. It is only the turnkey
agent wrapper that breaks. No test covers depth 3 construction, which is why CI
is green.
Suggested fix
Option A — version-tolerant branch (recommended). Detect which generation
is installed and build the corresponding loop:
The two generations also differ at invoke time and need matching branches:
{"input": task}{"messages": [{"role": "user", "content": task}]}result["output"]result["messages"][-1].contentresult["intermediate_steps"]tool_callson the message listThis keeps the declared
langchain>=0.3honest.Option B — cap the extra at
langchain>=0.3,<1.0. Smaller change, but itlocks users out of LangChain 1.x for a limitation that only affects depth 3, and
depths 1 and 2 work fine there.
Option C — deprecate depth 3 in favour of depth 1 plus a user-built loop,
and document the
create_agentpattern.Whichever is chosen, the
chat_model=escape hatch does not currently help:the
AgentExecutorimport happens regardless of whether a pre-built model waspassed.
Workaround
demos/demo_2(PR #6) hits this and works around it by usingsecure_langchain_tools()(depth 1) and building the loop itself with acreate_agent/AgentExecutorbranch. Seedemos/demo_2/agent.py::IssueExplainerfor a working implementation ofOption A, including the invoke-shape differences above.
Note for whoever picks this up
The fix touches
janus/adapters/, so perCLAUDE.mdit needs theenforcement-reviewchecklist before commit. Worth adding a construction testfor all three depths against whichever LangChain generation CI installs —
the absence of one is what let this ship.