Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions src/uipath_langchain/agent/advanced/code_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@

_RESERVED_TOOL_NAMES = frozenset({"task"})

# Upstream's default ``tool_name``; the factory does not override it.
EVAL_TOOL_NAME = "eval"

# Subagent spec keys that make deepagents interrupt without a stamped tool.
_SUBAGENT_INTERRUPT_KEYS = ("interrupt_on", "permissions", "middleware")

Expand All @@ -60,6 +63,20 @@
# computation, and a bridged tool call does not consume it.
DEFAULT_EVAL_TIMEOUT_SECONDS = 5.0

SINGLE_IN_FLIGHT_NOTE = (
" Only one eval may run at a time: they share one interpreter and its state, "
"so a second call issued in the same turn fails instead of queueing. Put the "
"work in one call and use `await Promise.all([...])` to parallelise inside it."
)
"""Appended to the ``eval`` tool description by the factory.

Upstream renders that description from the persistence mode and offers no
override, and the single-in-flight rule is not in it. It is also not expressible
as a tool schema field: ``parallel_tool_calls`` is a request-level switch in both
the OpenAI and Anthropic APIs, so a model that is not told batches two ``eval``
calls and loses a turn to ``ConcurrentEvalError``.
"""


def ptc_tool_names(tools: Sequence[BaseTool]) -> list[str]:
"""Names of the agent tools that may be called from inside the REPL.
Expand Down Expand Up @@ -205,14 +222,33 @@ def build_code_interpreter_middleware(
len(tools),
"offered" if dispatch else "withheld",
)
return [
middleware_cls(
ptc=[*exposed, *PTC_FILESYSTEM_TOOLS],
mode=mode,
subagents=dispatch,
timeout=timeout,
)
]
middleware = middleware_cls(
ptc=[*exposed, *PTC_FILESYSTEM_TOOLS],
mode=mode,
subagents=dispatch,
timeout=timeout,
)
_append_single_in_flight_note(middleware)
return [middleware]


def _append_single_in_flight_note(middleware: Any) -> None:
"""Tell the model the REPL takes one call at a time.

Mutates the description of the tool this factory just built, rather than the
class, so no other consumer of ``langchain_quickjs`` is affected. A rendering
change upstream drops the note rather than corrupting it, which the factory
test catches.
"""
for tool in getattr(middleware, "tools", ()):
if tool.name == EVAL_TOOL_NAME:
tool.description = tool.description.rstrip() + SINGLE_IN_FLIGHT_NOTE
return
logger.warning(
"Code interpreter: no %r tool to annotate, so the model is not told that "
"only one eval may be in flight",
EVAL_TOOL_NAME,
)


def _without_camel_collisions(
Expand Down
20 changes: 20 additions & 0 deletions tests/agent/advanced/test_code_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
ptc_tool_names,
subagent_dispatch_is_replay_safe,
)
from uipath_langchain.agent.advanced.code_interpreter import (
EVAL_TOOL_NAME,
SINGLE_IN_FLIGHT_NOTE,
)

pytest.importorskip("langchain_quickjs", reason="needs the code-interpreter extra")

Expand Down Expand Up @@ -250,6 +254,22 @@ def test_dispatch_withheld_for_a_precompiled_subagent() -> None:
)


def test_eval_description_tells_the_model_only_one_may_be_in_flight() -> None:
"""The REPL takes one call at a time, and nothing else tells the model.

Upstream renders the description and offers no override, and per-tool
parallelism is not expressible in a tool schema, so a model that is not told
batches two ``eval`` calls in one turn and loses one to ``ConcurrentEvalError``.
Asserted on the description the model is shown, not on the constant.
"""
middleware = build_code_interpreter_middleware([_tool("read_invoice")])[0]
description = {t.name: t for t in middleware.tools}[EVAL_TOOL_NAME].description

assert SINGLE_IN_FLIGHT_NOTE.strip() in description
# The rendered description survives ahead of the note rather than being replaced.
assert description.startswith("Execute JavaScript")


def test_factory_returns_one_middleware() -> None:
"""The factory hands back exactly one entry, spliceable into a sequence."""
assert len(build_code_interpreter_middleware([_tool("read_invoice")])) == 1
Expand Down
Loading