Skip to content

JanusLangChainAgent (adapter depth 3) cannot be constructed under LangChain 1.x #7

Description

@ish-codes-magic

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions