chore: import upstream snapshot with attribution
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""Exercise `thread.tool_calls` against the `tools_agent` graph.
|
||||
|
||||
`tools_agent` (`graph/tools_agent.py`) wraps a
|
||||
`FakeMessagesListChatModel` in `create_agent` with a real `search`
|
||||
tool. The first scripted model turn returns an `AIMessage` with a
|
||||
`tool_calls=[search(query="v3")]`; langchain's tool node then executes
|
||||
`search` and surfaces a `ToolMessage`; the second turn returns a final
|
||||
`AIMessage("done.")` that terminates the agent.
|
||||
|
||||
Compared to `test_tool_calls.py` (which targets the synthetic
|
||||
`streaming_graph` and never produces real tool-call telemetry), this
|
||||
test verifies the v3 ``tools`` channel actually fires when the
|
||||
canonical langchain-agent surface is in play.
|
||||
|
||||
Pattern note: `thread.tool_calls` yields handles incrementally, but
|
||||
each handle's `deltas` and `output` are only completed when the
|
||||
*outer* iterator processes the matching `tool-finished` event. Drain
|
||||
the outer iterator to completion FIRST (via a list comprehension),
|
||||
then inspect handles -- this is the same pattern used in
|
||||
`tests/streaming/test_tool_calls_projection.py`. Iterating
|
||||
`handle.deltas` while the outer iterator is still suspended at its
|
||||
`yield` deadlocks.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from _common import check_api_reachable, header, make_async_client, make_sync_client
|
||||
|
||||
TOOLS_ASSISTANT_ID = "tools_agent"
|
||||
|
||||
|
||||
async def run_async() -> None:
|
||||
header("async tools_agent tool_calls")
|
||||
threads, raw = make_async_client()
|
||||
try:
|
||||
async with threads.stream(assistant_id=TOOLS_ASSISTANT_ID) as thread:
|
||||
await thread.run.start(
|
||||
input={"messages": [{"role": "human", "content": "search for v3"}]}
|
||||
)
|
||||
|
||||
# Drain the outer iterator first; lifecycle-terminal triggers
|
||||
# the None sentinel via the shared SSE fanout once the run
|
||||
# completes naturally.
|
||||
handles = [h async for h in thread.tool_calls]
|
||||
print(f" total handles: {len(handles)}")
|
||||
for handle in handles:
|
||||
deltas = [d async for d in handle.deltas]
|
||||
output = await handle.output
|
||||
joined = "".join(deltas)
|
||||
print(
|
||||
f" tool {handle.name}({handle.tool_call_id}): "
|
||||
f"args_stream={joined!r} output={output!r}"
|
||||
)
|
||||
assert any(h.name == "search" for h in handles), (
|
||||
"expected `search` tool call"
|
||||
)
|
||||
finally:
|
||||
await raw.aclose()
|
||||
|
||||
|
||||
def run_sync() -> None:
|
||||
header("sync tools_agent tool_calls")
|
||||
threads, raw = make_sync_client()
|
||||
try:
|
||||
with threads.stream(assistant_id=TOOLS_ASSISTANT_ID) as thread:
|
||||
thread.run.start(
|
||||
input={"messages": [{"role": "human", "content": "search for v3"}]}
|
||||
)
|
||||
|
||||
handles = list(thread.tool_calls)
|
||||
print(f" total handles: {len(handles)}")
|
||||
for handle in handles:
|
||||
deltas = list(handle.deltas)
|
||||
output = handle.output
|
||||
joined = "".join(deltas)
|
||||
print(
|
||||
f" tool {handle.name}({handle.tool_call_id}): "
|
||||
f"args_stream={joined!r} output={output!r}"
|
||||
)
|
||||
assert any(h.name == "search" for h in handles), (
|
||||
"expected `search` tool call"
|
||||
)
|
||||
finally:
|
||||
raw.close()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
check_api_reachable()
|
||||
asyncio.run(run_async())
|
||||
run_sync()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user