forked from rungalileo/sdk-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent.py
More file actions
201 lines (144 loc) · 5.37 KB
/
Copy pathagent.py
File metadata and controls
201 lines (144 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import os
from typing import TypedDict, Annotated
import dotenv
import openai
from langgraph.graph import END, StateGraph, add_messages
from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool
from langchain_core.messages import BaseMessage
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
openai_api_key = os.environ.get("OPENAI_API_KEY")
if not openai_api_key:
raise ValueError("OPENAI_API_KEY environment variable is required")
# Initialize OpenAI client for tool usage
client = openai.OpenAI(api_key=openai_api_key)
print("OpenAI client configured")
# ============================================================================
# TOOL DEFINITIONS
# ============================================================================
@tool
def validate_input_tool(user_input: str) -> str:
"""
Validates and prepares the user input for processing.
Args:
user_input: The user's input question to validate
Returns:
The validated user input
"""
print(f"[TOOL] Validating input: '{user_input}'")
if not user_input or len(user_input.strip()) == 0:
return "Error: Empty input provided"
return user_input.strip()
@tool
def generate_response_tool(user_input: str) -> str:
"""
Generates a response from OpenAI based on the user input.
Args:
user_input: The validated user input question
Returns:
The LLM's response to the question
"""
try:
print(f"[TOOL] Calling OpenAI with: '{user_input}'")
# Make the OpenAI API call - Traceloop automatically traces this
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": user_input}],
max_tokens=300,
temperature=0.7,
)
# Extract the response content
llm_response = response.choices[0].message.content
if not llm_response:
print("No response from OpenAI")
return "Error: No response from OpenAI"
else:
print(f"Received response: '{llm_response[:100]}...'")
return llm_response
except Exception as e:
print(f"Error calling OpenAI: {e}")
return f"Error: {str(e)}"
@tool
def format_answer_tool(llm_response: str) -> str:
"""
Formats and cleans up the LLM response into a concise answer.
Args:
llm_response: The raw response from the LLM
Returns:
A formatted and cleaned answer
"""
print(f"[TOOL] Formatting answer from: '{llm_response[:50]}...'")
# Simple parsing - extract first sentence for a concise answer
sentences = llm_response.split(". ")
parsed_answer = sentences[0] if sentences else llm_response
# Clean up the answer
parsed_answer = parsed_answer.strip()
if not parsed_answer.endswith(".") and parsed_answer:
parsed_answer += "."
print(f"Parsed answer: '{parsed_answer}'")
return parsed_answer
# List of all available tools
tools = [validate_input_tool, generate_response_tool, format_answer_tool]
# ============================================================================
# STATE DEFINITION
# ============================================================================
class AgentState(TypedDict):
# The add_messages reducer handles message list updates properly
# It ensures messages are appended correctly without duplication
messages: Annotated[list[BaseMessage], add_messages]
# ============================================================================
# NODE FUNCTIONS
# ============================================================================
def agent_node(state: AgentState):
"""
The agent node that decides which tools to call.
"""
messages = state["messages"]
# Initialize the LLM with tool calling capabilities
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
llm_with_tools = llm.bind_tools(tools)
# Get the agent's response
response = llm_with_tools.invoke(messages)
print(f"Agent response: {response}")
# Return just the new message - add_messages reducer will append it
return {"messages": [response]}
def should_continue(state: AgentState):
"""
Determines whether to continue with tool calls or end.
"""
messages = state["messages"]
last_message = messages[-1]
# If there are tool calls, continue to the tools node
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
# Otherwise, end the workflow
return "end"
# ============================================================================
# AGENT FACTORY
# ============================================================================
def create_agent():
"""
Creates an agent that uses tools to process requests.
This demonstrates the tool-calling pattern in LangGraph.
"""
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node("agent", agent_node)
workflow.add_node("tools", ToolNode(tools))
# Set entry point
workflow.set_entry_point("agent")
# Add conditional edges
workflow.add_conditional_edges(
"agent",
should_continue,
{
"tools": "tools",
"end": END,
},
)
# After tools are called, go back to the agent
workflow.add_edge("tools", "agent")
# Compile the workflow
app = workflow.compile()
return app