├── agents_deconstructed
├── __init__.py
├── format_tools.py
├── xml.py
├── openai_functions.py
└── react_chat.py
├── setup.py
├── README.md
└── notebooks
├── xml.ipynb
├── openai_functions.ipynb
└── react_chat.ipynb
/agents_deconstructed/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name="agents_deconstructed",
5 | version="0.0.2",
6 | description="Utils for running 'agents' where the core logic is not obfuscated.",
7 | packages=find_packages(),
8 | install_requires=[
9 | "langchain",
10 | ],
11 | )
12 |
--------------------------------------------------------------------------------
/agents_deconstructed/format_tools.py:
--------------------------------------------------------------------------------
1 | """Methods for formatting tools."""
2 | from typing import Sequence
3 | from langchain.tools import BaseTool
4 |
5 |
6 | def format_tools_description(tools: Sequence[BaseTool]) -> str:
7 | """Format tools just with their description."""
8 | tool_string = ""
9 | for tool in tools:
10 | tool_string += f"{tool.name}: {tool.description}\n"
11 | return tool_string
12 |
13 | def format_tools_args(tools: Sequence[BaseTool]) -> str:
14 | """Format tools with their description and args."""
15 | tool_descs = []
16 | for tool in tools:
17 | tool_desc = (
18 | f"> Tool Name: {tool.name}\n"
19 | f"Tool Description: {tool.description}\n"
20 | f"Tool Args: {tool.args}\n"
21 | )
22 | tool_descs.append(tool_desc)
23 | return "\n".join(tool_descs)
24 |
--------------------------------------------------------------------------------
/agents_deconstructed/xml.py:
--------------------------------------------------------------------------------
1 | from langchain.agents.agent import AgentOutputParser
2 | from langchain.schema import AgentAction, AgentFinish
3 | from typing import Union
4 |
5 | AGENT_INSTRUCTIONS = """You are a helpful assistant. Help the user answer any questions.
6 |
7 | You have access to the following tools:
8 |
9 | {tools}
10 |
11 | In order to use a tool, you can use and tags. \
12 | You will then get back a response in the form
13 | For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond:
14 |
15 | searchweather in SF
16 | 64 degrees
17 |
18 | When you are done, respond with a final answer between . For example:
19 |
20 | The weather in SF is 64 degrees
21 |
22 | Begin!
23 |
24 | Question: {question}"""
25 |
26 | def format_steps(intermediate_steps):
27 | log = ""
28 | for action, observation in intermediate_steps:
29 | log += (
30 | f"{action.tool}{action.tool_input}"
31 | f"{observation}"
32 | )
33 | return log
34 |
35 |
36 | class XMLAgentOutputParser(AgentOutputParser):
37 | """Output parser for XMLAgent."""
38 |
39 | def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
40 | if "" in text:
41 | tool, tool_input = text.split("")
42 | _tool = tool.split("")[1]
43 | _tool_input = tool_input.split("")[1]
44 | return AgentAction(tool=_tool, tool_input=_tool_input, log=text)
45 | elif "" in text:
46 | _, answer = text.split("")
47 | return AgentFinish(return_values={"output": answer}, log=text)
48 | else:
49 | raise ValueError
50 |
51 | def get_format_instructions(self) -> str:
52 | raise NotImplementedError
53 |
54 | @property
55 | def _type(self) -> str:
56 | return "xml-agent"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🤖🪟 Agents Deconstructed
2 |
3 | Deconstructing agents to let you harness their power in a more understandable and customizable way.
4 |
5 | ## 🚀 Overview
6 |
7 | [LangChain](https://github.com/langchain-ai/langchain) Agents (and agents in other frameworks) are powerful but tough to customize.
8 | At the heart of those agents lies a `while` loop.
9 | This package deconstructs agents by providing helper functions and then forcing the user to implement the `while` loop.
10 | This makes agents both more understandable and more customizable.
11 |
12 | ## 📄 Installation
13 | `pip install agents_deconstructed`
14 |
15 | ## 👨👩👧👦 Quickstart
16 |
17 | We currently have two notebooks showing how use `agents_deconstructed`:
18 |
19 | - [XML Agent](notebooks/xml.ipynb): An agent that uses XML formatting (tested with OpenAI and Anthropic Models.)
20 | - [OpenAI Functions](notebooks/openai_functions.ipynb): An agent that uses OpenAI Functions
21 |
22 | ## 🔧 Components
23 |
24 | `agents_deconstructed` provides a few types of helper functions:
25 |
26 | **Tool Formatters**
27 |
28 | Logic for converting tools into a format such that the language model can understand how to work with them.
29 |
30 | **Output Parsers**
31 |
32 | Logic for converting the output of an LLM into an `AgentAction` or `AgentFinish`.
33 |
34 | **Intermediate Steps**
35 |
36 | Logic for converting intermediate steps (tuples of `AgentAction` and `observation`) into a format such that the language model can understand how to work with them.
37 |
38 | **Prompts**
39 |
40 | The main instructions to the language model, this tells it out to think and how to output it's decisions.
41 | This is pretty tightly coupled to a `Output Parser` and way of formatting `Intermediate Steps`
42 |
43 | ## 💻 Algorithm
44 |
45 | With the above components, we can now easily create agent-like logic.
46 | Pseudo-code for that logic:
47 |
48 | ```python
49 | # Create agent as a prompt + LLM + output parser
50 | # This uses LangChain Expression Language for ease of composability
51 | agent = prompt | llm | output_parser
52 | # Initialize empty list to track steps
53 | steps = []
54 | # Use this to group all traces together
55 | with trace_as_chain_group("agent-run") as group_manager:
56 | # Get the first prediction
57 | inputs = {
58 | "input": ...,
59 | # Format intermediate steps as expected
60 | "intermediate_steps": format_steps(steps),
61 | # Format tools as expected
62 | "tools": format_tools(tools),
63 | }
64 | action = agent.invoke(inputs, config={"callbacks": group_manager})
65 | # Enter a loop until an AgentFinish is reached
66 | while not isinstance(action, AgentFinish):
67 | # Select the tool to use
68 | tool = ...
69 | # Run the tool
70 | observation = tool.run(action.tool_input, callbacks=group_manager)
71 | # Construct intermediate steps
72 | steps += [(action, observation)]
73 | # Do the next iteration
74 | inputs = {
75 | "input": ...,
76 | # Format intermediate steps as expected
77 | "intermediate_steps": format_steps(steps),
78 | # Format tools as expected
79 | "tools": format_tools(tools),
80 | }
81 | action = agent.invoke(inputs, config={"callbacks": group_manager})
82 | ```
83 |
84 | ## ⚖️ Comparison to LangChain Agents
85 |
86 | Here is a (biased) comparison against LangChain agents:
87 |
88 | **Pros**:
89 |
90 | - Uses LangChain Expression Language (LangChain Agents haven't switched to use this)
91 | - More customizable
92 | - More understandable
93 |
94 | **Cons**:
95 |
96 | - Doesn't yet have same level of robustness (LangChain agents have safeguards against tools erroring, selecting tools that don't exist, etc)
97 | - More work to set up
98 |
99 | **The Same**:
100 |
101 | - Logging to [LangSmith](https://smith.langchain.com/) (since we can use `trace_as_chain_group`)
102 |
--------------------------------------------------------------------------------
/agents_deconstructed/openai_functions.py:
--------------------------------------------------------------------------------
1 | import json
2 | from json import JSONDecodeError
3 | from typing import List, Tuple, Union
4 |
5 | from langchain.schema import (
6 | AgentAction,
7 | AgentFinish,
8 | BasePromptTemplate,
9 | OutputParserException,
10 | )
11 |
12 | from dataclasses import dataclass
13 | from langchain.schema.messages import (
14 | AIMessage,
15 | BaseMessage,
16 | FunctionMessage,
17 | SystemMessage,
18 | )
19 | from langchain.schema import (
20 | ChatGeneration,
21 | Generation,
22 | OutputParserException,
23 | )
24 | from langchain.schema.output_parser import BaseGenerationOutputParser
25 |
26 | @dataclass
27 | class FunctionsAgentAction(AgentAction):
28 | message_log: List[BaseMessage]
29 |
30 | def _convert_agent_action_to_messages(
31 | agent_action: AgentAction, observation: str
32 | ) -> List[BaseMessage]:
33 | """Convert an agent action to a message.
34 |
35 | This code is used to reconstruct the original AI message from the agent action.
36 |
37 | Args:
38 | agent_action: Agent action to convert.
39 |
40 | Returns:
41 | AIMessage that corresponds to the original tool invocation.
42 | """
43 | if isinstance(agent_action, AgentAction):
44 | return agent_action.message_log + [
45 | _create_function_message(agent_action, observation)
46 | ]
47 | else:
48 | return [AIMessage(content=agent_action.log)]
49 |
50 |
51 | def _create_function_message(
52 | agent_action: AgentAction, observation: str
53 | ) -> FunctionMessage:
54 | """Convert agent action and observation into a function message.
55 | Args:
56 | agent_action: the tool invocation request from the agent
57 | observation: the result of the tool invocation
58 | Returns:
59 | FunctionMessage that corresponds to the original tool invocation
60 | """
61 | if not isinstance(observation, str):
62 | try:
63 | content = json.dumps(observation, ensure_ascii=False)
64 | except Exception:
65 | content = str(observation)
66 | else:
67 | content = observation
68 | return FunctionMessage(
69 | name=agent_action.tool,
70 | content=content,
71 | )
72 |
73 |
74 | def format_intermediate_steps(
75 | intermediate_steps: List[Tuple[AgentAction, str]],
76 | ) -> List[BaseMessage]:
77 | """Format intermediate steps.
78 | Args:
79 | intermediate_steps: Steps the LLM has taken to date, along with observations
80 | Returns:
81 | list of messages to send to the LLM for the next prediction
82 | """
83 | messages = []
84 |
85 | for intermediate_step in intermediate_steps:
86 | agent_action, observation = intermediate_step
87 | messages.extend(_convert_agent_action_to_messages(agent_action, observation))
88 |
89 | return messages
90 |
91 | class FunctionsAgentOutputParser(BaseGenerationOutputParser[Union[AgentAction, AgentFinish]]):
92 |
93 | def parse_result(self, result: List[Generation]) -> Union[AgentAction, AgentFinish]:
94 | generation = result[0]
95 | if not isinstance(generation, ChatGeneration):
96 | raise OutputParserException(
97 | "This output parser can only be used with a chat generation."
98 | )
99 | message = generation.message
100 |
101 | function_call = message.additional_kwargs.get("function_call", {})
102 |
103 | if function_call:
104 | function_name = function_call["name"]
105 | try:
106 | _tool_input = json.loads(function_call["arguments"])
107 | except JSONDecodeError:
108 | raise OutputParserException(
109 | f"Could not parse tool input: {function_call} because "
110 | f"the `arguments` is not valid JSON."
111 | )
112 |
113 | # HACK HACK HACK:
114 | # The code that encodes tool input into Open AI uses a special variable
115 | # name called `__arg1` to handle old style tools that do not expose a
116 | # schema and expect a single string argument as an input.
117 | # We unpack the argument here if it exists.
118 | # Open AI does not support passing in a JSON array as an argument.
119 | if "__arg1" in _tool_input:
120 | tool_input = _tool_input["__arg1"]
121 | else:
122 | tool_input = _tool_input
123 |
124 | content_msg = "responded: {content}\n" if message.content else "\n"
125 |
126 | return FunctionsAgentAction(
127 | tool=function_name,
128 | tool_input=tool_input,
129 | log=f"\nInvoking: `{function_name}` with `{tool_input}`\n{content_msg}\n",
130 | message_log=[message],
131 | )
132 |
133 | return AgentFinish(return_values={"output": message.content}, log=message.content)
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/notebooks/xml.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "23daa091-aa91-464f-9af5-3778783ca920",
6 | "metadata": {},
7 | "source": [
8 | "# Simple XML\n",
9 | "\n",
10 | "This notebook goes over how run an agent that uses XML to format steps"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 5,
16 | "id": "b523ec52-74a4-48e7-8830-e252c4bc6acb",
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "# !pip install openai agents_deconstructed"
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": 1,
26 | "id": "812fd079-698c-4463-ae5f-1e0fa5f8019d",
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "from langchain.agents import tool\n",
31 | "from langchain.schema.agent import AgentFinish\n",
32 | "from langchain.callbacks.manager import (\n",
33 | " trace_as_chain_group,\n",
34 | ")\n",
35 | "from langchain import hub\n",
36 | "from langchain.chat_models import ChatAnthropic, ChatOpenAI"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": 2,
42 | "id": "b4418c1b-14c2-4c89-835b-1bfd0203c90f",
43 | "metadata": {},
44 | "outputs": [],
45 | "source": [
46 | "from agents_deconstructed import xml\n",
47 | "from agents_deconstructed.format_tools import format_tools_description"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": 4,
53 | "id": "79c2e71d-0900-4b01-8760-afcd089cc46c",
54 | "metadata": {},
55 | "outputs": [],
56 | "source": [
57 | "# Create our 'agent' - this is a runnable\n",
58 | "prompt = hub.pull('shoggoth13/xml-agent')\n",
59 | "agent = prompt | ChatOpenAI(temperature=0).bind(stop=[\"\", \"\"]) | xml.XMLAgentOutputParser()"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": 5,
65 | "id": "287a88c5-f508-41f9-8f19-3a9ae3ef203d",
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "# Create a dummy tool\n",
70 | "@tool\n",
71 | "def run_code(query: str):\n",
72 | " \"\"\"Input should be Python code. Use this to do math.\"\"\"\n",
73 | " return eval(query)"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": 6,
79 | "id": "e84e4e03-a97f-4bb4-a869-b565ec4f8a74",
80 | "metadata": {},
81 | "outputs": [
82 | {
83 | "name": "stdout",
84 | "output_type": "stream",
85 | "text": [
86 | "[(AgentAction(tool='run_code', tool_input='2 + 2', log='run_code2 + 2'), 4)]\n"
87 | ]
88 | }
89 | ],
90 | "source": [
91 | "# Use this to group all traces together\n",
92 | "with trace_as_chain_group(\"agent-run\") as group_manager:\n",
93 | " # Keep track of steps taken\n",
94 | " steps = []\n",
95 | " # Get the first response from the agent\n",
96 | " action = agent.invoke({\n",
97 | " \"question\": \"whats 2 + 2\",\n",
98 | " \"tools\": format_tools_description([run_code]),\n",
99 | " \"intermediate_steps\": xml.format_steps(steps)\n",
100 | " }, config={\"callbacks\": group_manager})\n",
101 | "\n",
102 | " # While it's not finished, iterate\n",
103 | " while not isinstance(action, AgentFinish):\n",
104 | " # Select the tool to use\n",
105 | " tool = {\n",
106 | " \"run_code\": run_code,\n",
107 | " }[action.tool]\n",
108 | " # Run the tool\n",
109 | " observation = tool.run(action.tool_input, callbacks=group_manager)\n",
110 | " # Construct intermediate steps\n",
111 | " steps += [(action, observation)]\n",
112 | " # Log the steps\n",
113 | " print(steps)\n",
114 | " # Do the next iteration\n",
115 | " action = agent.invoke({\n",
116 | " \"question\": \"whats 2 + 2\",\n",
117 | " \"tools\": format_tools_description([run_code]),\n",
118 | " \"intermediate_steps\": xml.format_steps(steps)\n",
119 | " }, config={\"callbacks\": group_manager})"
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": 7,
125 | "id": "09321ae0-ea15-48e1-8070-ca8b31dbfc8f",
126 | "metadata": {},
127 | "outputs": [
128 | {
129 | "name": "stdout",
130 | "output_type": "stream",
131 | "text": [
132 | "AgentFinish(return_values={'output': '2 + 2 equals 4'}, log='2 + 2 equals 4')\n"
133 | ]
134 | }
135 | ],
136 | "source": [
137 | "print(action)"
138 | ]
139 | },
140 | {
141 | "cell_type": "code",
142 | "execution_count": null,
143 | "id": "6e5cee08-dd11-4402-af95-6a477a41cb00",
144 | "metadata": {},
145 | "outputs": [],
146 | "source": []
147 | }
148 | ],
149 | "metadata": {
150 | "kernelspec": {
151 | "display_name": "Python 3 (ipykernel)",
152 | "language": "python",
153 | "name": "python3"
154 | },
155 | "language_info": {
156 | "codemirror_mode": {
157 | "name": "ipython",
158 | "version": 3
159 | },
160 | "file_extension": ".py",
161 | "mimetype": "text/x-python",
162 | "name": "python",
163 | "nbconvert_exporter": "python",
164 | "pygments_lexer": "ipython3",
165 | "version": "3.10.2"
166 | }
167 | },
168 | "nbformat": 4,
169 | "nbformat_minor": 5
170 | }
171 |
--------------------------------------------------------------------------------
/agents_deconstructed/react_chat.py:
--------------------------------------------------------------------------------
1 | """Adopted from Llama Index.
2 |
3 | https://github.com/jerryjliu/llama_index/blob/main/llama_index/agent/react/output_parser.py
4 | """
5 | from langchain.schema import (
6 | AgentAction,
7 | AgentFinish,
8 | )
9 | from langchain.schema.output_parser import BaseOutputParser
10 | from langchain.schema.messages import HumanMessage, AIMessage
11 | from typing import Union, Tuple
12 | import ast
13 |
14 | import re
15 |
16 |
17 | def extract_tool_use(input_text: str) -> Tuple[str, str, str]:
18 | pattern = r"\s*Thought:(.*?)Action:(.*?)Action Input:(.*?)(?:\n|$)"
19 |
20 | match = re.search(pattern, input_text, re.DOTALL)
21 | if not match:
22 | raise ValueError(
23 | "Could not extract tool use from input text: {}".format(input_text)
24 | )
25 |
26 | thought = match.group(1).strip()
27 | action = match.group(2).strip()
28 | action_input = match.group(3).strip()
29 | return thought, action, action_input
30 |
31 |
32 | def extract_final_response(input_text: str) -> Tuple[str, str]:
33 | pattern = r"\s*Thought:(.*?)Answer:(.*?)(?:\n|$)"
34 |
35 | match = re.search(pattern, input_text, re.DOTALL)
36 | if not match:
37 | raise ValueError(
38 | "Could not extract final answer from input text: {}".format(input_text)
39 | )
40 |
41 | thought = match.group(1).strip()
42 | answer = match.group(2).strip()
43 | return thought, answer
44 |
45 | def extract_json_str(text: str) -> str:
46 | """Extract JSON string from text."""
47 | # NOTE: this regex parsing is taken from langchain.output_parsers.pydantic
48 | match = re.search(r"\{.*\}", text.strip(), re.MULTILINE | re.IGNORECASE | re.DOTALL)
49 | if not match:
50 | raise ValueError(f"Could not extract json string from output: {text}")
51 |
52 | return match.group()
53 |
54 |
55 | class ReActOutputParser(BaseOutputParser):
56 |
57 | def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
58 | """Parse output from ReAct agent.
59 |
60 | We expect the output to be in one of the following formats:
61 | 1. If the agent need to use a tool to answer the question:
62 | ```
63 | Thought:
64 | Action:
65 | Action Input:
66 | ```
67 | 2. If the agent can answer the question without any tools:
68 | ```
69 | Thought:
70 | Answer:
71 | ```
72 | """
73 | if "Thought:" not in text:
74 | # NOTE: handle the case where the agent directly outputs the answer
75 | # instead of following the thought-answer format
76 | return AgentFinish(
77 | log="I can answer without any tools.", return_values={"output": text}
78 | )
79 |
80 | if "Answer:" in text:
81 | thought, answer = extract_final_response(text)
82 | return AgentFinish(
83 | log=thought, return_values={"output": answer}
84 | )
85 |
86 | if "Action:" in text:
87 | thought, action, action_input = extract_tool_use(text)
88 | json_str = extract_json_str(action_input)
89 |
90 | # NOTE: we found that json.loads does not reliably parse
91 | # json with single quotes, so we use ast instead
92 | # action_input_dict = json.loads(json_str)
93 | action_input_dict = ast.literal_eval(json_str)
94 |
95 | return AgentAction(
96 | log=thought, tool=action, tool_input=action_input_dict
97 | )
98 |
99 | raise ValueError("Could not parse output: {}".format(text))
100 |
101 |
102 | def format_steps(intermediate_steps):
103 | log = []
104 | for action, observation in intermediate_steps:
105 | log.extend([
106 | HumanMessage(content=action.log),
107 | AIMessage(content=f"Observation: {observation}")
108 | ])
109 | return log
110 |
111 | REACT_AGENT_INSTRUCTIONS = """\
112 |
113 | You are designed to help with a variety of tasks, from answering questions \
114 | to providing summaries to other types of analyses.
115 |
116 | ## Tools
117 | You have access to a wide variety of tools. You are responsible for using
118 | the tools in any sequence you deem appropriate to complete the task at hand.
119 | This may require breaking the task into subtasks and using different tools
120 | to complete each subtask.
121 |
122 | You have access to the following tools:
123 | {tool_desc}
124 |
125 | ## Output Format
126 | To answer the question, please use the following format.
127 |
128 | ```
129 | Thought: I need to use a tool to help me answer the question.
130 | Action: tool name (one of {tool_names})
131 | Action Input: the input to the tool, in a JSON format representing the kwargs (e.g. {{"text": "hello world", "num_beams": 5}})
132 | ```
133 | Please use a valid JSON format for the action input. Do NOT do this {{'text': 'hello world', 'num_beams': 5}}.
134 |
135 | If this format is used, the user will respond in the following format:
136 |
137 | ```
138 | Observation: tool response
139 | ```
140 |
141 | You should keep repeating the above format until you have enough information
142 | to answer the question without using any more tools. At that point, you MUST respond
143 | in the following format:
144 |
145 | ```
146 | Thought: I can answer without using any more tools.
147 | Answer: [your answer here]
148 | ```
149 |
150 | ## Current Conversation
151 | Below is the current conversation consisting of interleaving human and assistant messages.
152 |
153 | """
154 |
--------------------------------------------------------------------------------
/notebooks/openai_functions.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "23daa091-aa91-464f-9af5-3778783ca920",
6 | "metadata": {},
7 | "source": [
8 | "# OpenAI Functions\n",
9 | "\n",
10 | "This notebook goes over how run an agent that uses OpenAI Functions"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 5,
16 | "id": "b523ec52-74a4-48e7-8830-e252c4bc6acb",
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "# !pip install openai agents_deconstructed"
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": 14,
26 | "id": "812fd079-698c-4463-ae5f-1e0fa5f8019d",
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "from langchain.agents import tool\n",
31 | "from langchain.schema.agent import AgentFinish\n",
32 | "from langchain.callbacks.manager import (\n",
33 | " trace_as_chain_group,\n",
34 | ")\n",
35 | "from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
36 | "from langchain.chat_models import ChatOpenAI\n",
37 | "from langchain.tools.convert_to_openai import format_tool_to_openai_function"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": 4,
43 | "id": "b4418c1b-14c2-4c89-835b-1bfd0203c90f",
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "from agents_deconstructed import openai_functions"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": 5,
53 | "id": "1341df6a-b9d6-4dc3-a6d0-c8055d40d6fe",
54 | "metadata": {},
55 | "outputs": [],
56 | "source": [
57 | "# Create a dummy tool\n",
58 | "@tool\n",
59 | "def run_code(query: str):\n",
60 | " \"\"\"Input should be Python code. Use this to do math.\"\"\"\n",
61 | " return eval(query)"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": 7,
67 | "id": "b6d078f2-dfc0-4754-91e8-403708483e3d",
68 | "metadata": {},
69 | "outputs": [],
70 | "source": [
71 | "functions = [dict(format_tool_to_openai_function(t)) for t in [run_code]]"
72 | ]
73 | },
74 | {
75 | "cell_type": "code",
76 | "execution_count": 15,
77 | "id": "79c2e71d-0900-4b01-8760-afcd089cc46c",
78 | "metadata": {},
79 | "outputs": [],
80 | "source": [
81 | "# Create our 'agent' - this is a runnable\n",
82 | "agent = ChatPromptTemplate.from_messages([\n",
83 | " (\"system\", \"You are a helpful assistant\"),\n",
84 | " (\"user\", \"{question}\"),\n",
85 | " MessagesPlaceholder(variable_name=\"intermediate_steps\"),\n",
86 | "]) | ChatOpenAI(temperature=0).bind(functions=functions) | openai_functions.FunctionsAgentOutputParser()"
87 | ]
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": 16,
92 | "id": "e84e4e03-a97f-4bb4-a869-b565ec4f8a74",
93 | "metadata": {},
94 | "outputs": [
95 | {
96 | "name": "stdout",
97 | "output_type": "stream",
98 | "text": [
99 | "[(FunctionsAgentAction(tool='run_code', tool_input={'query': '2 + 2'}, log=\"\\nInvoking: `run_code` with `{'query': '2 + 2'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'name': 'run_code', 'arguments': '{\\n \"query\": \"2 + 2\"\\n}'}}, example=False)]), 4)]\n"
100 | ]
101 | }
102 | ],
103 | "source": [
104 | "# Use this to group all traces together\n",
105 | "with trace_as_chain_group(\"agent-run\") as group_manager:\n",
106 | " # Keep track of steps taken\n",
107 | " steps = []\n",
108 | " # Get the first response from the agent\n",
109 | " action = agent.invoke({\n",
110 | " \"question\": \"whats 2 + 2\",\n",
111 | " \"intermediate_steps\": openai_functions.format_intermediate_steps(steps)\n",
112 | " }, config={\"callbacks\": group_manager})\n",
113 | "\n",
114 | " # While it's not finished, iterate\n",
115 | " while not isinstance(action, AgentFinish):\n",
116 | " # Select the tool to use\n",
117 | " tool = {\n",
118 | " \"run_code\": run_code,\n",
119 | " }[action.tool]\n",
120 | " # Run the tool\n",
121 | " observation = tool.run(action.tool_input, callbacks=group_manager)\n",
122 | " # Construct intermediate steps\n",
123 | " steps += [(action, observation)]\n",
124 | " # Log the steps\n",
125 | " print(steps)\n",
126 | " # Do the next iteration\n",
127 | " action = agent.invoke({\n",
128 | " \"question\": \"whats 2 + 2\",\n",
129 | " \"intermediate_steps\": openai_functions.format_intermediate_steps(steps)\n",
130 | " }, config={\"callbacks\": group_manager})"
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": 17,
136 | "id": "09321ae0-ea15-48e1-8070-ca8b31dbfc8f",
137 | "metadata": {},
138 | "outputs": [
139 | {
140 | "name": "stdout",
141 | "output_type": "stream",
142 | "text": [
143 | "AgentFinish(return_values={'output': '2 + 2 equals 4.'}, log='2 + 2 equals 4.')\n"
144 | ]
145 | }
146 | ],
147 | "source": [
148 | "print(action)"
149 | ]
150 | },
151 | {
152 | "cell_type": "code",
153 | "execution_count": null,
154 | "id": "6e5cee08-dd11-4402-af95-6a477a41cb00",
155 | "metadata": {},
156 | "outputs": [],
157 | "source": []
158 | }
159 | ],
160 | "metadata": {
161 | "kernelspec": {
162 | "display_name": "Python 3 (ipykernel)",
163 | "language": "python",
164 | "name": "python3"
165 | },
166 | "language_info": {
167 | "codemirror_mode": {
168 | "name": "ipython",
169 | "version": 3
170 | },
171 | "file_extension": ".py",
172 | "mimetype": "text/x-python",
173 | "name": "python",
174 | "nbconvert_exporter": "python",
175 | "pygments_lexer": "ipython3",
176 | "version": "3.10.2"
177 | }
178 | },
179 | "nbformat": 4,
180 | "nbformat_minor": 5
181 | }
182 |
--------------------------------------------------------------------------------
/notebooks/react_chat.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "3b080df1-e300-43a8-8249-1551a30e2913",
6 | "metadata": {},
7 | "source": [
8 | "# ReAct Chat\n",
9 | "\n",
10 | "This agent uses ReAct to reason about what to do. Works with Chat Models.\n",
11 | "\n",
12 | "Logic \"deconstructed\" from LlamaIndex"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": 1,
18 | "id": "ed0082ff-6801-48ad-a87e-03bf6afc4642",
19 | "metadata": {},
20 | "outputs": [],
21 | "source": [
22 | "# !pip install openai agents_deconstructed"
23 | ]
24 | },
25 | {
26 | "cell_type": "code",
27 | "execution_count": 10,
28 | "id": "f2cdb5a6-6d7e-4c9f-b8c5-a43cd3cd0dff",
29 | "metadata": {},
30 | "outputs": [],
31 | "source": [
32 | "from langchain.agents import tool\n",
33 | "from langchain.schema.agent import AgentFinish\n",
34 | "from langchain.callbacks.manager import (\n",
35 | " trace_as_chain_group,\n",
36 | ")\n",
37 | "from langchain import hub\n",
38 | "from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
39 | "from langchain.chat_models import ChatOpenAI"
40 | ]
41 | },
42 | {
43 | "cell_type": "code",
44 | "execution_count": 3,
45 | "id": "45b71716-ee57-4103-8eac-ff4b4db2f48e",
46 | "metadata": {},
47 | "outputs": [],
48 | "source": [
49 | "from agents_deconstructed import react_chat\n",
50 | "from agents_deconstructed.format_tools import format_tools_args"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 4,
56 | "id": "cb691a5d-1f4e-4432-87d1-807b7660fcef",
57 | "metadata": {},
58 | "outputs": [],
59 | "source": [
60 | "# Create a dummy tool\n",
61 | "@tool\n",
62 | "def run_code(query: str):\n",
63 | " \"\"\"Input should be Python code. Use this to do math.\"\"\"\n",
64 | " return eval(query)"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": 19,
70 | "id": "765d17bf-0aa4-4d41-a534-8135d893cc80",
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "# Create our prompt, either directly or from hub\n",
75 | "prompt = ChatPromptTemplate.from_messages([\n",
76 | " (\"system\", react_chat.REACT_AGENT_INSTRUCTIONS),\n",
77 | " (\"user\", \"{question}\"),\n",
78 | " MessagesPlaceholder(variable_name=\"intermediate_steps\"),\n",
79 | "])\n",
80 | "# Or call pull from hub!\n",
81 | "#prompt = hub.pull(\"shoggoth13/react-chat-agent\")"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": 20,
87 | "id": "3c7f6f77-19b2-4238-b277-3bf6c0ae9e94",
88 | "metadata": {},
89 | "outputs": [],
90 | "source": [
91 | "tools = [run_code]"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": 21,
97 | "id": "217f893a-1b70-4fb7-b54a-15a21514b5df",
98 | "metadata": {},
99 | "outputs": [],
100 | "source": [
101 | "prompt = prompt.partial(\n",
102 | " tool_desc=format_tools_args(tools),\n",
103 | " tool_names=\", \".join([tool.name for tool in tools])\n",
104 | ")"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": 22,
110 | "id": "60eb89c1-0a62-4c2d-b2e3-b5cf3d4491bd",
111 | "metadata": {},
112 | "outputs": [],
113 | "source": [
114 | "agent = prompt | ChatOpenAI(temperature=0) | react_chat.ReActOutputParser()"
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": 23,
120 | "id": "1c400980-13c7-4098-b85c-58d6c3aeb452",
121 | "metadata": {},
122 | "outputs": [
123 | {
124 | "name": "stdout",
125 | "output_type": "stream",
126 | "text": [
127 | "[(AgentAction(tool='run_code', tool_input={'query': '2 + 2'}, log='I can use the `run_code` tool to perform the addition.'), 4)]\n"
128 | ]
129 | }
130 | ],
131 | "source": [
132 | "# Use this to group all traces together\n",
133 | "with trace_as_chain_group(\"agent-run\") as group_manager:\n",
134 | " # Keep track of steps taken\n",
135 | " steps = []\n",
136 | " # Get the first response from the agent\n",
137 | " action = agent.invoke({\n",
138 | " \"question\": \"whats 2 + 2\",\n",
139 | " \"intermediate_steps\": react_chat.format_steps(steps)\n",
140 | " }, config={\"callbacks\": group_manager})\n",
141 | "\n",
142 | " # While it's not finished, iterate\n",
143 | " while not isinstance(action, AgentFinish):\n",
144 | " # Select the tool to use\n",
145 | " tool = {\n",
146 | " \"run_code\": run_code,\n",
147 | " }[action.tool]\n",
148 | " # Run the tool\n",
149 | " observation = tool.run(action.tool_input, callbacks=group_manager)\n",
150 | " # Construct intermediate steps\n",
151 | " steps += [(action, observation)]\n",
152 | " # Log the steps\n",
153 | " print(steps)\n",
154 | " # Do the next iteration\n",
155 | " action = agent.invoke({\n",
156 | " \"question\": \"whats 2 + 2\",\n",
157 | " \"intermediate_steps\": react_chat.format_steps(steps)\n",
158 | " }, config={\"callbacks\": group_manager})"
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": 24,
164 | "id": "170434d9-c737-4906-8c9e-6ae65d1fcf40",
165 | "metadata": {},
166 | "outputs": [
167 | {
168 | "data": {
169 | "text/plain": [
170 | "AgentFinish(return_values={'output': '4'}, log='I can answer without using any more tools.')"
171 | ]
172 | },
173 | "execution_count": 24,
174 | "metadata": {},
175 | "output_type": "execute_result"
176 | }
177 | ],
178 | "source": [
179 | "action"
180 | ]
181 | },
182 | {
183 | "cell_type": "code",
184 | "execution_count": null,
185 | "id": "7bc5d6df-60a1-4010-b2a1-7bed2f7e1b41",
186 | "metadata": {},
187 | "outputs": [],
188 | "source": []
189 | }
190 | ],
191 | "metadata": {
192 | "kernelspec": {
193 | "display_name": "Python 3 (ipykernel)",
194 | "language": "python",
195 | "name": "python3"
196 | },
197 | "language_info": {
198 | "codemirror_mode": {
199 | "name": "ipython",
200 | "version": 3
201 | },
202 | "file_extension": ".py",
203 | "mimetype": "text/x-python",
204 | "name": "python",
205 | "nbconvert_exporter": "python",
206 | "pygments_lexer": "ipython3",
207 | "version": "3.10.2"
208 | }
209 | },
210 | "nbformat": 4,
211 | "nbformat_minor": 5
212 | }
213 |
--------------------------------------------------------------------------------