-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent.py
More file actions
165 lines (125 loc) · 5.77 KB
/
agent.py
File metadata and controls
165 lines (125 loc) · 5.77 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
"""
Weather Activity Agent - LangGraph + Qwen (via Ollama)
"""
from langgraph.graph import StateGraph, END
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage
from typing import TypedDict, Annotated
import operator
import json
from tools.weather import get_weather
from tools.activities import get_activity_recommendations
# ── 1. State Definition ─────────────────────────────────────────────────────
# This is the "memory" that flows through every node in the graph.
class AgentState(TypedDict):
messages: Annotated[list, operator.add] # Full message history
location: str # User-provided location
weather_data: dict # Raw weather fetched from API
final_answer: str # Agent's final response
# ── 2. LLM Setup ─────────────────────────────────────────────────────────────
# Point LangChain at your local Ollama instance running Qwen
llm = ChatOllama(
model="qwen3.5:9b", # Change to whatever Qwen model you've pulled
base_url="http://localhost:11434",
temperature=0.7,
)
# Bind our tools so the LLM knows it can call them
tools = [get_weather, get_activity_recommendations]
llm_with_tools = llm.bind_tools(tools)
SYSTEM_PROMPT = """You are a helpful weather and activity assistant.
When a user asks about activities or what to do, you should:
1. First use get_weather to fetch current conditions for their location
2. Then use get_activity_recommendations to suggest suitable activities
3. Finally, summarize both in a friendly, conversational response
Always be specific about WHY certain activities are recommended based on the weather.
"""
# ── 3. Node Functions ─────────────────────────────────────────────────────────
# Each node receives the full state, does something, and returns state updates.
def call_llm(state: AgentState) -> dict:
"""Main LLM node — decides what tool to call next, or generates final answer."""
messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
def run_tools(state: AgentState) -> dict:
"""Tool executor node — runs whatever tool the LLM decided to call."""
tool_map = {t.name: t for t in tools}
last_message = state["messages"][-1]
results = []
for tool_call in last_message.tool_calls:
tool_name = tool_call["name"]
tool_args = tool_call["args"]
print(f" 🔧 Running tool: {tool_name}({tool_args})")
if tool_name in tool_map:
result = tool_map[tool_name].invoke(tool_args)
else:
result = f"Error: tool '{tool_name}' not found."
results.append(
ToolMessage(
content=str(result),
tool_call_id=tool_call["id"],
name=tool_name
)
)
return {"messages": results}
def should_continue(state: AgentState) -> str:
"""
Routing function — the 'brain' of the graph loop.
Returns 'tools' to keep looping, or 'end' to finish.
"""
last_message = state["messages"][-1]
# If the last LLM message has tool calls → go run them
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
# Otherwise the LLM is done — extract the final answer
return "end"
def extract_final(state: AgentState) -> dict:
"""Pulls the final text response out of the last AI message."""
last_message = state["messages"][-1]
return {"final_answer": last_message.content}
# ── 4. Build the Graph ────────────────────────────────────────────────────────
def build_agent():
graph = StateGraph(AgentState)
# Add nodes
graph.add_node("llm", call_llm)
graph.add_node("tools", run_tools)
graph.add_node("extract", extract_final)
# Entry point
graph.set_entry_point("llm")
# Conditional routing after LLM responds
graph.add_conditional_edges(
"llm",
should_continue,
{
"tools": "tools", # LLM wants to call a tool → run it
"end": "extract", # LLM is done → extract answer
}
)
# After tools run, always go back to the LLM to process results
graph.add_edge("tools", "llm")
graph.add_edge("extract", END)
return graph.compile()
# ── 5. Run It ─────────────────────────────────────────────────────────────────
def run_agent(user_input: str, location: str):
agent = build_agent()
print(f"\n{'='*50}")
print(f"📍 Location: {location}")
print(f"💬 Query: {user_input}")
print(f"{'='*50}\n")
initial_state = {
"messages": [HumanMessage(content=f"Location: {location}\n\nUser question: {user_input}")],
"location": location,
"weather_data": {},
"final_answer": "",
}
result = agent.invoke(initial_state)
print(f"\n{'='*50}")
print("✅ FINAL ANSWER:")
print(f"{'='*50}")
print(result["final_answer"])
return result["final_answer"]
if __name__ == "__main__":
# Example run — change location to wherever you want
run_agent(
user_input="What should I do outside today?",
location="Fremont, CA"
)