This commit is contained in:
Harrison Chase
2024-01-05 16:01:08 -08:00
committed by Nuno Campos
parent 80147c02e9
commit 6a93a68fc3
2 changed files with 222 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "53b9dce4-e4ae-4bdb-b752-0f04350a2e3d",
"metadata": {},
"outputs": [],
"source": [
"from langchain.chat_models import ChatOpenAI\n",
"from langchain.agents import create_openai_functions_agent\n",
"from langchain_core.prompts import PromptTemplate\n",
"from langchain import hub\n",
"from langchain.agents import AgentExecutor, create_openai_functions_agent\n",
"from langchain_community.chat_models import ChatOpenAI\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_core.runnables import RunnablePassthrough, RunnableLambda\n",
"from permchain.langgraph import Actor, DecisionPoint, Graph\n",
"\n",
"tools = [TavilySearchResults(max_results=1)]\n",
"\n",
"# Get the prompt to use - you can modify this!\n",
"prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
"\n",
"# Choose the LLM that will drive the agent\n",
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\")\n",
"\n",
"# Construct the OpenAI Functions agent\n",
"agent_runnable = create_openai_functions_agent(llm, tools, prompt)\n",
"\n",
"from langchain_core.agents import AgentFinish\n",
"# Define decision-making logic\n",
"def should_continue(data):\n",
" # Logic to decide whether to continue in the loop or exit\n",
" if isinstance(data['agent_outcome'], AgentFinish):\n",
" return \"exit\"\n",
" else:\n",
" return \"continue\"\n",
" \n",
"def execute_tools(data):\n",
" agent_action = data.pop('agent_outcome')\n",
" observation = {t.name: t for t in tools}[agent_action.tool].invoke(agent_action.tool_input)\n",
" data['intermediate_steps'].append((agent_action, observation))\n",
" return data\n",
" \n",
" \n",
"\n",
"# Define agents\n",
"agent = RunnablePassthrough.assign(\n",
" agent_outcome = agent_runnable\n",
")\n",
"\n",
"# Define a new graph\n",
"workflow = Graph()\n",
"decision_point = DecisionPoint(\"exit?\", should_continue)\n",
"llm_agent = Actor(\"agent\", agent)\n",
"tool_actor = Actor(\"tools\", RunnableLambda(execute_tools))\n",
"\n",
"# Register actors\n",
"workflow.register(llm_agent)\n",
"workflow.register(tool_actor)\n",
"workflow.register(decision_point)\n",
"\n",
"# Define connections with conditional logic\n",
"workflow.connect(llm_agent, decision_point)\n",
"workflow.branch(decision_point, tool_actor, condition=\"continue\")\n",
"workflow.branch(decision_point, None, condition=\"exit\") # Exit the workflow\n",
"workflow.connect(tool_actor, llm_agent)\n",
"\n",
"# Define entry point and execute the graph\n",
"workflow.set_entry_point(llm_agent)\n",
"chain = workflow.compile()\n",
"\n",
"chain.invoke({\"input\": \"what is the weather in sf\", \"intermediate_steps\": []})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2515edbc-a9b6-42ce-bb5e-f1f2503d1bb4",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+115
View File
@@ -0,0 +1,115 @@
from langchain_core.runnables import Runnable, RunnableMap
from typing import Callable, Union, Optional
from permchain import Channel, Pregel
class Actor:
def __init__(self, name: str, runnable: Runnable):
self.name = name
self.runnable = runnable
class DecisionPoint:
def __init__(self, name: str, callable: Callable):
self.name = name
self.callable = callable
class End:
name = "end"
def branch(data, condition, mapping):
result = condition(data)
return Channel.write_to(mapping[result])
class Graph:
def __init__(self):
self.nodes = {"end": End()}
self.connections = {}
self.branches = {}
self.entry_point: Optional[str] = None
self.finish_points = set()
def register(self, node: Union[Actor, DecisionPoint]):
if node.name in self.nodes:
raise ValueError(f"Actor `{node.name}` already present.")
self.nodes[node.name] = node
def connect(self, start: Actor, end: Union[Actor, DecisionPoint]):
if start.name not in self.nodes:
raise ValueError(f"Need to register `{start.name}` first")
if end.name not in self.nodes:
raise ValueError(f"Need to register `{end.name}` first")
if start.name in self.connections:
raise ValueError(f"Already found path for {start.name}")
self.connections[start.name] = end.name
def branch(self, start: DecisionPoint, end: Optional[Actor], condition: str):
end = end or End()
if start.name not in self.nodes:
raise ValueError(f"Need to register `{start.name}` first")
if end.name not in self.nodes:
raise ValueError(f"Need to register `{end.name}` first")
if start.name not in self.branches:
self.branches[start.name] = {}
if condition in self.branches[start.name]:
raise ValueError(f"Already found a condition for {start.name} and {condition}")
self.branches[start.name][condition] = end.name
def set_entry_point(self, node: Union[DecisionPoint, Actor]):
if node.name not in self.nodes:
raise ValueError(f"Need to register `{node.name}` first")
self.entry_point = node.name
def set_finish_point(self, node: Actor):
if node.name not in self.nodes:
raise ValueError(f"Need to register `{node.name}` first")
self.finish_points |= node.name
def compile(self):
# Validate all nodes have an entry point
all_nodes = set(self.nodes)
all_entry_points = set(self.connections).union(self.branches).union(self.finish_points)
branch_ends = set()
for v in self.branches.values():
branch_ends.update(v.values())
all_finish_points = set(self.connections.values()).union(branch_ends).union({self.entry_point})
# If a node is not a finish point, then it is missing an entry point
missing_entry = all_nodes.difference(all_finish_points)
if missing_entry:
raise ValueError(f"Some nodes are missing entry points: {missing_entry}")
# If a node is not an entry point, then it is missing a finish point
missing_finish = all_nodes.difference(all_entry_points).difference({"end"})
if missing_finish:
raise ValueError(f"Some nodes are missing finish points: {missing_finish}")
chains = {
start: Channel.subscribe_to(start) | self.nodes[start].runnable | Channel.write_to(end)
for start, end in self.connections.items()
}
decisions = {
start: Channel.subscribe_to(start) | (lambda x: branch(x, self.nodes[start].callable, mapping))
for start, mapping in self.branches.items()
}
endings = {
end: Channel.subscribe_to(end) | self.nodes[end].runnable | Channel.write_to("end")
for end in self.finish_points
}
app = Pregel(
chains = {**chains, **decisions, **endings},
input=self.entry_point,
output="end"
)
return app