Skip to content

Commit ecf3508

Browse files
init: TODO agent
1 parent ee680a4 commit ecf3508

File tree

3 files changed

+449
-0
lines changed

3 files changed

+449
-0
lines changed

examples/agents/todo_example.py

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
#!/usr/bin/env python3
2+
"""Simplified Hiking Trip Planning Agent - Process Under Hood, Stream Final Summary."""
3+
4+
import asyncio
5+
import json
6+
import os
7+
8+
from pydantic import BaseModel
9+
10+
from ragbits.agents.todo_agent import TodoAgent
11+
from ragbits.core.llms import LiteLLM
12+
from ragbits.core.prompt.prompt import Prompt
13+
14+
15+
class HikingPlanInput(BaseModel):
16+
"""Simplified input for hiking trip planning."""
17+
18+
location: str
19+
duration_days: int
20+
group_size: int
21+
skill_level: str
22+
season: str
23+
preferences: str
24+
25+
26+
class PlanningPrompt(Prompt[HikingPlanInput]):
27+
"""Unified prompt for all hiking planning tasks."""
28+
29+
system_prompt = """
30+
You are an expert hiking guide with 15+ years of experience. You create comprehensive,
31+
actionable hiking trip plans by working through planning tasks systematically.
32+
33+
Always prioritize safety while maximizing the adventure experience.
34+
Provide specific recommendations with real names, locations, costs, and practical details.
35+
"""
36+
37+
user_prompt = """
38+
Plan a {{ duration_days }}-day hiking trip for {{ group_size }} {{ skill_level }} hikers
39+
in {{ location }} during {{ season }}.
40+
41+
Preferences: {{ preferences }}
42+
43+
Complete this specific planning task with detailed, actionable recommendations.
44+
"""
45+
46+
47+
class TaskGenerationPrompt(Prompt[HikingPlanInput]):
48+
"""Prompt for AI to generate planning tasks."""
49+
50+
system_prompt = """
51+
You are an expert hiking trip planner. Create a JSON array of planning tasks.
52+
53+
IMPORTANT: Return ONLY the JSON array, no other text or explanations.
54+
55+
Create 3-5 specific planning tasks. Each task should be actionable and focused.
56+
57+
Example response:
58+
["Research trail routes and difficulty levels", "Find accommodation near trailheads",
59+
"Analyze weather and gear needs"]
60+
"""
61+
62+
user_prompt = """
63+
Create planning tasks for:
64+
- {{ duration_days }}-day hiking trip in {{ location }}
65+
- {{ group_size }} {{ skill_level }} hikers
66+
- Season: {{ season }}
67+
- Preferences: {{ preferences }}
68+
69+
Return only the JSON array of task descriptions.
70+
"""
71+
72+
73+
class SummaryPrompt(Prompt[HikingPlanInput]):
74+
"""Prompt for creating final comprehensive summary."""
75+
76+
system_prompt = """
77+
You are an expert hiking guide creating a comprehensive trip plan summary.
78+
Based on your planning analysis, create a complete, actionable hiking trip plan
79+
that covers all essential aspects of the adventure.
80+
81+
Structure your response clearly with specific recommendations and practical details.
82+
"""
83+
84+
user_prompt = """
85+
Create a comprehensive hiking trip plan for:
86+
- {{ duration_days }}-day trip in {{ location }}
87+
- {{ group_size }} {{ skill_level }} hikers
88+
- Season: {{ season }}
89+
- Preferences: {{ preferences }}
90+
91+
Provide a complete plan including:
92+
• Trail routes and difficulty assessments
93+
• Accommodation recommendations near trailheads
94+
• Detailed budget breakdown with costs
95+
• Weather analysis and gear recommendations
96+
• Daily schedule with sunrise/sunset times
97+
• Post-hike activities and relaxation options
98+
• Safety protocols and emergency contacts
99+
100+
Make this a step-by-step plan someone could follow immediately.
101+
"""
102+
103+
104+
def clear_console() -> None:
105+
"""Clear the console screen."""
106+
os.system("clear" if os.name == "posix" else "cls") # noqa: S605
107+
108+
109+
def display_task_list(agent: TodoAgent, title: str = "Planning Progress") -> None:
110+
"""Display current task list with visual status indicators."""
111+
clear_console()
112+
print("🏔️ AI Hiking Trip Planner")
113+
print("=" * 50)
114+
print(f"\n📋 {title}")
115+
print("─" * 30)
116+
117+
for task in agent.tasks:
118+
if task.status.value == "pending":
119+
status_icon = "⏳"
120+
elif task.status.value == "in-progress":
121+
status_icon = "🔄"
122+
else: # done
123+
status_icon = "✅"
124+
125+
print(f" {status_icon} {task.description}")
126+
127+
print(f"\n📊 Progress: {len(agent.done_tasks)}/{len(agent.tasks)} completed")
128+
129+
130+
async def smart_hiking_planner() -> None:
131+
"""Smart hiking planner that processes tasks under the hood and streams final summary."""
132+
llm = LiteLLM(model_name="gpt-4o-mini")
133+
134+
clear_console()
135+
print("🏔️ Smart AI Hiking Trip Planner")
136+
print("=" * 50)
137+
138+
# Trip configuration
139+
trip_input = HikingPlanInput(
140+
location="Tatra Mountains - Poland",
141+
duration_days=1,
142+
group_size=2,
143+
skill_level="intermediate",
144+
season="late September/early October",
145+
preferences="scenic views, no longer than 15km, no crowds, avoid Morskie Oko/Giewont/Kasprowy Wierch",
146+
)
147+
148+
print("\n📋 Trip Configuration:")
149+
print(f" 📍 Location: {trip_input.location}")
150+
print(f" 📅 Duration: {trip_input.duration_days} day(s)")
151+
print(f" 👥 Group: {trip_input.group_size} {trip_input.skill_level} hikers")
152+
print(f" 🌤️ Season: {trip_input.season}")
153+
print(f" ⭐ Preferences: {trip_input.preferences}")
154+
155+
await asyncio.sleep(2)
156+
157+
# AI creates planning tasks
158+
print("\n🤖 AI analyzing trip requirements and generating tasks...")
159+
160+
# Create task generation agent
161+
task_generator = TodoAgent(llm=llm, prompt=TaskGenerationPrompt)
162+
163+
# Generate tasks with AI
164+
task_response = await task_generator.run(trip_input)
165+
166+
# Parse AI-generated tasks with robust error handling
167+
try:
168+
# Extract response content - AgentResult has .content attribute
169+
if hasattr(task_response, "content"):
170+
response_content = task_response.content.strip()
171+
else:
172+
response_content = str(task_response).strip()
173+
174+
# Multiple strategies to extract JSON
175+
import re
176+
177+
# Strategy 1: Try direct JSON parsing
178+
try:
179+
planning_tasks = json.loads(response_content)
180+
except json.JSONDecodeError:
181+
# Strategy 2: Extract JSON array from text
182+
json_patterns = [
183+
r"\[(?:[^[\]]*(?:\[[^\]]*\])*)*\]", # Complex nested array pattern
184+
r"\[.*?\]", # Simple array pattern
185+
]
186+
187+
planning_tasks = None
188+
for pattern in json_patterns:
189+
json_match = re.search(pattern, response_content, re.DOTALL)
190+
if json_match:
191+
json_str = json_match.group(0)
192+
try:
193+
planning_tasks = json.loads(json_str)
194+
break
195+
except json.JSONDecodeError:
196+
continue
197+
198+
if planning_tasks is None:
199+
raise ValueError("No valid JSON array found in response") from None
200+
201+
# Validate it's a list of strings
202+
if not isinstance(planning_tasks, list):
203+
raise ValueError(f"Expected list, got {type(planning_tasks)}")
204+
205+
if not all(isinstance(task, str) for task in planning_tasks):
206+
raise ValueError("All tasks must be strings")
207+
208+
if len(planning_tasks) == 0:
209+
raise ValueError("No tasks generated")
210+
211+
except Exception as e:
212+
print(f"⚠️ AI task generation failed ({e}), using fallback tasks...")
213+
planning_tasks = [
214+
"Research optimal trail routes and scenic viewpoints",
215+
"Find accommodation close to trailheads",
216+
"Analyze weather conditions and gear requirements",
217+
"Plan daily schedule with sunrise/sunset times",
218+
"Research post-hike activities and relaxation spots",
219+
]
220+
221+
# Create planning agent
222+
planning_agent = TodoAgent(llm=llm, prompt=PlanningPrompt, keep_history=True)
223+
planning_agent.create_todo_list(planning_tasks)
224+
225+
await asyncio.sleep(1)
226+
display_task_list(planning_agent, "AI-Generated Planning Tasks")
227+
await asyncio.sleep(2)
228+
229+
print("\n🚀 Processing tasks under the hood...")
230+
await asyncio.sleep(1)
231+
232+
# Process all tasks under the hood (user sees progress, not details)
233+
for task in planning_agent.tasks:
234+
# Show current progress
235+
planning_agent.mark_task(task.id, "in-progress")
236+
display_task_list(planning_agent, f"Processing: {task.description[:40]}...")
237+
238+
# Process task with AI (under the hood)
239+
await planning_agent.run(trip_input)
240+
241+
# Mark as completed
242+
planning_agent.mark_task(task.id, "done")
243+
await asyncio.sleep(1)
244+
245+
# Show all tasks completed
246+
display_task_list(planning_agent, "All Planning Tasks Completed!")
247+
await asyncio.sleep(2)
248+
249+
# Generate and stream comprehensive summary
250+
print("\n🎯 Generating comprehensive trip plan...")
251+
print("🤖 AI compiling all research into actionable plan...")
252+
await asyncio.sleep(2)
253+
254+
# Create summary agent with all gathered information
255+
summary_agent = TodoAgent(llm=llm, prompt=SummaryPrompt)
256+
257+
# Clear screen for final summary
258+
clear_console()
259+
print(f"{'='*60}")
260+
print("🏔️ COMPLETE HIKING TRIP PLAN")
261+
print(f"{'='*60}")
262+
print()
263+
264+
# Stream the comprehensive summary
265+
streaming_result = summary_agent.run_streaming(trip_input)
266+
267+
async for chunk in streaming_result:
268+
if isinstance(chunk, str):
269+
print(chunk, end="", flush=True)
270+
271+
print(f"\n\n{'='*60}")
272+
print("✅ Trip planning completed!")
273+
print(f"📊 Agent processed {len(planning_agent.tasks)} planning tasks")
274+
print("🎉 Ready for your adventure!")
275+
276+
277+
async def main() -> None:
278+
"""Run the smart hiking planner."""
279+
print("🚀 Smart AI Hiking Trip Planner")
280+
print("AI processes planning tasks under the hood, then streams comprehensive plan")
281+
print("\nPress Ctrl+C to exit...")
282+
await asyncio.sleep(3)
283+
284+
try:
285+
await smart_hiking_planner()
286+
print("\n✨ Planning completed successfully!")
287+
288+
except KeyboardInterrupt:
289+
print("\n👋 Planning interrupted")
290+
except Exception as e:
291+
print(f"\n❌ Error: {e}")
292+
print("Make sure OPENAI_API_KEY is set")
293+
294+
295+
if __name__ == "__main__":
296+
asyncio.run(main())

packages/ragbits-agents/src/ragbits/agents/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
AgentRunContext,
88
ToolCallResult,
99
)
10+
from ragbits.agents.todo_agent import Task, TaskStatus, TodoAgent
1011
from ragbits.agents.types import QuestionAnswerAgent, QuestionAnswerPromptInput, QuestionAnswerPromptOutput
1112

1213
__all__ = [
@@ -19,5 +20,8 @@
1920
"QuestionAnswerAgent",
2021
"QuestionAnswerPromptInput",
2122
"QuestionAnswerPromptOutput",
23+
"Task",
24+
"TaskStatus",
25+
"TodoAgent",
2226
"ToolCallResult",
2327
]

0 commit comments

Comments
 (0)