mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 18:50:33 +00:00
fix(core): preserve structured inputs on tool runs in tracers (#37108)
Tool runs in `_TracerCore._create_tool_run` were discarding the
structured `inputs` dict that `BaseTool.run` passes to `on_tool_start`,
replacing it with `{"input": str(filtered_tool_input)}`. Consequently,
every multi-arg tool (e.g. ones in `deepagents` like `execute`,
`edit_file`, `write_file`, `grep`, ...) appeared in LangSmith with a
stringified, escaped dump of its arguments — multi-line bash commands
rendered with `\n` and were effectively unreadable. Chain runs already
preserved dicts via `_get_chain_inputs`; tool runs are now symmetric.
## Changes
- Preserve `inputs` when it is already a `dict` in the `original` /
`original+chat` branch of `_TracerCore._create_tool_run`, falling back
to `{"input": input_str}` only when no structured payload was provided
- Add regression tests in the sync and async base-tracer suites that
pass a structured `inputs` to `on_tool_start` and assert the dict
survives onto the resulting `Run`
## Breaking change
Custom `BaseTracer` subclasses that parsed `Run.inputs["input"]` as a
stringified dict for tool runs will need to read the structured fields
directly. The shape now matches what `on_tool_start(inputs=...)` has
always received — introduced alongside `_schema_format` in the
`astream_events` work — and what `streaming_events` consumers already
see.
This commit is contained in:
@@ -449,7 +449,7 @@ class _TracerCore(ABC):
|
|||||||
kwargs.update({"metadata": metadata})
|
kwargs.update({"metadata": metadata})
|
||||||
|
|
||||||
if self._schema_format in {"original", "original+chat"}:
|
if self._schema_format in {"original", "original+chat"}:
|
||||||
inputs = {"input": input_str}
|
inputs = inputs if isinstance(inputs, dict) else {"input": input_str}
|
||||||
elif self._schema_format == "streaming_events":
|
elif self._schema_format == "streaming_events":
|
||||||
inputs = {"input": inputs}
|
inputs = {"input": inputs}
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -214,6 +214,40 @@ async def test_tracer_tool_run() -> None:
|
|||||||
assert tracer.runs == [compare_run]
|
assert tracer.runs == [compare_run]
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2023-01-01")
|
||||||
|
async def test_tracer_tool_run_preserves_structured_inputs() -> None:
|
||||||
|
"""Structured `inputs` from `BaseTool.run` should not be flattened to `str`."""
|
||||||
|
uuid = uuid4()
|
||||||
|
structured_inputs = {"command": "echo 'hello\nworld'", "timeout": None}
|
||||||
|
compare_run = Run(
|
||||||
|
id=str(uuid),
|
||||||
|
name="tool",
|
||||||
|
start_time=datetime.now(timezone.utc),
|
||||||
|
end_time=datetime.now(timezone.utc),
|
||||||
|
events=[
|
||||||
|
{"name": "start", "time": datetime.now(timezone.utc)},
|
||||||
|
{"name": "end", "time": datetime.now(timezone.utc)},
|
||||||
|
],
|
||||||
|
extra={},
|
||||||
|
serialized={"name": "tool"},
|
||||||
|
inputs=structured_inputs,
|
||||||
|
outputs={"output": "ok"},
|
||||||
|
error=None,
|
||||||
|
run_type="tool",
|
||||||
|
trace_id=uuid,
|
||||||
|
dotted_order=f"20230101T000000000000Z{uuid}",
|
||||||
|
)
|
||||||
|
tracer = FakeAsyncTracer()
|
||||||
|
await tracer.on_tool_start(
|
||||||
|
serialized={"name": "tool"},
|
||||||
|
input_str=str(structured_inputs),
|
||||||
|
run_id=uuid,
|
||||||
|
inputs=structured_inputs,
|
||||||
|
)
|
||||||
|
await tracer.on_tool_end("ok", run_id=uuid)
|
||||||
|
assert tracer.runs == [compare_run]
|
||||||
|
|
||||||
|
|
||||||
@freeze_time("2023-01-01")
|
@freeze_time("2023-01-01")
|
||||||
async def test_tracer_nested_run() -> None:
|
async def test_tracer_nested_run() -> None:
|
||||||
"""Test tracer on a nested run."""
|
"""Test tracer on a nested run."""
|
||||||
|
|||||||
@@ -217,6 +217,40 @@ def test_tracer_tool_run() -> None:
|
|||||||
assert tracer.runs == [compare_run]
|
assert tracer.runs == [compare_run]
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2023-01-01")
|
||||||
|
def test_tracer_tool_run_preserves_structured_inputs() -> None:
|
||||||
|
"""Structured `inputs` from `BaseTool.run` should not be flattened to `str`."""
|
||||||
|
uuid = uuid4()
|
||||||
|
structured_inputs = {"command": "echo 'hello\nworld'", "timeout": None}
|
||||||
|
compare_run = Run(
|
||||||
|
id=str(uuid),
|
||||||
|
name="tool",
|
||||||
|
start_time=datetime.now(timezone.utc),
|
||||||
|
end_time=datetime.now(timezone.utc),
|
||||||
|
events=[
|
||||||
|
{"name": "start", "time": datetime.now(timezone.utc)},
|
||||||
|
{"name": "end", "time": datetime.now(timezone.utc)},
|
||||||
|
],
|
||||||
|
extra={},
|
||||||
|
serialized={"name": "tool"},
|
||||||
|
inputs=structured_inputs,
|
||||||
|
outputs={"output": "ok"},
|
||||||
|
error=None,
|
||||||
|
run_type="tool",
|
||||||
|
trace_id=uuid,
|
||||||
|
dotted_order=f"20230101T000000000000Z{uuid}",
|
||||||
|
)
|
||||||
|
tracer = FakeTracer()
|
||||||
|
tracer.on_tool_start(
|
||||||
|
serialized={"name": "tool"},
|
||||||
|
input_str=str(structured_inputs),
|
||||||
|
run_id=uuid,
|
||||||
|
inputs=structured_inputs,
|
||||||
|
)
|
||||||
|
tracer.on_tool_end("ok", run_id=uuid)
|
||||||
|
assert tracer.runs == [compare_run]
|
||||||
|
|
||||||
|
|
||||||
@freeze_time("2023-01-01")
|
@freeze_time("2023-01-01")
|
||||||
def test_tracer_nested_run() -> None:
|
def test_tracer_nested_run() -> None:
|
||||||
"""Test tracer on a nested run."""
|
"""Test tracer on a nested run."""
|
||||||
|
|||||||
Reference in New Issue
Block a user