mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
revert: feat(langchain): ls_agent_type tag on create_agent calls (#37249)
This commit is contained in:
@@ -1647,12 +1647,7 @@ def create_agent(
|
||||
|
||||
# Set recursion limit to 9_999
|
||||
# https://github.com/langchain-ai/langgraph/issues/7313
|
||||
config: RunnableConfig = {
|
||||
"recursion_limit": 9_999,
|
||||
"configurable": {
|
||||
"ls_agent_type": "root",
|
||||
},
|
||||
}
|
||||
config: RunnableConfig = {"recursion_limit": 9_999}
|
||||
config["metadata"] = {"ls_integration": "langchain_create_agent"}
|
||||
if name:
|
||||
config["metadata"]["lc_agent_name"] = name
|
||||
|
||||
@@ -16,17 +16,12 @@ configurations.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Annotated, Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from langchain_core.callbacks import BaseCallbackHandler
|
||||
from langchain_core.messages import HumanMessage, ToolMessage
|
||||
from langchain_core.tools import tool
|
||||
from langgraph.prebuilt import InjectedStore
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
from langsmith import Client
|
||||
from langsmith.run_helpers import tracing_context
|
||||
|
||||
from langchain.agents import create_agent
|
||||
from langchain.agents.middleware.types import AgentMiddleware, AgentState
|
||||
@@ -847,109 +842,3 @@ async def test_combined_injected_state_runtime_store_async() -> None:
|
||||
# Verify store was injected and writable
|
||||
assert injected_data["store"] is not None
|
||||
assert injected_data["store_write_success"] is True
|
||||
|
||||
|
||||
def test_ls_agent_type_is_trace_only_metadata() -> None:
|
||||
"""Test that ls_agent_type is added to metadata on tracing only, not in streamed chunks."""
|
||||
# Capture metadata from regular callback handler (simulates streamed metadata)
|
||||
captured_callback_metadata: list[dict[str, Any]] = []
|
||||
|
||||
class CaptureHandler(BaseCallbackHandler):
|
||||
def on_chain_start(
|
||||
self,
|
||||
serialized: dict[str, Any],
|
||||
inputs: dict[str, Any],
|
||||
*,
|
||||
run_id: str,
|
||||
parent_run_id: str | None = None,
|
||||
tags: list[str] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
captured_callback_metadata.append({"tags": tags, "metadata": metadata})
|
||||
|
||||
# Create a mock client to capture what gets sent to LangSmith
|
||||
mock_session = MagicMock()
|
||||
mock_client = Client(session=mock_session, api_key="test", auto_batch_tracing=False)
|
||||
|
||||
agent = create_agent(
|
||||
model=FakeToolCallingModel(tool_calls=[[], []]),
|
||||
tools=[],
|
||||
system_prompt="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
# Use tracing_context to enable tracing with the mock client
|
||||
with tracing_context(client=mock_client, enabled=True):
|
||||
agent.invoke(
|
||||
{"messages": [HumanMessage("hi?")]},
|
||||
config={"callbacks": [CaptureHandler()]},
|
||||
)
|
||||
|
||||
# Verify that ls_agent_type is NOT in the regular callback metadata
|
||||
# (it should only go to the tracer via langsmith_inheritable_metadata)
|
||||
assert len(captured_callback_metadata) > 0
|
||||
for captured in captured_callback_metadata:
|
||||
metadata = captured.get("metadata") or {}
|
||||
assert metadata.get("ls_agent_type") is None, (
|
||||
f"ls_agent_type should not be in callback metadata, but got: {metadata}"
|
||||
)
|
||||
|
||||
# Verify that ls_agent_type IS in the tracer metadata (sent to LangSmith)
|
||||
# Get the POST requests to the LangSmith API
|
||||
posts = []
|
||||
for call in mock_session.request.mock_calls:
|
||||
if call.args and call.args[0] == "POST":
|
||||
body = json.loads(call.kwargs["data"])
|
||||
if "post" in body:
|
||||
posts.extend(body["post"])
|
||||
else:
|
||||
posts.append(body)
|
||||
|
||||
assert len(posts) >= 1
|
||||
# Find the root run (the agent execution)
|
||||
root_post = posts[0]
|
||||
metadata = root_post.get("extra", {}).get("metadata", {})
|
||||
assert metadata.get("ls_agent_type") == "root", (
|
||||
f"ls_agent_type should be 'root' in tracer metadata, but got: {metadata}"
|
||||
)
|
||||
|
||||
|
||||
def test_ls_agent_type_is_overridable() -> None:
|
||||
"""Test that ls_agent_type can be overridden via configurable in invoke config."""
|
||||
# Create a mock client to capture what gets sent to LangSmith
|
||||
mock_session = MagicMock()
|
||||
mock_client = Client(session=mock_session, api_key="test", auto_batch_tracing=False)
|
||||
|
||||
agent = create_agent(
|
||||
model=FakeToolCallingModel(tool_calls=[[], []]),
|
||||
tools=[],
|
||||
system_prompt="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
# Use tracing_context to enable tracing with the mock client
|
||||
with tracing_context(client=mock_client, enabled=True):
|
||||
agent.invoke(
|
||||
{"messages": [HumanMessage("hi?")]},
|
||||
config={"configurable": {"ls_agent_type": "subagent", "custom_key": "custom_value"}},
|
||||
)
|
||||
|
||||
# Verify that ls_agent_type is overridden and configurable is merged in the tracer metadata
|
||||
posts = []
|
||||
for call in mock_session.request.mock_calls:
|
||||
if call.args and call.args[0] == "POST":
|
||||
body = json.loads(call.kwargs["data"])
|
||||
if "post" in body:
|
||||
posts.extend(body["post"])
|
||||
else:
|
||||
posts.append(body)
|
||||
|
||||
assert len(posts) >= 1
|
||||
root_post = posts[0]
|
||||
metadata = root_post.get("extra", {}).get("metadata", {})
|
||||
assert metadata.get("ls_agent_type") == "subagent", (
|
||||
f"ls_agent_type should be 'subagent' in tracer metadata, but got: {metadata}"
|
||||
)
|
||||
# Verify that the additional configurable key is merged into metadata
|
||||
assert metadata.get("custom_key") == "custom_value", (
|
||||
f"custom_key should be 'custom_value' in tracer metadata, but got: {metadata}"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Regression tests for subagent stream event propagation.
|
||||
|
||||
Reproduces a bug where `create_agent` set ``ls_agent_type`` inside the
|
||||
parent agent's ``configurable`` and, as a side effect, ``updates``,
|
||||
``values``, and ``custom`` stream events from sub-agents invoked through
|
||||
tools were dropped during ``stream(..., subgraphs=True)``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from langchain_core.messages import HumanMessage, ToolCall
|
||||
from langchain_core.tools import tool
|
||||
|
||||
from langchain.agents import create_agent
|
||||
from tests.unit_tests.agents.model import FakeToolCallingModel
|
||||
|
||||
|
||||
def _make_subagent_caller_tool():
|
||||
"""Build a subagent and a tool that invokes it."""
|
||||
subagent = create_agent(
|
||||
model=FakeToolCallingModel(tool_calls=[[]]),
|
||||
name="subagent",
|
||||
)
|
||||
|
||||
@tool
|
||||
def call_subagent(query: str) -> str:
|
||||
"""Delegate the query to a sub-agent."""
|
||||
result = subagent.invoke({"messages": [HumanMessage(query)]})
|
||||
return result["messages"][-1].text
|
||||
|
||||
return call_subagent
|
||||
|
||||
|
||||
def _make_parent_agent(call_subagent_tool) -> object:
|
||||
parent_tool_calls: list[list[ToolCall]] = [
|
||||
[{"args": {"query": "hi"}, "id": "call_1", "name": "call_subagent"}],
|
||||
[],
|
||||
]
|
||||
return create_agent(
|
||||
model=FakeToolCallingModel(tool_calls=parent_tool_calls),
|
||||
tools=[call_subagent_tool],
|
||||
name="parent",
|
||||
)
|
||||
|
||||
|
||||
def test_subagent_updates_emitted_when_streaming_with_subgraphs() -> None:
|
||||
"""`updates` events from a tool-invoked sub-agent must be streamed.
|
||||
|
||||
Without the fix, the parent agent's ``configurable`` overrode the
|
||||
streaming machinery's per-run state, suppressing ``updates`` events
|
||||
from any sub-graph invoked inside a tool.
|
||||
"""
|
||||
call_subagent_tool = _make_subagent_caller_tool()
|
||||
parent = _make_parent_agent(call_subagent_tool)
|
||||
|
||||
subagent_update_events = []
|
||||
for namespace, mode, _data in parent.stream(
|
||||
{"messages": [HumanMessage("hi")]},
|
||||
stream_mode=["updates", "messages"],
|
||||
subgraphs=True,
|
||||
):
|
||||
if mode == "updates" and namespace:
|
||||
subagent_update_events.append(namespace)
|
||||
|
||||
assert subagent_update_events, (
|
||||
"expected `updates` events from the sub-agent's subgraph namespace, but none were emitted"
|
||||
)
|
||||
|
||||
|
||||
async def test_subagent_updates_emitted_when_astreaming_with_subgraphs() -> None:
|
||||
"""Async counterpart of the sync regression test."""
|
||||
call_subagent_tool = _make_subagent_caller_tool()
|
||||
parent = _make_parent_agent(call_subagent_tool)
|
||||
|
||||
subagent_update_events = []
|
||||
async for namespace, mode, _data in parent.astream(
|
||||
{"messages": [HumanMessage("hi")]},
|
||||
stream_mode=["updates", "messages"],
|
||||
subgraphs=True,
|
||||
):
|
||||
if mode == "updates" and namespace:
|
||||
subagent_update_events.append(namespace)
|
||||
|
||||
assert subagent_update_events, (
|
||||
"expected `updates` events from the sub-agent's subgraph namespace, but none were emitted"
|
||||
)
|
||||
Reference in New Issue
Block a user