Merge pull request #353 from langchain-ai/nc/26apr/tracing-improv

Improve tracing output for function nodes
This commit is contained in:
Nuno Campos
2024-04-29 09:02:38 -07:00
committed by GitHub
4 changed files with 202 additions and 156 deletions
@@ -90,7 +90,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 1,
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
"metadata": {},
"outputs": [],
@@ -118,7 +118,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 2,
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
"metadata": {},
"outputs": [],
@@ -140,7 +140,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 3,
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
"metadata": {},
"outputs": [
@@ -148,7 +148,7 @@
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/nuno/dev/langgraph/.venv/lib/python3.11/site-packages/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: The function `bind_tools` is in beta. It is actively being worked on, so the API may change.\n",
"/Users/nuno/dev/langgraph/.venv/lib/python3.11/site-packages/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: The method `ChatAnthropic.bind_tools` is in beta. It is actively being worked on, so the API may change.\n",
" warn_beta(\n"
]
}
@@ -159,7 +159,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 4,
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
"metadata": {},
"outputs": [],
@@ -202,7 +202,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 5,
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
"metadata": {},
"outputs": [],
@@ -246,7 +246,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 6,
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
"metadata": {},
"outputs": [],
@@ -308,7 +308,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 7,
"id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752",
"metadata": {},
"outputs": [
@@ -316,12 +316,12 @@
"data": {
"text/plain": [
"{'messages': [HumanMessage(content='what is the weather in sf'),\n",
" AIMessage(content=[{'text': '<thinking>\\nThe relevant tool to answer this question is tavily_search_results_json, which can provide comprehensive results about current events like weather.\\n\\nTo call tavily_search_results_json, I need to provide a value for the required \"query\" parameter. The user\\'s request directly specifies the query to search for - \"weather in sf\". \"sf\" here likely refers to San Francisco.\\n\\nI have the required parameter value to make the API call, so I will proceed with the search.\\n</thinking>', 'type': 'text'}, {'id': 'toolu_01AnGNJEYsAvrJpJYh6MuV16', 'input': {'query': 'weather in San Francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], response_metadata={'id': 'msg_01BujbLhL23TFWN8zrarY4So', 'model': 'claude-3-opus-20240229', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 507, 'output_tokens': 170}}, id='run-3c5a870b-48b4-4b94-8c70-f5dc322da35a-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'toolu_01AnGNJEYsAvrJpJYh6MuV16'}]),\n",
" ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1712857380, \\'localtime\\': \\'2024-04-11 10:43\\'}, \\'current\\': {\\'last_updated_epoch\\': 1712856600, \\'last_updated\\': \\'2024-04-11 10:30\\', \\'temp_c\\': 15.6, \\'temp_f\\': 60.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 4.3, \\'wind_kph\\': 6.8, \\'wind_degree\\': 50, \\'wind_dir\\': \\'NE\\', \\'pressure_mb\\': 1015.0, \\'pressure_in\\': 29.96, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 78, \\'cloud\\': 25, \\'feelslike_c\\': 15.6, \\'feelslike_f\\': 60.1, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 5.1, \\'gust_kph\\': 8.3}}\"}]', name='tavily_search_results_json', tool_call_id='toolu_01AnGNJEYsAvrJpJYh6MuV16'),\n",
" AIMessage(content=\"<search_quality_reflection>\\nThe search results provide a comprehensive weather report for San Francisco, including the current temperature, conditions, wind, humidity, and other relevant details. This should be sufficient to fully answer the question of what the current weather is like in San Francisco.\\n</search_quality_reflection>\\n<search_quality_score>5</search_quality_score>\\n\\n<result>\\nAccording to the weather report, the current weather in San Francisco is:\\n\\nTemperature: 60.1°F (15.6°C)\\nConditions: Partly cloudy \\nWind: 4.3 mph (6.8 km/h) from the northeast\\nHumidity: 78%\\n\\nIt feels like 60.1°F (15.6°C). Visibility is 9 miles (16 km). The UV index is 5 out of 10.\\n\\nSo in summary, it's a mild spring day in San Francisco with some cloud cover, light winds, and comfortable temperatures in the low 60s Fahrenheit. A light jacket or sweater should suffice for being outdoors.\\n</result>\", response_metadata={'id': 'msg_01KhNRhAaoyT8v7kk8QJUyYj', 'model': 'claude-3-opus-20240229', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 1102, 'output_tokens': 250}}, id='run-5694fbc4-8041-4577-a007-227e09ef5bd8-0')]}"
" AIMessage(content=[{'text': '<thinking>\\nThe relevant tool to answer this question is tavily_search_results_json, which can provide comprehensive information about current events like weather.\\n\\nTo call this function, I need to provide a value for the required \"query\" parameter. The user\\'s request directly specifies they want to know the weather in \"sf\", which I can reasonably infer refers to San Francisco.\\n\\nTherefore, I have enough information to populate the required parameter:\\nquery = \"weather in San Francisco\"\\n\\n</thinking>', 'type': 'text'}, {'id': 'toolu_0183a3MorRJu43zykiCWKAyo', 'input': {'query': 'weather in San Francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], response_metadata={'id': 'msg_01Lg8ZNFNwbDXz9VfxZyRCSb', 'model': 'claude-3-opus-20240229', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 507, 'output_tokens': 166}}, id='run-587209cf-1406-47f1-9476-73f9c75f4650-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'toolu_0183a3MorRJu43zykiCWKAyo'}]),\n",
" ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714170321, \\'localtime\\': \\'2024-04-26 15:25\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714169700, \\'last_updated\\': \\'2024-04-26 15:15\\', \\'temp_c\\': 17.2, \\'temp_f\\': 63.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 34.9, \\'wind_kph\\': 56.2, \\'wind_degree\\': 280, \\'wind_dir\\': \\'W\\', \\'pressure_mb\\': 1017.0, \\'pressure_in\\': 30.02, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 60, \\'cloud\\': 50, \\'feelslike_c\\': 17.2, \\'feelslike_f\\': 63.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 4.0, \\'gust_mph\\': 39.4, \\'gust_kph\\': 63.4}}\"}]', name='tavily_search_results_json', tool_call_id='toolu_0183a3MorRJu43zykiCWKAyo'),\n",
" AIMessage(content=\"<search_quality_reflection>\\nThe search results provide a comprehensive and up-to-date weather report for San Francisco, including key details like the current temperature, weather conditions, wind, humidity, and more. This should be sufficient to fully answer the question of what the current weather is like in San Francisco.\\n</search_quality_reflection>\\n\\n<search_quality_score>5</search_quality_score>\\n\\n<result>\\nAccording to the current weather report, the weather in San Francisco right now is:\\n\\nTemperature: 63°F (17.2°C)\\nConditions: Partly cloudy \\nWind: 34.9 mph (56.2 km/h) winds from the west\\nHumidity: 60%\\n\\nIt feels like 63°F (17.2°C). Visibility is good at 9 miles (16 km). The UV index is moderate at 4.0 out of 11. \\n\\nOverall, it's a mild spring day in San Francisco with some cloud cover and breezy conditions. A light jacket or sweater should suffice for being outdoors.\\n</result>\", response_metadata={'id': 'msg_01LS72RMeicMF1xT7enopKpJ', 'model': 'claude-3-opus-20240229', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 1097, 'output_tokens': 251}}, id='run-794deb88-bea5-4d0d-93db-bf5dc38445f0-0')]}"
]
},
"execution_count": 8,
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
+4 -4
View File
@@ -13,7 +13,7 @@ from typing import (
)
from langchain_core.runnables import Runnable
from langchain_core.runnables.base import RunnableLike, coerce_to_runnable
from langchain_core.runnables.base import RunnableLike
from langchain_core.runnables.config import RunnableConfig
from langchain_core.runnables.graph import (
Node as RunnableGraphNode,
@@ -25,7 +25,7 @@ from langgraph.constants import TAG_HIDDEN
from langgraph.pregel import Channel, Pregel
from langgraph.pregel.read import PregelNode
from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry
from langgraph.utils import DrawableGraph, RunnableCallable
from langgraph.utils import DrawableGraph, RunnableCallable, coerce_to_runnable
logger = logging.getLogger(__name__)
@@ -112,7 +112,7 @@ class Graph:
if key == END or key == START:
raise ValueError(f"Node `{key}` is reserved.")
self.nodes[key] = coerce_to_runnable(action)
self.nodes[key] = coerce_to_runnable(action, name=key, trace=False)
def add_edge(self, start_key: str, end_key: str) -> None:
if self.compiled:
@@ -167,7 +167,7 @@ class Graph:
"not be reflected in the compiled graph."
)
# find a name for the condition
path = coerce_to_runnable(path)
path = coerce_to_runnable(path, name=None, trace=True)
name = path.name or "condition"
# validate the condition
if name in self.branches[source]:
+82 -6
View File
@@ -1,9 +1,25 @@
import asyncio
import enum
import inspect
import sys
from contextvars import copy_context
from functools import partial
from typing import Any, Awaitable, Callable, Optional
from langchain_core.runnables import Runnable, RunnableConfig
from langchain_core.runnables.config import merge_configs
from langchain_core.runnables.base import (
Runnable,
RunnableConfig,
RunnableLambda,
RunnableLike,
RunnableParallel,
)
from langchain_core.runnables.config import (
merge_configs,
run_in_executor,
var_child_runnable_config,
)
from langchain_core.runnables.graph import Edge, Graph, Node, is_uuid
from langchain_core.runnables.utils import accepts_config
# Before Python 3.11 native StrEnum is not available
@@ -27,7 +43,19 @@ class RunnableCallable(Runnable):
recurse: bool = True,
**kwargs: Any,
) -> None:
self.name = name or func.__name__
if name is not None:
self.name = name
elif func:
try:
if func.__name__ != "<lambda>":
self.name = func.__name__
except AttributeError:
pass
elif afunc:
try:
self.name = afunc.__name__
except AttributeError:
pass
self.func = func
self.afunc = afunc
self.config = {"tags": tags} if tags else None
@@ -49,7 +77,15 @@ class RunnableCallable(Runnable):
self.func, input, merge_configs(self.config, config), **self.kwargs
)
else:
ret = self.func(input, merge_configs(self.config, config), **self.kwargs)
config = merge_configs(self.config, config)
context = copy_context()
context.run(var_child_runnable_config.set, config)
kwargs = (
{**self.kwargs, "config": config}
if accepts_config(self.func)
else self.kwargs
)
ret = context.run(self.func, input, **kwargs)
if isinstance(ret, Runnable) and self.recurse:
return ret.invoke(input, config)
return ret
@@ -62,9 +98,20 @@ class RunnableCallable(Runnable):
self.afunc, input, merge_configs(self.config, config), **self.kwargs
)
else:
ret = await self.afunc(
input, merge_configs(self.config, config), **self.kwargs
config = merge_configs(self.config, config)
context = copy_context()
context.run(var_child_runnable_config.set, config)
kwargs = (
{**self.kwargs, "config": config}
if accepts_config(self.afunc)
else self.kwargs
)
if sys.version_info >= (3, 11):
ret = await asyncio.create_task(
self.afunc(input, **kwargs), context=context
)
else:
ret = await self.afunc(input, **kwargs)
if isinstance(ret, Runnable) and self.recurse:
return await ret.ainvoke(input, config)
return ret
@@ -99,3 +146,32 @@ class DrawableGraph(Graph):
Node(f"{prefix}:{first.id}", first.data) if first else None,
Node(f"{prefix}:{last.id}", last.data) if last else None,
)
def coerce_to_runnable(thing: RunnableLike, *, name: str, trace: bool) -> Runnable:
"""Coerce a runnable-like object into a Runnable.
Args:
thing: A runnable-like object.
Returns:
A Runnable.
"""
if isinstance(thing, Runnable):
return thing
elif inspect.isasyncgenfunction(thing) or inspect.isgeneratorfunction(thing):
return RunnableLambda(thing, name=name)
elif callable(thing):
if asyncio.iscoroutinefunction(thing):
return RunnableCallable(None, thing, name=name, trace=trace)
else:
return RunnableCallable(
thing, partial(run_in_executor, None, thing), name=name, trace=trace
)
elif isinstance(thing, dict):
return RunnableParallel(thing)
else:
raise TypeError(
f"Expected a Runnable, callable or dict."
f"Instead got an unsupported type: {type(thing)}"
)
+104 -134
View File
@@ -252,10 +252,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "left"
}
@@ -265,10 +264,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "right"
}
@@ -278,10 +276,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_start"
}
@@ -291,12 +288,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "RunnableLambda"
"name": "RunnableCallable"
}
}
],
@@ -391,10 +387,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "left"
}
@@ -404,10 +399,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "right"
}
@@ -417,10 +411,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_start"
}
@@ -430,12 +423,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "RunnableLambda"
"name": "RunnableCallable"
}
}
],
@@ -537,12 +529,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "execute_tools"
"name": "tools"
}
},
{
@@ -550,10 +541,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -704,12 +694,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "execute_tools"
"name": "tools"
}
},
{
@@ -717,10 +706,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -876,12 +864,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "execute_tools"
"name": "tools"
}
},
{
@@ -889,10 +876,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -1043,12 +1029,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "execute_tools"
"name": "tools"
}
},
{
@@ -1056,10 +1041,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -1221,12 +1205,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "execute_tools"
"name": "tools"
}
},
{
@@ -1234,10 +1217,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -1335,12 +1317,11 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "execute_tools"
"name": "tools"
}
},
{
@@ -1348,10 +1329,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -1683,10 +1663,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -1796,10 +1775,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -1973,12 +1951,11 @@
dict({
'data': dict({
'id': list([
'langchain_core',
'runnables',
'base',
'RunnableLambda',
'langgraph',
'utils',
'RunnableCallable',
]),
'name': 'logic',
'name': 'tool_one',
}),
'id': 'tool_one',
'type': 'runnable',
@@ -1991,12 +1968,11 @@
dict({
'data': dict({
'id': list([
'langchain_core',
'runnables',
'base',
'RunnableLambda',
'langgraph',
'utils',
'RunnableCallable',
]),
'name': 'logic',
'name': 'tool_two_slow',
}),
'id': 'tool_two:tool_two_slow',
'type': 'runnable',
@@ -2004,12 +1980,11 @@
dict({
'data': dict({
'id': list([
'langchain_core',
'runnables',
'base',
'RunnableLambda',
'langgraph',
'utils',
'RunnableCallable',
]),
'name': 'logic',
'name': 'tool_two_fast',
}),
'id': 'tool_two:tool_two_fast',
'type': 'runnable',
@@ -2017,12 +1992,11 @@
dict({
'data': dict({
'id': list([
'langchain_core',
'runnables',
'base',
'RunnableLambda',
'langgraph',
'utils',
'RunnableCallable',
]),
'name': 'RunnableLambda',
'name': 'RunnableCallable',
}),
'id': 'tool_two:condition',
'type': 'runnable',
@@ -2030,12 +2004,11 @@
dict({
'data': dict({
'id': list([
'langchain_core',
'runnables',
'base',
'RunnableLambda',
'langgraph',
'utils',
'RunnableCallable',
]),
'name': 'logic',
'name': 'tool_three',
}),
'id': 'tool_three',
'type': 'runnable',
@@ -2043,12 +2016,11 @@
dict({
'data': dict({
'id': list([
'langchain_core',
'runnables',
'base',
'RunnableLambda',
'langgraph',
'utils',
'RunnableCallable',
]),
'name': 'RunnableLambda',
'name': 'RunnableCallable',
}),
'id': 'condition',
'type': 'runnable',
@@ -2164,10 +2136,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}
@@ -2278,10 +2249,9 @@
"type": "runnable",
"data": {
"id": [
"langchain_core",
"runnables",
"base",
"RunnableLambda"
"langgraph",
"utils",
"RunnableCallable"
],
"name": "should_continue"
}