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
|
# Set recursion limit to 9_999
|
||||||
# https://github.com/langchain-ai/langgraph/issues/7313
|
# https://github.com/langchain-ai/langgraph/issues/7313
|
||||||
config: RunnableConfig = {
|
config: RunnableConfig = {"recursion_limit": 9_999}
|
||||||
"recursion_limit": 9_999,
|
|
||||||
"configurable": {
|
|
||||||
"ls_agent_type": "root",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
config["metadata"] = {"ls_integration": "langchain_create_agent"}
|
config["metadata"] = {"ls_integration": "langchain_create_agent"}
|
||||||
if name:
|
if name:
|
||||||
config["metadata"]["lc_agent_name"] = name
|
config["metadata"]["lc_agent_name"] = name
|
||||||
|
|||||||
@@ -16,17 +16,12 @@ configurations.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import TYPE_CHECKING, Annotated, Any
|
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.messages import HumanMessage, ToolMessage
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from langgraph.prebuilt import InjectedStore
|
from langgraph.prebuilt import InjectedStore
|
||||||
from langgraph.store.memory import InMemoryStore
|
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 import create_agent
|
||||||
from langchain.agents.middleware.types import AgentMiddleware, AgentState
|
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
|
# Verify store was injected and writable
|
||||||
assert injected_data["store"] is not None
|
assert injected_data["store"] is not None
|
||||||
assert injected_data["store_write_success"] is True
|
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