-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathagent.py
More file actions
131 lines (116 loc) · 6.25 KB
/
agent.py
File metadata and controls
131 lines (116 loc) · 6.25 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
import json
from groq import Groq
from config import GROQ_API_KEY, LLM_MODEL, MAX_TOOL_ROUNDS
from tools import lookup_plant, get_seasonal_conditions
_client = Groq(api_key=GROQ_API_KEY)
# ──────────────────────────────────────────────
# Tool definitions
#
# These are the schemas that tell the LLM what tools are available and how to
# call them. The LLM reads these descriptions and decides when (and how) to use
# each tool. They're already complete — your job is to implement the tool
# functions in tools.py and the agent loop below.
# ──────────────────────────────────────────────
TOOL_DEFINITIONS = [
{
"type": "function",
"function": {
"name": "lookup_plant",
"description": (
"Look up care information for a specific houseplant by name. "
"Returns detailed watering, light, humidity, and temperature requirements. "
"Use this whenever the user asks about a specific plant."
),
"parameters": {
"type": "object",
"properties": {
"plant_name": {
"type": "string",
"description": "The plant name to look up. Can be a common name, scientific name, or nickname (e.g., 'pothos', 'devil's ivy', 'Monstera deliciosa').",
}
},
"required": ["plant_name"],
},
},
},
{
"type": "function",
"function": {
"name": "get_seasonal_conditions",
"description": (
"Get seasonal care adjustments for houseplants. "
"Returns guidance on watering, fertilizing, light, and pests for the current or specified season. "
"Use this when a user asks a season-specific question, or to complement plant care advice with seasonal context."
),
"parameters": {
"type": "object",
"properties": {
"season": {
"type": "string",
"description": "The season to get care conditions for. If omitted, the current season is detected automatically.",
"enum": ["spring", "summer", "fall", "winter"],
}
},
"required": [],
},
},
},
]
# ──────────────────────────────────────────────
# System prompt
# ──────────────────────────────────────────────
SYSTEM_PROMPT = (
"You are a knowledgeable and friendly plant care advisor. "
"Help users care for their houseplants by looking up specific plant information "
"and current seasonal conditions using your available tools.\n\n"
"Always use your tools to look up plant-specific information before answering — "
"don't rely on your general knowledge alone. If a plant isn't in your database, "
"say so clearly and offer general guidance based on what the user describes.\n\n"
"Keep your advice practical and specific. Cite the source of your information "
"when you have it (e.g., 'According to the care data for your monstera...')."
)
# ──────────────────────────────────────────────
# Tool dispatch
#
# This is already complete. It routes tool calls from the LLM to the actual
# Python functions in tools.py, and returns results as JSON strings (which is
# what the Groq API expects for tool results).
# ──────────────────────────────────────────────
def dispatch_tool(tool_name: str, tool_args: dict) -> str:
"""Route a tool call to the correct function and return the result as a JSON string."""
print(f" → Tool call: {tool_name}({tool_args})")
if tool_name == "lookup_plant":
result = lookup_plant(tool_args["plant_name"])
elif tool_name == "get_seasonal_conditions":
result = get_seasonal_conditions(tool_args.get("season"))
else:
result = {"error": f"Unknown tool: {tool_name}"}
print(f" ← Result: {json.dumps(result)[:120]}{'...' if len(json.dumps(result)) > 120 else ''}")
return json.dumps(result)
# ──────────────────────────────────────────────
# Agent loop
# ──────────────────────────────────────────────
def run_agent(user_message: str, history: list) -> str:
"""
Run the plant care agent for one user turn and return its response.
TODO — Milestone 2:
The agent loop follows a specific pattern that you'll implement here. Read
specs/agent-loop-spec.md carefully before writing any code — understand the
full loop before implementing any part of it.
The loop works like this:
1. Build a messages list: system prompt + conversation history + new user message
2. Call the LLM with messages and TOOL_DEFINITIONS
3. If the response contains tool_calls:
a. Append the assistant message (with tool_calls) to messages
b. For each tool call: execute via dispatch_tool(), append the result
c. Call the LLM again with the updated messages
d. Repeat until no more tool_calls (or MAX_TOOL_ROUNDS is reached)
4. Return the final text response
Key details to get right:
- The assistant message must be appended BEFORE tool results
- Tool result messages use role="tool" with a tool_call_id field
- Append the assistant's message object directly (not just its content)
- The history format from Gradio: list of [user_message, assistant_message] pairs
Before writing code, complete specs/agent-loop-spec.md.
"""
return "🌱 Agent not yet implemented. Complete Milestone 2 to activate the Plant Advisor."