bk1210's picture
Upload 4 files
751c5e3 verified
Raw
History Blame Contribute Delete
6.28 kB
"""
agent.py - Smart Resource Finder Agent
Implements the Observe β†’ Think β†’ Act agentic loop using Groq + tool calling.
"""
import json
import os
from groq import Groq
from tools import RESOURCE_TOOL, execute_tool
# ─── Constants ────────────────────────────────────────────────────────────────
MODEL = "llama-3.3-70b-versatile"
SYSTEM_PROMPT = """You are StudyBot, an expert AI academic assistant for college students.
Your mission: help students find the best possible learning resources for any academic topic.
## Your Behaviour
1. OBSERVE β€” carefully read the student's topic.
2. THINK β€” determine the appropriate difficulty level and which resource types will help most.
3. ACT β€” call the `search_study_resources` tool with the right parameters, then use its
output to compose a rich, well-structured Markdown response.
## Response Format (after tool call)
Structure your final answer with these sections:
- 🎯 **Topic Overview** β€” 2-3 sentences explaining what the topic is about
- πŸ“š **Recommended Resources** β€” grouped by type with names, URLs, and why they help
- πŸ—ΊοΈ **Suggested Study Path** β€” a short ordered plan (e.g., Step 1 β†’ Step 2 β†’ Step 3)
- πŸ’‘ **Pro Tips** β€” 2-3 quick study tips specific to this topic
Use clear Markdown: headers, bullet points, bold text, and emoji for readability.
Always include real, accurate resource names and URLs where you know them.
Keep the tone friendly, encouraging, and concise."""
# ─── Agent ────────────────────────────────────────────────────────────────────
class ResourceFinderAgent:
"""
Agentic loop:
1. Send user topic + system prompt to Groq LLM.
2. LLM decides to call `search_study_resources` tool.
3. Agent executes the tool and feeds result back to LLM.
4. LLM composes the final Markdown answer.
"""
def __init__(self, api_key: str):
self.client = Groq(api_key=api_key)
# ── Step helpers ──────────────────────────────────────────────────────────
def _observe(self, topic: str) -> list[dict]:
"""Build the initial message list from the student's topic."""
return [
{
"role": "user",
"content": (
f"I need study resources for the following topic: **{topic}**\n\n"
"Please find comprehensive learning materials that will help me "
"understand and master this subject."
),
}
]
def _think_and_act(self, messages: list[dict]) -> str:
"""
Send messages to Groq. If the model calls a tool, execute it and
continue the loop until a final text response is produced.
"""
max_iterations = 5 # safety cap
for _ in range(max_iterations):
response = self.client.chat.completions.create(
model=MODEL,
messages=messages,
tools=[RESOURCE_TOOL],
tool_choice="auto",
max_tokens=2048,
temperature=0.7,
)
choice = response.choices[0]
# ── Tool call branch ──────────────────────────────────────────
if choice.finish_reason == "tool_calls":
assistant_msg = {
"role": "assistant",
"content": choice.message.content or "",
"tool_calls": [
{
"id": tc.id,
"type": "function",
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in choice.message.tool_calls
],
}
messages.append(assistant_msg)
# Execute every tool the model requested
for tool_call in choice.message.tool_calls:
tool_args = json.loads(tool_call.function.arguments)
tool_result = execute_tool(tool_call.function.name, tool_args)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result,
}
)
# Loop back β†’ model will now compose the final answer
continue
# ── Final text response ───────────────────────────────────────
return choice.message.content or "No response generated."
return "Agent reached maximum iterations without a final answer."
# ── Public API ────────────────────────────────────────────────────────────
def run(self, topic: str) -> dict:
"""
Full Observe β†’ Think β†’ Act pipeline.
Returns a dict with keys: topic, result, steps.
"""
steps = []
# 1. Observe
steps.append(f"πŸ‘οΈ **Observe:** Received topic β†’ _{topic}_")
messages = self._observe(topic)
# 2. Think + Act
steps.append("🧠 **Think:** Analysing topic and selecting resource types…")
steps.append("⚑ **Act:** Calling Groq LLM with tool-use enabled…")
result = self._think_and_act(messages)
steps.append("βœ… **Done:** Resources compiled and formatted.")
return {"topic": topic, "result": result, "steps": steps}