mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-22 07:32:25 +02:00
96 KiB
96 KiB
In [1]:
import ast
import json
import re
from typing import Any, Optional, Sequence, Union
from langchain.agents.agent import AgentOutputParser
from langchain.schema import OutputParserException
from langchain_core.tools import BaseTool
THOUGHT_PATTERN = r"Thought: ([^\n]*)"
# ACTION_PATTERN = r"\n*(\d+)\. (.*?})(\s*#\w+\n)?"
# $1 or ${1} -> 1
ID_PATTERN = r"\$\{?(\d+)\}?"
END_OF_PLAN = "<END_OF_PLAN>"
class ActionParserFSM:
def __init__(self):
self.reset()
def reset(self):
self.state = "START"
self.task_index = ""
self.action = ""
self.comment = ""
self.bracket_count = 0
self.actions = []
def parse(self, text: str):
for char in text:
action = self.process_char(char)
if action:
yield action
action = self.save_action()
if action:
yield action
def process_char(self, char: str) -> Optional[dict]:
action = None
if self.state == "START":
if char.isdigit():
self.state = "NUMBER"
self.task_index += char
elif char == "\n":
self.reset()
elif self.state == "NUMBER":
if char == ".":
self.state = "ACTION"
elif char.isdigit():
self.task_index += char
else:
self.reset()
elif self.state == "ACTION":
if char == "{":
self.bracket_count += 1
elif char == "}":
self.bracket_count -= 1
if self.bracket_count == 0:
self.state = "COMMENT"
self.action += char
elif self.state == "COMMENT":
if char == "\n":
action = self.save_action()
self.reset()
else:
self.comment += char
return action
def save_action(self):
if self.task_index and self.action:
parsed_action = json.loads(self.action.strip())
tool_name, args = next(iter(parsed_action.items()))
return {
"task_index": int(self.task_index),
"tool_name": tool_name,
"args": args,
}
class LLMCompilerPlanParser(AgentOutputParser, extra="allow"):
"""Planning output parser."""
def __init__(self, tools: Sequence[BaseTool], **kwargs):
super().__init__(**kwargs)
self.tools = tools
def parse(self, text: str) -> list[str]:
parser = ActionParserFSM()
graph_dict = {}
for task in parser.parse(text):
idx = int(task["task_index"])
task = instantiate_task(
tools=self.tools,
idx=idx,
tool_name=task["tool_name"],
args=task["args"],
)
graph_dict[idx] = task
if task["tool"] == "join":
break
return graph_dict
### Helper functions
def default_dependency_rule(idx, args: str):
matches = re.findall(ID_PATTERN, args)
numbers = [int(match) for match in matches]
return idx in numbers
def _get_dependencies_from_graph(
idx: int, tool_name: str, args: Sequence[Any]
) -> dict[str, list[str]]:
"""Get dependencies from a graph."""
if tool_name == "join":
return list(range(1, idx))
return [i for i in range(1, idx) if default_dependency_rule(i, str(args))]
def instantiate_task(
tools: Sequence[BaseTool],
idx: int,
tool_name: str,
args: Union[dict, str, bool, None],
) -> dict:
dependencies = _get_dependencies_from_graph(idx, tool_name, args)
if tool_name == "join":
tool = "join"
else:
try:
tool = tools[[tool.name for tool in tools].index(tool_name)]
except ValueError as e:
raise OutputParserException(f"Tool {tool_name} not found.")
return dict(
tool=tool,
args=args,
dependencies=dependencies,
)In [2]:
import asyncio
import json
import re
from typing import Any, Optional, Sequence, Union
from uuid import UUID
from langchain.callbacks.base import AsyncCallbackHandler, Callbacks
from langchain.chat_models.base import BaseChatModel
from langchain.schema import LLMResult
from langchain.schema.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel
from langchain_core.runnables import RunnableBranch
from langchain_core.tools import BaseTool
END_OF_PLAN = "<END_OF_PLAN>"
JOINER_FINISH = "Finish"
JOINER_REPLAN = "Replan"
JOIN_DESCRIPTION = (
"join():\n"
" - Collects and combines results from prior actions.\n"
" - A LLM agent is called upon invoking join to either finalize the user query or wait until the plans are executed.\n"
" - join should always be the last action in the plan, and will be called in two scenarios:\n"
" (a) if the answer can be determined by gathering the outputs from tasks to generate the final response.\n"
" (b) if the answer cannot be determined in the planning phase before you execute the plans. "
)
planner_prompt_tmpl_str = (
"Given a user query, create a plan to solve it with the utmost parallelizability. "
"Each plan should comprise an action from the following {num_tools} types:\n"
"{tool_descriptions}"
f"\n{{num_toolsp1}}. {JOIN_DESCRIPTION}"
"Guidelines:\n"
" - Each action described above contains input/output types and description.\n"
" - You must strictly adhere to the input and output types for each action.\n"
" - The action descriptions contain the guidelines. You MUST strictly follow those guidelines when you use the actions.\n"
" - Each action in the plan should strictly be one of the above types. Follow the Python conventions for each action.\n"
" - Pass arguments by keyword ONLY\n"
" - Each action MUST have a unique ID, which is strictly increasing.\n"
" - Inputs for actions can either be constants or outputs from preceding actions. "
"In the latter case, use the format $id to denote the ID of the previous action whose output will be the input.\n"
f" - Always call join as the last action in the plan. Say '{END_OF_PLAN}' after you call join\n"
" - Ensure the plan maximizes parallelizability.\n"
" - Only use the provided action types. If a query cannot be addressed using these, invoke the join action for the next steps.\n"
" - Never explain the plan with comments (e.g. #).\n"
" - Never introduce new actions other than the ones provided.\n\n"
"{replan}"
"{examples}"
)
def _generate_planner_prompt(
tools: Sequence[BaseTool],
example_prompt=str,
):
tool_descriptions = "\n".join(
f"{i+1}. {tool.description}" for i, tool in enumerate(tools)
)
planner_prompt_template = ChatPromptTemplate.from_messages(
[("system", planner_prompt_tmpl_str), ("user", "Question: {input}{context}")]
).partial(
tool_descriptions=tool_descriptions,
examples="Here are some examples:\n\n" + example_prompt
if example_prompt
else "",
num_tools=len(tools),
num_toolsp1=len(tools) + 1,
)
return planner_prompt_template
def create_planner(
llm: BaseChatModel,
example_prompt: str,
example_prompt_replan: str,
tools: Sequence[Union[BaseTool]],
stop: Optional[list[str]] = None,
):
og_planner_prompt = _generate_planner_prompt(tools, example_prompt).partial(
replan="",
context="",
)
replanner_prompt = _generate_planner_prompt(tools, example_prompt).partial(
replan=' - You are given "Previous Plan" which is the plan that the previous agent created along with the execution results '
"(given as Observation) of each plan and a general thought (given as Thought) about the executed results."
'You MUST use these information to create the next plan under "Current Plan".\n'
' - When starting the Current Plan, you should start with "Thought" that outlines the strategy for the next plan.\n'
" - In the Current Plan, you should NEVER repeat the actions that are already executed in the Previous Plan.\n"
)
bound_llm = llm.bind(stop=stop)
return (
RunnableBranch(
((lambda x: x.get("replan")), replanner_prompt),
og_planner_prompt,
)
| bound_llm
| LLMCompilerPlanParser(tools=tools)
)In [3]:
from langchain.tools import tool
from langchain_openai import ChatOpenAI
@tool
def get_user_id(first_name: str, last_name: Optional[str] = None):
"""Query the user IDs of everyone with the provided name."""
return 4
@tool
def get_scores(class_name: str, user_id: int):
"""Query the class registry for grades of the provided user ID."""
return "A+"
examples = (
"Question: What's the user ID for Johnny Drop Tables?\n"
'1. {"get_user_id": {"first_name": "Johnny", "last_name":"Drop Tables"}}\n'
f'2. {{"join": null}}{END_OF_PLAN}\n'
"###\n"
"\n"
"Question: What was Eric Zhang's score in Calc?\n"
'1. {"get_user_id": {"first_name": "Eric", "last_name":"Zhang"}}\n'
'2. {"get_scores": {"class_name": "calc", "user_id": "$1"}}\n'
f'3. {{"join": null}}{END_OF_PLAN}\n'
"###\n"
"\n"
)
planner = create_planner(
ChatOpenAI(model="gpt-3.5-turbo"),
example_prompt=examples,
example_prompt_replan="",
tools=[get_user_id, get_scores],
)In [4]:
tasks = planner.invoke(
{"input": "What are the Calc BC grades for Sam and Will Van Damm?"}
)
tasksOut [4]:
{1: {'tool': StructuredTool(name='get_user_id', description='get_user_id(first_name: str, last_name: Optional[str] = None) - Query the user IDs of everyone with the provided name.', args_schema=<class 'pydantic.main.get_user_idSchemaSchema'>, func=<function get_user_id at 0x1242af240>),
'args': {'first_name': 'Sam', 'last_name': 'Van Damm'},
'dependencies': []},
2: {'tool': StructuredTool(name='get_user_id', description='get_user_id(first_name: str, last_name: Optional[str] = None) - Query the user IDs of everyone with the provided name.', args_schema=<class 'pydantic.main.get_user_idSchemaSchema'>, func=<function get_user_id at 0x1242af240>),
'args': {'first_name': 'Will', 'last_name': 'Van Damm'},
'dependencies': []},
3: {'tool': StructuredTool(name='get_scores', description='get_scores(class_name: str, user_id: int) - Query the class registry for grades of the provided user ID.', args_schema=<class 'pydantic.main.get_scoresSchemaSchema'>, func=<function get_scores at 0x1242af2e0>),
'args': {'class_name': 'Calc BC', 'user_id': '$1'},
'dependencies': [1]},
4: {'tool': StructuredTool(name='get_scores', description='get_scores(class_name: str, user_id: int) - Query the class registry for grades of the provided user ID.', args_schema=<class 'pydantic.main.get_scoresSchemaSchema'>, func=<function get_scores at 0x1242af2e0>),
'args': {'class_name': 'Calc BC', 'user_id': '$2'},
'dependencies': [2]},
5: {'tool': 'join', 'args': None, 'dependencies': [1, 2, 3, 4]}}In [5]:
import logging
from langchain_core.agents import AgentFinish
from langchain_core.runnables import (
RunnableLambda,
RunnableParallel,
RunnablePassthrough,
)
from langgraph.graph import END, Graph
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()
def _sort_tasks(data):
sorted_tasks = []
while data:
no_deps = {k: v for k, v in data.items() if not v["dependencies"]}
if not no_deps:
raise ValueError("We seem to have run into a circular dependency.")
sorted_tasks.append(no_deps)
data = {
k: {
**v,
"dependencies": [d for d in v["dependencies"] if d not in no_deps],
}
for k, v in data.items()
if k not in no_deps
}
return sorted_tasks
def _resolve_arg(x: dict, arg):
return x[f"task_{arg[1:]}"] if isinstance(arg, str) and arg.startswith("$") else arg
def _execute_task(task, x, config):
tool_to_use = task["tool"]
args = task["args"]
if isinstance(args, str):
resolved_args = _resolve_arg(x, args)
elif isinstance(args, dict):
resolved_args = {key: _resolve_arg(x, val) for key, val in args.items()}
else:
logger.warning(f"Unsupported arg type: {args}")
return tool_to_use.invoke(resolved_args, config)
def construct_dag(tasks):
sorted_tasks = _sort_tasks(tasks)
chain = None
for idx, task_group in enumerate(sorted_tasks):
if len(task_group) == 1 and next(iter(task_group.values()))["tool"] == "join":
# TODO: actually join the values
step = lambda x: {"join": x}
else:
# Cascade all results forward
constructor = (
RunnableParallel if chain is None else RunnablePassthrough.assign
)
step = constructor(
**{
f"task_{idx}": RunnableLambda(
lambda x, config: _execute_task(task, x, config)
).with_config(run_name=f"task_{idx}")
for idx, task in task_group.items()
}
).with_config(run_name=f"TaskGroup{idx}")
if chain is None:
chain = step
else:
chain |= step
if chain is not None:
return chain | RunnablePassthrough.assign(tasks=lambda _: tasks)
return chainIn [6]:
graph = construct_dag(tasks)
graph.get_graph().print_ascii() +------------------------------+
| Parallel<task_1,task_2>Input |
+------------------------------+
***** *****
**** ****
*** ***
+---------------------------------------+ +---------------------------------------+
| Lambda(lambda x, config: _execute_... | | Lambda(lambda x, config: _execute_... |
+---------------------------------------+ +---------------------------------------+
***** *****
**** ****
*** ***
+-------------------------------+
| Parallel<task_1,task_2>Output |
+-------------------------------+
*
*
*
+------------------------------+
| Parallel<task_3,task_4>Input |
****+------------------------------+*****
******** * *********
******** * ********
***** * *****
+---------------------------------------+ +---------------------------------------+ +-------------+
| Lambda(lambda x, config: _execute_... | | Lambda(lambda x, config: _execute_... | ****| Passthrough |
+---------------------------------------+* +---------------------------------------+ ******** +-------------+
******** * *********
******** * ********
***** * *****
+-------------------------------+
| Parallel<task_3,task_4>Output |
+-------------------------------+
*
*
*
+-------------------------------+
| Lambda(lambda x: {'join': x}) |
+-------------------------------+
*
*
*
+----------------------+
| Parallel<tasks>Input |
+----------------------+
*** ***
*** ***
** **
+-------------------------+ +-------------+
| Lambda(lambda _: tasks) | | Passthrough |
+-------------------------+ +-------------+
*** ***
*** ***
** **
+-----------------------+
| Parallel<tasks>Output |
+-----------------------+
In [7]:
chain = planner | construct_dagIn [15]:
example_question = "Did Aliya get a better score than Roger in Geology?"
task_results = chain.invoke({"input": example_question})
task_results["join"]INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
[0;31m---------------------------------------------------------------------------[0m [0;31mKeyError[0m Traceback (most recent call last) Cell [0;32mIn[15], line 2[0m [1;32m 1[0m example_question [38;5;241m=[39m [38;5;124m"[39m[38;5;124mDid Aliya get a better score than Roger in Geology?[39m[38;5;124m"[39m [0;32m----> 2[0m task_results [38;5;241m=[39m [43mchain[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m[43m{[49m[38;5;124;43m"[39;49m[38;5;124;43minput[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mexample_question[49m[43m}[49m[43m)[49m [1;32m 3[0m task_results[[38;5;124m"[39m[38;5;124mjoin[39m[38;5;124m"[39m] File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:456[0m, in [0;36mPregel.invoke[0;34m(self, input, config, output, **kwargs)[0m [1;32m 447[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 448[0m [38;5;28mself[39m, [1;32m 449[0m [38;5;28minput[39m: Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any], [0;32m (...)[0m [1;32m 453[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Any, [1;32m 454[0m ) [38;5;241m-[39m[38;5;241m>[39m Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]: [1;32m 455[0m latest: Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any] [38;5;241m=[39m [38;5;28;01mNone[39;00m [0;32m--> 456[0m [43m [49m[38;5;28;43;01mfor[39;49;00m[43m [49m[43mchunk[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mstream[49m[43m([49m [1;32m 457[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 458[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 459[0m [43m [49m[43moutput[49m[38;5;241;43m=[39;49m[43moutput[49m[43m [49m[38;5;28;43;01mif[39;49;00m[43m [49m[43moutput[49m[43m [49m[38;5;129;43;01mis[39;49;00m[43m [49m[38;5;129;43;01mnot[39;49;00m[43m [49m[38;5;28;43;01mNone[39;49;00m[43m [49m[38;5;28;43;01melse[39;49;00m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43moutput[49m[43m,[49m [1;32m 460[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 461[0m [43m [49m[43m)[49m[43m:[49m [1;32m 462[0m [43m [49m[43mlatest[49m[43m [49m[38;5;241;43m=[39;49m[43m [49m[43mchunk[49m [1;32m 463[0m [38;5;28;01mreturn[39;00m latest File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:483[0m, in [0;36mPregel.transform[0;34m(self, input, config, output, **kwargs)[0m [1;32m 475[0m [38;5;28;01mdef[39;00m [38;5;21mtransform[39m( [1;32m 476[0m [38;5;28mself[39m, [1;32m 477[0m [38;5;28minput[39m: Iterator[Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]], [0;32m (...)[0m [1;32m 481[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Any, [1;32m 482[0m ) [38;5;241m-[39m[38;5;241m>[39m Iterator[Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]]: [0;32m--> 483[0m [43m [49m[38;5;28;43;01mfor[39;49;00m[43m [49m[43mchunk[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_transform_stream_with_config[49m[43m([49m [1;32m 484[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_transform[49m[43m,[49m[43m [49m[43mconfig[49m[43m,[49m[43m [49m[43moutput[49m[38;5;241;43m=[39;49m[43moutput[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m [1;32m 485[0m [43m [49m[43m)[49m[43m:[49m [1;32m 486[0m [43m [49m[38;5;28;43;01myield[39;49;00m[43m [49m[43mchunk[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:1226[0m, in [0;36mRunnable._transform_stream_with_config[0;34m(self, input, transformer, config, run_type, **kwargs)[0m [1;32m 1224[0m [38;5;28;01mtry[39;00m: [1;32m 1225[0m [38;5;28;01mwhile[39;00m [38;5;28;01mTrue[39;00m: [0;32m-> 1226[0m chunk: Output [38;5;241m=[39m [43mcontext[49m[38;5;241;43m.[39;49m[43mrun[49m[43m([49m[38;5;28;43mnext[39;49m[43m,[49m[43m [49m[43miterator[49m[43m)[49m [38;5;66;03m# type: ignore[39;00m [1;32m 1227[0m [38;5;28;01myield[39;00m chunk [1;32m 1228[0m [38;5;28;01mif[39;00m final_output_supported: File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:301[0m, in [0;36mPregel._transform[0;34m(self, input, run_manager, config, output)[0m [1;32m 291[0m done, inflight [38;5;241m=[39m concurrent[38;5;241m.[39mfutures[38;5;241m.[39mwait( [1;32m 292[0m [ [1;32m 293[0m executor[38;5;241m.[39msubmit(proc[38;5;241m.[39minvoke, [38;5;28minput[39m, config) [0;32m (...)[0m [1;32m 297[0m timeout[38;5;241m=[39m[38;5;28mself[39m[38;5;241m.[39mstep_timeout, [1;32m 298[0m ) [1;32m 300[0m [38;5;66;03m# interrupt on failure or timeout[39;00m [0;32m--> 301[0m [43m_interrupt_or_proceed[49m[43m([49m[43mdone[49m[43m,[49m[43m [49m[43minflight[49m[43m,[49m[43m [49m[43mstep[49m[43m)[49m [1;32m 303[0m [38;5;66;03m# apply writes to channels[39;00m [1;32m 304[0m _apply_writes(checkpoint, channels, pending_writes, config, step [38;5;241m+[39m [38;5;241m1[39m) File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:548[0m, in [0;36m_interrupt_or_proceed[0;34m(done, inflight, step)[0m [1;32m 546[0m inflight[38;5;241m.[39mpop()[38;5;241m.[39mcancel() [1;32m 547[0m [38;5;66;03m# raise the exception[39;00m [0;32m--> 548[0m [38;5;28;01mraise[39;00m exc [1;32m 549[0m [38;5;66;03m# TODO this is where retry of an entire step would happen[39;00m [1;32m 551[0m [38;5;28;01mif[39;00m inflight: [1;32m 552[0m [38;5;66;03m# if we got here means we timed out[39;00m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58[0m, in [0;36m_WorkItem.run[0;34m(self)[0m [1;32m 55[0m [38;5;28;01mreturn[39;00m [1;32m 57[0m [38;5;28;01mtry[39;00m: [0;32m---> 58[0m result [38;5;241m=[39m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mfn[49m[43m([49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43margs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mkwargs[49m[43m)[49m [1;32m 59[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m exc: [1;32m 60[0m [38;5;28mself[39m[38;5;241m.[39mfuture[38;5;241m.[39mset_exception(exc) File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:3596[0m, in [0;36mRunnableBindingBase.invoke[0;34m(self, input, config, **kwargs)[0m [1;32m 3590[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 3591[0m [38;5;28mself[39m, [1;32m 3592[0m [38;5;28minput[39m: Input, [1;32m 3593[0m config: Optional[RunnableConfig] [38;5;241m=[39m [38;5;28;01mNone[39;00m, [1;32m 3594[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Optional[Any], [1;32m 3595[0m ) [38;5;241m-[39m[38;5;241m>[39m Output: [0;32m-> 3596[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mbound[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 3597[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 3598[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_merge_configs[49m[43m([49m[43mconfig[49m[43m)[49m[43m,[49m [1;32m 3599[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43m{[49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mkwargs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m}[49m[43m,[49m [1;32m 3600[0m [43m [49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:1774[0m, in [0;36mRunnableSequence.invoke[0;34m(self, input, config)[0m [1;32m 1772[0m [38;5;28;01mtry[39;00m: [1;32m 1773[0m [38;5;28;01mfor[39;00m i, step [38;5;129;01min[39;00m [38;5;28menumerate[39m([38;5;28mself[39m[38;5;241m.[39msteps): [0;32m-> 1774[0m [38;5;28minput[39m [38;5;241m=[39m [43mstep[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 1775[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 1776[0m [43m [49m[38;5;66;43;03m# mark each step as a child run[39;49;00m [1;32m 1777[0m [43m [49m[43mpatch_config[49m[43m([49m [1;32m 1778[0m [43m [49m[43mconfig[49m[43m,[49m[43m [49m[43mcallbacks[49m[38;5;241;43m=[39;49m[43mrun_manager[49m[38;5;241;43m.[39;49m[43mget_child[49m[43m([49m[38;5;124;43mf[39;49m[38;5;124;43m"[39;49m[38;5;124;43mseq:step:[39;49m[38;5;132;43;01m{[39;49;00m[43mi[49m[38;5;241;43m+[39;49m[38;5;241;43m1[39;49m[38;5;132;43;01m}[39;49;00m[38;5;124;43m"[39;49m[43m)[49m [1;32m 1779[0m [43m [49m[43m)[49m[43m,[49m [1;32m 1780[0m [43m [49m[43m)[49m [1;32m 1781[0m [38;5;66;03m# finish the root run[39;00m [1;32m 1782[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/prompts/base.py:93[0m, in [0;36mBasePromptTemplate.invoke[0;34m(self, input, config)[0m [1;32m 90[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 91[0m [38;5;28mself[39m, [38;5;28minput[39m: Dict, config: Optional[RunnableConfig] [38;5;241m=[39m [38;5;28;01mNone[39;00m [1;32m 92[0m ) [38;5;241m-[39m[38;5;241m>[39m PromptValue: [0;32m---> 93[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_call_with_config[49m[43m([49m [1;32m 94[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_format_prompt_with_error_handling[49m[43m,[49m [1;32m 95[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 96[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 97[0m [43m [49m[43mrun_type[49m[38;5;241;43m=[39;49m[38;5;124;43m"[39;49m[38;5;124;43mprompt[39;49m[38;5;124;43m"[39;49m[43m,[49m [1;32m 98[0m [43m [49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:975[0m, in [0;36mRunnable._call_with_config[0;34m(self, func, input, config, run_type, **kwargs)[0m [1;32m 971[0m context [38;5;241m=[39m copy_context() [1;32m 972[0m context[38;5;241m.[39mrun(var_child_runnable_config[38;5;241m.[39mset, child_config) [1;32m 973[0m output [38;5;241m=[39m cast( [1;32m 974[0m Output, [0;32m--> 975[0m [43mcontext[49m[38;5;241;43m.[39;49m[43mrun[49m[43m([49m [1;32m 976[0m [43m [49m[43mcall_func_with_variable_args[49m[43m,[49m [1;32m 977[0m [43m [49m[43mfunc[49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 978[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 979[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 980[0m [43m [49m[43mrun_manager[49m[43m,[49m [1;32m 981[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 982[0m [43m [49m[43m)[49m, [1;32m 983[0m ) [1;32m 984[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: [1;32m 985[0m run_manager[38;5;241m.[39mon_chain_error(e) File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/config.py:323[0m, in [0;36mcall_func_with_variable_args[0;34m(func, input, config, run_manager, **kwargs)[0m [1;32m 321[0m [38;5;28;01mif[39;00m run_manager [38;5;129;01mis[39;00m [38;5;129;01mnot[39;00m [38;5;28;01mNone[39;00m [38;5;129;01mand[39;00m accepts_run_manager(func): [1;32m 322[0m kwargs[[38;5;124m"[39m[38;5;124mrun_manager[39m[38;5;124m"[39m] [38;5;241m=[39m run_manager [0;32m--> 323[0m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/prompts/base.py:83[0m, in [0;36mBasePromptTemplate._format_prompt_with_error_handling[0;34m(self, inner_input)[0m [1;32m 81[0m missing [38;5;241m=[39m [38;5;28mset[39m([38;5;28mself[39m[38;5;241m.[39minput_variables)[38;5;241m.[39mdifference(inner_input) [1;32m 82[0m [38;5;28;01mif[39;00m missing: [0;32m---> 83[0m [38;5;28;01mraise[39;00m [38;5;167;01mKeyError[39;00m( [1;32m 84[0m [38;5;124mf[39m[38;5;124m"[39m[38;5;124mInput to [39m[38;5;132;01m{[39;00m[38;5;28mself[39m[38;5;241m.[39m[38;5;18m__class__[39m[38;5;241m.[39m[38;5;18m__name__[39m[38;5;132;01m}[39;00m[38;5;124m is missing variables [39m[38;5;132;01m{[39;00mmissing[38;5;132;01m}[39;00m[38;5;124m. [39m[38;5;124m"[39m [1;32m 85[0m [38;5;124mf[39m[38;5;124m"[39m[38;5;124m Expected: [39m[38;5;132;01m{[39;00m[38;5;28mself[39m[38;5;241m.[39minput_variables[38;5;132;01m}[39;00m[38;5;124m"[39m [1;32m 86[0m [38;5;124mf[39m[38;5;124m"[39m[38;5;124m Received: [39m[38;5;132;01m{[39;00m[38;5;28mlist[39m(inner_input[38;5;241m.[39mkeys())[38;5;132;01m}[39;00m[38;5;124m"[39m [1;32m 87[0m ) [1;32m 88[0m [38;5;28;01mreturn[39;00m [38;5;28mself[39m[38;5;241m.[39mformat_prompt([38;5;241m*[39m[38;5;241m*[39minner_input) [0;31mKeyError[0m: "Input to ChatPromptTemplate is missing variables {'input'}. Expected: ['input', 'scratchpad'] Received: ['join', 'tasks', 'scratchpad']"
In [9]:
from langchain_core.output_parsers import StrOutputParser
from typing_extensions import TypedDict
def format_task(task, idx):
thought = "" # TODO: Pass through CoT
tool = task["tool"]
tool_name = tool if isinstance(tool, str) else tool.name # Handle join()
return f"{thought}{idx}. {{{tool_name}: {task['args']}}}"
def format_tasks(executor_output: dict):
tasks = executor_output["tasks"]
formatted_plan = "\n".join(format_task(task, idx) for idx, task in tasks.items())
observations = "\n".join(f"{k}: {v}" for k, v in executor_output["join"].items())
return f"Original Plan:\n{formatted_plan}\nExecuted plan results:\n{observations}"
def _parse_joiner_output(raw_answer: str) -> str:
thought, answer, is_replan = "", "", False # default values
raw_answers = raw_answer.split("\n")
for ans in raw_answers:
if ans.startswith("Action:"):
answer = ans[ans.find("(") + 1 : ans.find(")")]
is_replan = JOINER_REPLAN in ans
elif ans.startswith("Thought:"):
thought = ans.split("Thought:")[1].strip()
if is_replan:
return {"thought": thought, "context": answer}
else:
return {"thought": thought, "answer": answer}In [17]:
def create_joiner(prompt, llm):
return (
(lambda x: {**x["plan"], "input": x["input"]})
| RunnablePassthrough.assign(scratchpad=format_tasks)
| ChatPromptTemplate.from_messages([("system", prompt), ("user", "{input}")])
| llm
| StrOutputParser()
| _parse_joiner_output
)In [18]:
system_prompt = (
"Solve a question answering task with interleaving Observation, Thought, and Action steps. Here are some guidelines:\n"
" - In the Assistant Scratchpad, you will be given results of a plan you have executed to answer the user's question.\n"
" - Thought needs to reason about the question based on the Observations in 1-2 sentences.\n"
" - There are cases where the Observations are unclear or irrelevant (in the case the task execution was unsuccessful or subpar). Only heed the relevant ones\n"
" Respond in the following format:\n\n"
"Thought: <reason about the task results and whether you have sufficient information to answer the question>\n"
"Action: <action to take>\n"
"Available actions:\n"
f" (1) {JOINER_FINISH}(the final answer to return to the user): returns the answer and finishes the task.\n"
f" (2) {JOINER_REPLAN}(the reasoning and information to provide to make a better next plan): instructs why we must replan\n\n"
" Examples:\n"
"Question: How many users are currently using the new product?\n"
"...task returns the number 32,000\n"
"Thought: I find no issue with the original plan, and the results satisfy everything in the user question.\n"
f"Action: {JOINER_FINISH}(32,000 users currently use the new product)\n###\n"
"Question: Are the gophers beating the rabbits??\n"
"...task returns the a score of 7 for rabbits but no other value...\n"
"Thought: I need the gophers' score to make a final decision.\n"
f"Action: {JOINER_REPLAN}(The rabbits have a score of 7, but I need the gophers' score.)"
"Assistant Scratchpad:\n{scratchpad}"
)In [19]:
joiner = create_joiner(system_prompt, ChatOpenAI(model="gpt-3.5-turbo"))
joiner.invoke({"plan": task_results, "input": example_question})Out [19]:
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
{'thought': 'Based on the executed plan, both Aliya and Roger received an A+ score in Geology. Therefore, Aliya did not get a better score than Roger in Geology.',
'answer': 'No, Aliya did not get a better score than Roger in Geology. They both received an A+.'}In [25]:
from langgraph.graph import END, Graph
workflow = Graph()
# 1. Define vertices
planner = create_planner(
llm=ChatOpenAI(model="gpt-3.5-turbo"),
example_prompt=examples,
# TODO: You can update and optimize the replanner prompt to
# better critique the original plan
example_prompt_replan=examples,
tools=[get_user_id, get_scores],
)
plan_and_execute = RunnablePassthrough.assign(plan=planner | construct_dag)
joiner = create_joiner(system_prompt, ChatOpenAI(model="gpt-3.5-turbo"))
joiner_node = RunnablePassthrough.assign(joined_output=joiner)
workflow.add_node("plan_and_execute", plan_and_execute)
workflow.add_node("join", joiner_node)
workflow.add_node("end", lambda x: x["joined_output"].get("answer", x))
## Define edges
workflow.add_edge("plan_and_execute", "join")
### This condition determines looping logic
def should_continue(joiner_output):
if joiner_output["joined_output"].get("context"):
return "continue"
return "end"
workflow.add_conditional_edges(
start_key="join",
# Next, we pass in the function that will determine which node is called next.
condition=should_continue,
conditional_edge_mapping={
# If it generates context, we must replan
"continue": "plan_and_execute",
# Otherwise we finish.
"end": "end",
},
)
workflow.set_entry_point("plan_and_execute")
workflow.set_finish_point("end")
chain = workflow.compile()In [28]:
# chain.invoke({"input": "Did Aliya get a better score than Roger in Geology?"})In [29]:
# Try something to trigger replanning
chain.invoke(
{
"input": "Did the sum of Aliya and Roger's grades in Calc map out to less than"
" the total grade for the class?"
}
)INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
[0;31m---------------------------------------------------------------------------[0m [0;31mKeyError[0m Traceback (most recent call last) Cell [0;32mIn[29], line 2[0m [1;32m 1[0m [38;5;66;03m# Try something to trigger replanning[39;00m [0;32m----> 2[0m [43mchain[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 3[0m [43m [49m[43m{[49m [1;32m 4[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43minput[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mDid the sum of Aliya and Roger[39;49m[38;5;124;43m'[39;49m[38;5;124;43ms grades in Calc map out to less than[39;49m[38;5;124;43m"[39;49m [1;32m 5[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43m the total grade for the class?[39;49m[38;5;124;43m"[39;49m [1;32m 6[0m [43m [49m[43m}[49m [1;32m 7[0m [43m)[49m File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:456[0m, in [0;36mPregel.invoke[0;34m(self, input, config, output, **kwargs)[0m [1;32m 447[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 448[0m [38;5;28mself[39m, [1;32m 449[0m [38;5;28minput[39m: Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any], [0;32m (...)[0m [1;32m 453[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Any, [1;32m 454[0m ) [38;5;241m-[39m[38;5;241m>[39m Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]: [1;32m 455[0m latest: Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any] [38;5;241m=[39m [38;5;28;01mNone[39;00m [0;32m--> 456[0m [43m [49m[38;5;28;43;01mfor[39;49;00m[43m [49m[43mchunk[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mstream[49m[43m([49m [1;32m 457[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 458[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 459[0m [43m [49m[43moutput[49m[38;5;241;43m=[39;49m[43moutput[49m[43m [49m[38;5;28;43;01mif[39;49;00m[43m [49m[43moutput[49m[43m [49m[38;5;129;43;01mis[39;49;00m[43m [49m[38;5;129;43;01mnot[39;49;00m[43m [49m[38;5;28;43;01mNone[39;49;00m[43m [49m[38;5;28;43;01melse[39;49;00m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43moutput[49m[43m,[49m [1;32m 460[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 461[0m [43m [49m[43m)[49m[43m:[49m [1;32m 462[0m [43m [49m[43mlatest[49m[43m [49m[38;5;241;43m=[39;49m[43m [49m[43mchunk[49m [1;32m 463[0m [38;5;28;01mreturn[39;00m latest File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:483[0m, in [0;36mPregel.transform[0;34m(self, input, config, output, **kwargs)[0m [1;32m 475[0m [38;5;28;01mdef[39;00m [38;5;21mtransform[39m( [1;32m 476[0m [38;5;28mself[39m, [1;32m 477[0m [38;5;28minput[39m: Iterator[Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]], [0;32m (...)[0m [1;32m 481[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Any, [1;32m 482[0m ) [38;5;241m-[39m[38;5;241m>[39m Iterator[Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]]: [0;32m--> 483[0m [43m [49m[38;5;28;43;01mfor[39;49;00m[43m [49m[43mchunk[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_transform_stream_with_config[49m[43m([49m [1;32m 484[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_transform[49m[43m,[49m[43m [49m[43mconfig[49m[43m,[49m[43m [49m[43moutput[49m[38;5;241;43m=[39;49m[43moutput[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m [1;32m 485[0m [43m [49m[43m)[49m[43m:[49m [1;32m 486[0m [43m [49m[38;5;28;43;01myield[39;49;00m[43m [49m[43mchunk[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:1226[0m, in [0;36mRunnable._transform_stream_with_config[0;34m(self, input, transformer, config, run_type, **kwargs)[0m [1;32m 1224[0m [38;5;28;01mtry[39;00m: [1;32m 1225[0m [38;5;28;01mwhile[39;00m [38;5;28;01mTrue[39;00m: [0;32m-> 1226[0m chunk: Output [38;5;241m=[39m context[38;5;241m.[39mrun([38;5;28mnext[39m, iterator) [38;5;66;03m# type: ignore[39;00m [1;32m 1227[0m [38;5;28;01myield[39;00m chunk [1;32m 1228[0m [38;5;28;01mif[39;00m final_output_supported: File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:301[0m, in [0;36mPregel._transform[0;34m(self, input, run_manager, config, output)[0m [1;32m 291[0m done, inflight [38;5;241m=[39m concurrent[38;5;241m.[39mfutures[38;5;241m.[39mwait( [1;32m 292[0m [ [1;32m 293[0m executor[38;5;241m.[39msubmit(proc[38;5;241m.[39minvoke, [38;5;28minput[39m, config) [0;32m (...)[0m [1;32m 297[0m timeout[38;5;241m=[39m[38;5;28mself[39m[38;5;241m.[39mstep_timeout, [1;32m 298[0m ) [1;32m 300[0m [38;5;66;03m# interrupt on failure or timeout[39;00m [0;32m--> 301[0m [43m_interrupt_or_proceed[49m[43m([49m[43mdone[49m[43m,[49m[43m [49m[43minflight[49m[43m,[49m[43m [49m[43mstep[49m[43m)[49m [1;32m 303[0m [38;5;66;03m# apply writes to channels[39;00m [1;32m 304[0m _apply_writes(checkpoint, channels, pending_writes, config, step [38;5;241m+[39m [38;5;241m1[39m) File [0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:548[0m, in [0;36m_interrupt_or_proceed[0;34m(done, inflight, step)[0m [1;32m 546[0m inflight[38;5;241m.[39mpop()[38;5;241m.[39mcancel() [1;32m 547[0m [38;5;66;03m# raise the exception[39;00m [0;32m--> 548[0m [38;5;28;01mraise[39;00m exc [1;32m 549[0m [38;5;66;03m# TODO this is where retry of an entire step would happen[39;00m [1;32m 551[0m [38;5;28;01mif[39;00m inflight: [1;32m 552[0m [38;5;66;03m# if we got here means we timed out[39;00m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58[0m, in [0;36m_WorkItem.run[0;34m(self)[0m [1;32m 55[0m [38;5;28;01mreturn[39;00m [1;32m 57[0m [38;5;28;01mtry[39;00m: [0;32m---> 58[0m result [38;5;241m=[39m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mfn[49m[43m([49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43margs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mkwargs[49m[43m)[49m [1;32m 59[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m exc: [1;32m 60[0m [38;5;28mself[39m[38;5;241m.[39mfuture[38;5;241m.[39mset_exception(exc) File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:3596[0m, in [0;36mRunnableBindingBase.invoke[0;34m(self, input, config, **kwargs)[0m [1;32m 3590[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 3591[0m [38;5;28mself[39m, [1;32m 3592[0m [38;5;28minput[39m: Input, [1;32m 3593[0m config: Optional[RunnableConfig] [38;5;241m=[39m [38;5;28;01mNone[39;00m, [1;32m 3594[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Optional[Any], [1;32m 3595[0m ) [38;5;241m-[39m[38;5;241m>[39m Output: [0;32m-> 3596[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mbound[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 3597[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 3598[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_merge_configs[49m[43m([49m[43mconfig[49m[43m)[49m[43m,[49m [1;32m 3599[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43m{[49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mkwargs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m}[49m[43m,[49m [1;32m 3600[0m [43m [49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:1774[0m, in [0;36mRunnableSequence.invoke[0;34m(self, input, config)[0m [1;32m 1772[0m [38;5;28;01mtry[39;00m: [1;32m 1773[0m [38;5;28;01mfor[39;00m i, step [38;5;129;01min[39;00m [38;5;28menumerate[39m([38;5;28mself[39m[38;5;241m.[39msteps): [0;32m-> 1774[0m [38;5;28minput[39m [38;5;241m=[39m [43mstep[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 1775[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 1776[0m [43m [49m[38;5;66;43;03m# mark each step as a child run[39;49;00m [1;32m 1777[0m [43m [49m[43mpatch_config[49m[43m([49m [1;32m 1778[0m [43m [49m[43mconfig[49m[43m,[49m[43m [49m[43mcallbacks[49m[38;5;241;43m=[39;49m[43mrun_manager[49m[38;5;241;43m.[39;49m[43mget_child[49m[43m([49m[38;5;124;43mf[39;49m[38;5;124;43m"[39;49m[38;5;124;43mseq:step:[39;49m[38;5;132;43;01m{[39;49;00m[43mi[49m[38;5;241;43m+[39;49m[38;5;241;43m1[39;49m[38;5;132;43;01m}[39;49;00m[38;5;124;43m"[39;49m[43m)[49m [1;32m 1779[0m [43m [49m[43m)[49m[43m,[49m [1;32m 1780[0m [43m [49m[43m)[49m [1;32m 1781[0m [38;5;66;03m# finish the root run[39;00m [1;32m 1782[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/passthrough.py:415[0m, in [0;36mRunnableAssign.invoke[0;34m(self, input, config, **kwargs)[0m [1;32m 409[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 410[0m [38;5;28mself[39m, [1;32m 411[0m [38;5;28minput[39m: Dict[[38;5;28mstr[39m, Any], [1;32m 412[0m config: Optional[RunnableConfig] [38;5;241m=[39m [38;5;28;01mNone[39;00m, [1;32m 413[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Any, [1;32m 414[0m ) [38;5;241m-[39m[38;5;241m>[39m Dict[[38;5;28mstr[39m, Any]: [0;32m--> 415[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_call_with_config[49m[43m([49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_invoke[49m[43m,[49m[43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[43mconfig[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:975[0m, in [0;36mRunnable._call_with_config[0;34m(self, func, input, config, run_type, **kwargs)[0m [1;32m 971[0m context [38;5;241m=[39m copy_context() [1;32m 972[0m context[38;5;241m.[39mrun(var_child_runnable_config[38;5;241m.[39mset, child_config) [1;32m 973[0m output [38;5;241m=[39m cast( [1;32m 974[0m Output, [0;32m--> 975[0m [43mcontext[49m[38;5;241;43m.[39;49m[43mrun[49m[43m([49m [1;32m 976[0m [43m [49m[43mcall_func_with_variable_args[49m[43m,[49m [1;32m 977[0m [43m [49m[43mfunc[49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 978[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 979[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 980[0m [43m [49m[43mrun_manager[49m[43m,[49m [1;32m 981[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 982[0m [43m [49m[43m)[49m, [1;32m 983[0m ) [1;32m 984[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: [1;32m 985[0m run_manager[38;5;241m.[39mon_chain_error(e) File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/config.py:323[0m, in [0;36mcall_func_with_variable_args[0;34m(func, input, config, run_manager, **kwargs)[0m [1;32m 321[0m [38;5;28;01mif[39;00m run_manager [38;5;129;01mis[39;00m [38;5;129;01mnot[39;00m [38;5;28;01mNone[39;00m [38;5;129;01mand[39;00m accepts_run_manager(func): [1;32m 322[0m kwargs[[38;5;124m"[39m[38;5;124mrun_manager[39m[38;5;124m"[39m] [38;5;241m=[39m run_manager [0;32m--> 323[0m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/passthrough.py:402[0m, in [0;36mRunnableAssign._invoke[0;34m(self, input, run_manager, config, **kwargs)[0m [1;32m 389[0m [38;5;28;01mdef[39;00m [38;5;21m_invoke[39m( [1;32m 390[0m [38;5;28mself[39m, [1;32m 391[0m [38;5;28minput[39m: Dict[[38;5;28mstr[39m, Any], [0;32m (...)[0m [1;32m 394[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Any, [1;32m 395[0m ) [38;5;241m-[39m[38;5;241m>[39m Dict[[38;5;28mstr[39m, Any]: [1;32m 396[0m [38;5;28;01massert[39;00m [38;5;28misinstance[39m( [1;32m 397[0m [38;5;28minput[39m, [38;5;28mdict[39m [1;32m 398[0m ), [38;5;124m"[39m[38;5;124mThe input to RunnablePassthrough.assign() must be a dict.[39m[38;5;124m"[39m [1;32m 400[0m [38;5;28;01mreturn[39;00m { [1;32m 401[0m [38;5;241m*[39m[38;5;241m*[39m[38;5;28minput[39m, [0;32m--> 402[0m [38;5;241m*[39m[38;5;241m*[39m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mmapper[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 403[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 404[0m [43m [49m[43mpatch_config[49m[43m([49m[43mconfig[49m[43m,[49m[43m [49m[43mcallbacks[49m[38;5;241;43m=[39;49m[43mrun_manager[49m[38;5;241;43m.[39;49m[43mget_child[49m[43m([49m[43m)[49m[43m)[49m[43m,[49m [1;32m 405[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 406[0m [43m [49m[43m)[49m, [1;32m 407[0m } File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:2339[0m, in [0;36mRunnableParallel.invoke[0;34m(self, input, config)[0m [1;32m 2326[0m [38;5;28;01mwith[39;00m get_executor_for_config(config) [38;5;28;01mas[39;00m executor: [1;32m 2327[0m futures [38;5;241m=[39m [ [1;32m 2328[0m executor[38;5;241m.[39msubmit( [1;32m 2329[0m step[38;5;241m.[39minvoke, [0;32m (...)[0m [1;32m 2337[0m [38;5;28;01mfor[39;00m key, step [38;5;129;01min[39;00m steps[38;5;241m.[39mitems() [1;32m 2338[0m ] [0;32m-> 2339[0m output [38;5;241m=[39m [43m{[49m[43mkey[49m[43m:[49m[43m [49m[43mfuture[49m[38;5;241;43m.[39;49m[43mresult[49m[43m([49m[43m)[49m[43m [49m[38;5;28;43;01mfor[39;49;00m[43m [49m[43mkey[49m[43m,[49m[43m [49m[43mfuture[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[38;5;28;43mzip[39;49m[43m([49m[43msteps[49m[43m,[49m[43m [49m[43mfutures[49m[43m)[49m[43m}[49m [1;32m 2340[0m [38;5;66;03m# finish the root run[39;00m [1;32m 2341[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:2339[0m, in [0;36m<dictcomp>[0;34m(.0)[0m [1;32m 2326[0m [38;5;28;01mwith[39;00m get_executor_for_config(config) [38;5;28;01mas[39;00m executor: [1;32m 2327[0m futures [38;5;241m=[39m [ [1;32m 2328[0m executor[38;5;241m.[39msubmit( [1;32m 2329[0m step[38;5;241m.[39minvoke, [0;32m (...)[0m [1;32m 2337[0m [38;5;28;01mfor[39;00m key, step [38;5;129;01min[39;00m steps[38;5;241m.[39mitems() [1;32m 2338[0m ] [0;32m-> 2339[0m output [38;5;241m=[39m {key: [43mfuture[49m[38;5;241;43m.[39;49m[43mresult[49m[43m([49m[43m)[49m [38;5;28;01mfor[39;00m key, future [38;5;129;01min[39;00m [38;5;28mzip[39m(steps, futures)} [1;32m 2340[0m [38;5;66;03m# finish the root run[39;00m [1;32m 2341[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/_base.py:456[0m, in [0;36mFuture.result[0;34m(self, timeout)[0m [1;32m 454[0m [38;5;28;01mraise[39;00m CancelledError() [1;32m 455[0m [38;5;28;01melif[39;00m [38;5;28mself[39m[38;5;241m.[39m_state [38;5;241m==[39m FINISHED: [0;32m--> 456[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m__get_result[49m[43m([49m[43m)[49m [1;32m 457[0m [38;5;28;01melse[39;00m: [1;32m 458[0m [38;5;28;01mraise[39;00m [38;5;167;01mTimeoutError[39;00m() File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/_base.py:401[0m, in [0;36mFuture.__get_result[0;34m(self)[0m [1;32m 399[0m [38;5;28;01mif[39;00m [38;5;28mself[39m[38;5;241m.[39m_exception: [1;32m 400[0m [38;5;28;01mtry[39;00m: [0;32m--> 401[0m [38;5;28;01mraise[39;00m [38;5;28mself[39m[38;5;241m.[39m_exception [1;32m 402[0m [38;5;28;01mfinally[39;00m: [1;32m 403[0m [38;5;66;03m# Break a reference cycle with the exception in self._exception[39;00m [1;32m 404[0m [38;5;28mself[39m [38;5;241m=[39m [38;5;28;01mNone[39;00m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58[0m, in [0;36m_WorkItem.run[0;34m(self)[0m [1;32m 55[0m [38;5;28;01mreturn[39;00m [1;32m 57[0m [38;5;28;01mtry[39;00m: [0;32m---> 58[0m result [38;5;241m=[39m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mfn[49m[43m([49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43margs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mkwargs[49m[43m)[49m [1;32m 59[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m exc: [1;32m 60[0m [38;5;28mself[39m[38;5;241m.[39mfuture[38;5;241m.[39mset_exception(exc) File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:1774[0m, in [0;36mRunnableSequence.invoke[0;34m(self, input, config)[0m [1;32m 1772[0m [38;5;28;01mtry[39;00m: [1;32m 1773[0m [38;5;28;01mfor[39;00m i, step [38;5;129;01min[39;00m [38;5;28menumerate[39m([38;5;28mself[39m[38;5;241m.[39msteps): [0;32m-> 1774[0m [38;5;28minput[39m [38;5;241m=[39m [43mstep[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 1775[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 1776[0m [43m [49m[38;5;66;43;03m# mark each step as a child run[39;49;00m [1;32m 1777[0m [43m [49m[43mpatch_config[49m[43m([49m [1;32m 1778[0m [43m [49m[43mconfig[49m[43m,[49m[43m [49m[43mcallbacks[49m[38;5;241;43m=[39;49m[43mrun_manager[49m[38;5;241;43m.[39;49m[43mget_child[49m[43m([49m[38;5;124;43mf[39;49m[38;5;124;43m"[39;49m[38;5;124;43mseq:step:[39;49m[38;5;132;43;01m{[39;49;00m[43mi[49m[38;5;241;43m+[39;49m[38;5;241;43m1[39;49m[38;5;132;43;01m}[39;49;00m[38;5;124;43m"[39;49m[43m)[49m [1;32m 1779[0m [43m [49m[43m)[49m[43m,[49m [1;32m 1780[0m [43m [49m[43m)[49m [1;32m 1781[0m [38;5;66;03m# finish the root run[39;00m [1;32m 1782[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/branch.py:211[0m, in [0;36mRunnableBranch.invoke[0;34m(self, input, config, **kwargs)[0m [1;32m 209[0m [38;5;28;01mbreak[39;00m [1;32m 210[0m [38;5;28;01melse[39;00m: [0;32m--> 211[0m output [38;5;241m=[39m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mdefault[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 212[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 213[0m [43m [49m[43mconfig[49m[38;5;241;43m=[39;49m[43mpatch_config[49m[43m([49m [1;32m 214[0m [43m [49m[43mconfig[49m[43m,[49m[43m [49m[43mcallbacks[49m[38;5;241;43m=[39;49m[43mrun_manager[49m[38;5;241;43m.[39;49m[43mget_child[49m[43m([49m[43mtag[49m[38;5;241;43m=[39;49m[38;5;124;43m"[39;49m[38;5;124;43mbranch:default[39;49m[38;5;124;43m"[39;49m[43m)[49m [1;32m 215[0m [43m [49m[43m)[49m[43m,[49m [1;32m 216[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 217[0m [43m [49m[43m)[49m [1;32m 218[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: [1;32m 219[0m run_manager[38;5;241m.[39mon_chain_error(e) File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/prompts/base.py:93[0m, in [0;36mBasePromptTemplate.invoke[0;34m(self, input, config)[0m [1;32m 90[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 91[0m [38;5;28mself[39m, [38;5;28minput[39m: Dict, config: Optional[RunnableConfig] [38;5;241m=[39m [38;5;28;01mNone[39;00m [1;32m 92[0m ) [38;5;241m-[39m[38;5;241m>[39m PromptValue: [0;32m---> 93[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_call_with_config[49m[43m([49m [1;32m 94[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_format_prompt_with_error_handling[49m[43m,[49m [1;32m 95[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 96[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 97[0m [43m [49m[43mrun_type[49m[38;5;241;43m=[39;49m[38;5;124;43m"[39;49m[38;5;124;43mprompt[39;49m[38;5;124;43m"[39;49m[43m,[49m [1;32m 98[0m [43m [49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/base.py:975[0m, in [0;36mRunnable._call_with_config[0;34m(self, func, input, config, run_type, **kwargs)[0m [1;32m 971[0m context [38;5;241m=[39m copy_context() [1;32m 972[0m context[38;5;241m.[39mrun(var_child_runnable_config[38;5;241m.[39mset, child_config) [1;32m 973[0m output [38;5;241m=[39m cast( [1;32m 974[0m Output, [0;32m--> 975[0m [43mcontext[49m[38;5;241;43m.[39;49m[43mrun[49m[43m([49m [1;32m 976[0m [43m [49m[43mcall_func_with_variable_args[49m[43m,[49m [1;32m 977[0m [43m [49m[43mfunc[49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 978[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 979[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 980[0m [43m [49m[43mrun_manager[49m[43m,[49m [1;32m 981[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 982[0m [43m [49m[43m)[49m, [1;32m 983[0m ) [1;32m 984[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: [1;32m 985[0m run_manager[38;5;241m.[39mon_chain_error(e) File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/runnables/config.py:323[0m, in [0;36mcall_func_with_variable_args[0;34m(func, input, config, run_manager, **kwargs)[0m [1;32m 321[0m [38;5;28;01mif[39;00m run_manager [38;5;129;01mis[39;00m [38;5;129;01mnot[39;00m [38;5;28;01mNone[39;00m [38;5;129;01mand[39;00m accepts_run_manager(func): [1;32m 322[0m kwargs[[38;5;124m"[39m[38;5;124mrun_manager[39m[38;5;124m"[39m] [38;5;241m=[39m run_manager [0;32m--> 323[0m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m File [0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/prompts/base.py:83[0m, in [0;36mBasePromptTemplate._format_prompt_with_error_handling[0;34m(self, inner_input)[0m [1;32m 81[0m missing [38;5;241m=[39m [38;5;28mset[39m([38;5;28mself[39m[38;5;241m.[39minput_variables)[38;5;241m.[39mdifference(inner_input) [1;32m 82[0m [38;5;28;01mif[39;00m missing: [0;32m---> 83[0m [38;5;28;01mraise[39;00m [38;5;167;01mKeyError[39;00m( [1;32m 84[0m [38;5;124mf[39m[38;5;124m"[39m[38;5;124mInput to [39m[38;5;132;01m{[39;00m[38;5;28mself[39m[38;5;241m.[39m[38;5;18m__class__[39m[38;5;241m.[39m[38;5;18m__name__[39m[38;5;132;01m}[39;00m[38;5;124m is missing variables [39m[38;5;132;01m{[39;00mmissing[38;5;132;01m}[39;00m[38;5;124m. [39m[38;5;124m"[39m [1;32m 85[0m [38;5;124mf[39m[38;5;124m"[39m[38;5;124m Expected: [39m[38;5;132;01m{[39;00m[38;5;28mself[39m[38;5;241m.[39minput_variables[38;5;132;01m}[39;00m[38;5;124m"[39m [1;32m 86[0m [38;5;124mf[39m[38;5;124m"[39m[38;5;124m Received: [39m[38;5;132;01m{[39;00m[38;5;28mlist[39m(inner_input[38;5;241m.[39mkeys())[38;5;132;01m}[39;00m[38;5;124m"[39m [1;32m 87[0m ) [1;32m 88[0m [38;5;28;01mreturn[39;00m [38;5;28mself[39m[38;5;241m.[39mformat_prompt([38;5;241m*[39m[38;5;241m*[39minner_input) [0;31mKeyError[0m: "Input to ChatPromptTemplate is missing variables {'input'}. Expected: ['input'] Received: ['thought', 'context']"
In [ ]: