mbxai 2.2.0__py3-none-any.whl → 2.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mbxai/__init__.py +23 -2
- mbxai/agent/__init__.py +13 -2
- mbxai/agent/client.py +840 -629
- mbxai/agent/client_legacy.py +804 -0
- mbxai/agent/models.py +264 -31
- mbxai/examples/enhanced_agent_example.py +344 -0
- mbxai/examples/redis_session_handler_example.py +248 -0
- mbxai/mcp/server.py +1 -1
- mbxai-2.3.1.dist-info/METADATA +1191 -0
- {mbxai-2.2.0.dist-info → mbxai-2.3.1.dist-info}/RECORD +12 -9
- mbxai-2.2.0.dist-info/METADATA +0 -492
- {mbxai-2.2.0.dist-info → mbxai-2.3.1.dist-info}/WHEEL +0 -0
- {mbxai-2.2.0.dist-info → mbxai-2.3.1.dist-info}/licenses/LICENSE +0 -0
mbxai/agent/models.py
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
Pydantic models for the agent client.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from typing import Any, Optional
|
5
|
+
from typing import Any, Optional, Union, Callable, Protocol, Dict
|
6
6
|
from pydantic import BaseModel, Field, field_validator
|
7
|
+
from enum import Enum
|
8
|
+
from abc import ABC, abstractmethod
|
7
9
|
import uuid
|
8
10
|
import re
|
9
11
|
|
@@ -41,20 +43,6 @@ class Result(BaseModel):
|
|
41
43
|
result: str = Field(description="The result text from the AI")
|
42
44
|
|
43
45
|
|
44
|
-
class AgentResponse(BaseModel):
|
45
|
-
"""Response from the agent that can contain questions or a final result."""
|
46
|
-
agent_id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique identifier for this agent session")
|
47
|
-
questions: list[Question] = Field(default_factory=list, description="List of questions for the user")
|
48
|
-
final_response: Optional[Any] = Field(default=None, description="The final response if processing is complete")
|
49
|
-
token_summary: Optional["TokenSummary"] = Field(default=None, description="Summary of token usage for this agent process")
|
50
|
-
|
51
|
-
def has_questions(self) -> bool:
|
52
|
-
"""Check if this response has questions that need to be answered."""
|
53
|
-
return len(self.questions) > 0
|
54
|
-
|
55
|
-
def is_complete(self) -> bool:
|
56
|
-
"""Check if this response contains a final result."""
|
57
|
-
return self.final_response is not None
|
58
46
|
|
59
47
|
|
60
48
|
class QuestionList(BaseModel):
|
@@ -88,20 +76,24 @@ class TokenUsage(BaseModel):
|
|
88
76
|
|
89
77
|
class TokenSummary(BaseModel):
|
90
78
|
"""Summary of token usage across all API calls in an agent process."""
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
79
|
+
requirement_analysis: TokenUsage = Field(default_factory=TokenUsage, description="Tokens used for requirement analysis")
|
80
|
+
tool_analysis: TokenUsage = Field(default_factory=TokenUsage, description="Tokens used for tool analysis")
|
81
|
+
todo_generation: TokenUsage = Field(default_factory=TokenUsage, description="Tokens used for todo list generation")
|
82
|
+
task_execution: list[TokenUsage] = Field(default_factory=list, description="Tokens used for each task execution")
|
83
|
+
dialog_interactions: list[TokenUsage] = Field(default_factory=list, description="Tokens used for human-in-the-loop interactions")
|
84
|
+
goal_evaluation: TokenUsage = Field(default_factory=TokenUsage, description="Tokens used for goal evaluation")
|
95
85
|
final_response: TokenUsage = Field(default_factory=TokenUsage, description="Tokens used for final response generation")
|
96
86
|
|
97
87
|
@property
|
98
88
|
def total_tokens(self) -> int:
|
99
89
|
"""Calculate total tokens used across all operations."""
|
100
90
|
total = (
|
101
|
-
self.
|
102
|
-
self.
|
103
|
-
|
104
|
-
sum(usage.total_tokens for usage in self.
|
91
|
+
self.requirement_analysis.total_tokens +
|
92
|
+
self.tool_analysis.total_tokens +
|
93
|
+
self.todo_generation.total_tokens +
|
94
|
+
sum(usage.total_tokens for usage in self.task_execution) +
|
95
|
+
sum(usage.total_tokens for usage in self.dialog_interactions) +
|
96
|
+
self.goal_evaluation.total_tokens +
|
105
97
|
self.final_response.total_tokens
|
106
98
|
)
|
107
99
|
return total
|
@@ -110,10 +102,12 @@ class TokenSummary(BaseModel):
|
|
110
102
|
def total_prompt_tokens(self) -> int:
|
111
103
|
"""Calculate total prompt tokens used across all operations."""
|
112
104
|
total = (
|
113
|
-
self.
|
114
|
-
self.
|
115
|
-
|
116
|
-
sum(usage.prompt_tokens for usage in self.
|
105
|
+
self.requirement_analysis.prompt_tokens +
|
106
|
+
self.tool_analysis.prompt_tokens +
|
107
|
+
self.todo_generation.prompt_tokens +
|
108
|
+
sum(usage.prompt_tokens for usage in self.task_execution) +
|
109
|
+
sum(usage.prompt_tokens for usage in self.dialog_interactions) +
|
110
|
+
self.goal_evaluation.prompt_tokens +
|
117
111
|
self.final_response.prompt_tokens
|
118
112
|
)
|
119
113
|
return total
|
@@ -122,10 +116,249 @@ class TokenSummary(BaseModel):
|
|
122
116
|
def total_completion_tokens(self) -> int:
|
123
117
|
"""Calculate total completion tokens used across all operations."""
|
124
118
|
total = (
|
125
|
-
self.
|
126
|
-
self.
|
127
|
-
|
128
|
-
sum(usage.completion_tokens for usage in self.
|
119
|
+
self.requirement_analysis.completion_tokens +
|
120
|
+
self.tool_analysis.completion_tokens +
|
121
|
+
self.todo_generation.completion_tokens +
|
122
|
+
sum(usage.completion_tokens for usage in self.task_execution) +
|
123
|
+
sum(usage.completion_tokens for usage in self.dialog_interactions) +
|
124
|
+
self.goal_evaluation.completion_tokens +
|
129
125
|
self.final_response.completion_tokens
|
130
126
|
)
|
131
127
|
return total
|
128
|
+
|
129
|
+
|
130
|
+
# New models for the enhanced agent architecture
|
131
|
+
|
132
|
+
class TaskStatus(str, Enum):
|
133
|
+
"""Status of a task in the todo list."""
|
134
|
+
PENDING = "pending"
|
135
|
+
IN_PROGRESS = "in_progress"
|
136
|
+
COMPLETED = "completed"
|
137
|
+
FAILED = "failed"
|
138
|
+
SKIPPED = "skipped"
|
139
|
+
|
140
|
+
|
141
|
+
class Task(BaseModel):
|
142
|
+
"""A task in the agent's todo list."""
|
143
|
+
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique identifier for the task")
|
144
|
+
title: str = Field(description="Short title of the task")
|
145
|
+
description: str = Field(description="Detailed description of what needs to be done")
|
146
|
+
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Current status of the task")
|
147
|
+
dependencies: list[str] = Field(default_factory=list, description="List of task IDs that must be completed before this task")
|
148
|
+
tools_needed: list[str] = Field(default_factory=list, description="List of tool names needed for this task")
|
149
|
+
estimated_complexity: int = Field(default=1, ge=1, le=5, description="Complexity rating from 1-5")
|
150
|
+
result: Optional[str] = Field(default=None, description="Result of the task execution")
|
151
|
+
error_message: Optional[str] = Field(default=None, description="Error message if task failed")
|
152
|
+
|
153
|
+
|
154
|
+
class TodoList(BaseModel):
|
155
|
+
"""A list of tasks to complete the user's goal."""
|
156
|
+
tasks: list[Task] = Field(description="List of tasks to complete")
|
157
|
+
estimated_total_time: Optional[str] = Field(default=None, description="Estimated total time to complete all tasks")
|
158
|
+
|
159
|
+
def get_next_task(self) -> Optional[Task]:
|
160
|
+
"""Get the next pending task that has all dependencies completed."""
|
161
|
+
completed_task_ids = {task.id for task in self.tasks if task.status == TaskStatus.COMPLETED}
|
162
|
+
|
163
|
+
for task in self.tasks:
|
164
|
+
if task.status == TaskStatus.PENDING:
|
165
|
+
# Check if all dependencies are completed
|
166
|
+
if all(dep_id in completed_task_ids for dep_id in task.dependencies):
|
167
|
+
return task
|
168
|
+
return None
|
169
|
+
|
170
|
+
def get_task_by_id(self, task_id: str) -> Optional[Task]:
|
171
|
+
"""Get a task by its ID."""
|
172
|
+
for task in self.tasks:
|
173
|
+
if task.id == task_id:
|
174
|
+
return task
|
175
|
+
return None
|
176
|
+
|
177
|
+
|
178
|
+
class HumanInteractionType(str, Enum):
|
179
|
+
"""Type of human interaction needed."""
|
180
|
+
DECISION = "decision"
|
181
|
+
QUESTION = "question"
|
182
|
+
DIALOG_OPTION = "dialog_option"
|
183
|
+
|
184
|
+
|
185
|
+
class DialogOption(BaseModel):
|
186
|
+
"""A dialog option for structured human-in-the-loop communication.
|
187
|
+
|
188
|
+
Dialog options are UI-aware patterns that tell the frontend how to handle
|
189
|
+
specific interactions (authentication, approvals, integrations, etc.).
|
190
|
+
They are NOT functions executed by the agent - they're instructions for the UI.
|
191
|
+
"""
|
192
|
+
id: str = Field(description="Unique identifier for the dialog option")
|
193
|
+
title: str = Field(description="Short title of the dialog option")
|
194
|
+
description: str = Field(description="Description of what this option does")
|
195
|
+
function: Optional[Callable[..., Any]] = Field(default=None, description="Optional function for backwards compatibility - prefer UI handling")
|
196
|
+
parameters: dict[str, Any] = Field(default_factory=dict, description="Parameters to help the UI handle this dialog (URLs, scopes, etc.)")
|
197
|
+
|
198
|
+
class Config:
|
199
|
+
arbitrary_types_allowed = True
|
200
|
+
|
201
|
+
|
202
|
+
class HumanInLoopRequest(BaseModel):
|
203
|
+
"""A request for human interaction."""
|
204
|
+
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique identifier for this interaction")
|
205
|
+
interaction_type: HumanInteractionType = Field(description="Type of interaction needed")
|
206
|
+
prompt: str = Field(description="The prompt/question for the human")
|
207
|
+
options: list[str] = Field(default_factory=list, description="Available options for decisions")
|
208
|
+
dialog_options: list[DialogOption] = Field(default_factory=list, description="Available dialog options")
|
209
|
+
context: str = Field(default="", description="Additional context for the interaction")
|
210
|
+
required: bool = Field(default=True, description="Whether this interaction is required")
|
211
|
+
|
212
|
+
|
213
|
+
class HumanInLoopResponse(BaseModel):
|
214
|
+
"""Response from human interaction."""
|
215
|
+
interaction_id: str = Field(description="ID of the interaction being responded to")
|
216
|
+
response_type: HumanInteractionType = Field(description="Type of response")
|
217
|
+
decision: Optional[str] = Field(default=None, description="Selected decision option")
|
218
|
+
answer: Optional[str] = Field(default=None, description="Answer to a question")
|
219
|
+
dialog_option_id: Optional[str] = Field(default=None, description="Selected dialog option ID")
|
220
|
+
additional_context: str = Field(default="", description="Additional context from the user")
|
221
|
+
|
222
|
+
|
223
|
+
class HumanInLoopResponseBatch(BaseModel):
|
224
|
+
"""Batch of human responses for multiple interactions."""
|
225
|
+
responses: list[HumanInLoopResponse] = Field(description="List of human responses")
|
226
|
+
|
227
|
+
def get_response_by_id(self, interaction_id: str) -> Optional[HumanInLoopResponse]:
|
228
|
+
"""Get a response by interaction ID."""
|
229
|
+
for response in self.responses:
|
230
|
+
if response.interaction_id == interaction_id:
|
231
|
+
return response
|
232
|
+
return None
|
233
|
+
|
234
|
+
|
235
|
+
# Session Handler Interface
|
236
|
+
class SessionHandler(Protocol):
|
237
|
+
"""Protocol for custom session storage implementations."""
|
238
|
+
|
239
|
+
def get_session(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
240
|
+
"""Retrieve a session by agent ID."""
|
241
|
+
...
|
242
|
+
|
243
|
+
def set_session(self, agent_id: str, session_data: Dict[str, Any]) -> None:
|
244
|
+
"""Store session data for an agent ID."""
|
245
|
+
...
|
246
|
+
|
247
|
+
def delete_session(self, agent_id: str) -> bool:
|
248
|
+
"""Delete a session by agent ID. Returns True if deleted, False if not found."""
|
249
|
+
...
|
250
|
+
|
251
|
+
def list_sessions(self) -> list[str]:
|
252
|
+
"""List all active session IDs."""
|
253
|
+
...
|
254
|
+
|
255
|
+
def session_exists(self, agent_id: str) -> bool:
|
256
|
+
"""Check if a session exists."""
|
257
|
+
...
|
258
|
+
|
259
|
+
|
260
|
+
class InMemorySessionHandler:
|
261
|
+
"""Default in-memory session handler implementation."""
|
262
|
+
|
263
|
+
def __init__(self):
|
264
|
+
self._sessions: Dict[str, Dict[str, Any]] = {}
|
265
|
+
|
266
|
+
def get_session(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
267
|
+
"""Retrieve a session by agent ID."""
|
268
|
+
return self._sessions.get(agent_id)
|
269
|
+
|
270
|
+
def set_session(self, agent_id: str, session_data: Dict[str, Any]) -> None:
|
271
|
+
"""Store session data for an agent ID."""
|
272
|
+
self._sessions[agent_id] = session_data
|
273
|
+
|
274
|
+
def delete_session(self, agent_id: str) -> bool:
|
275
|
+
"""Delete a session by agent ID. Returns True if deleted, False if not found."""
|
276
|
+
if agent_id in self._sessions:
|
277
|
+
del self._sessions[agent_id]
|
278
|
+
return True
|
279
|
+
return False
|
280
|
+
|
281
|
+
def list_sessions(self) -> list[str]:
|
282
|
+
"""List all active session IDs."""
|
283
|
+
return list(self._sessions.keys())
|
284
|
+
|
285
|
+
def session_exists(self, agent_id: str) -> bool:
|
286
|
+
"""Check if a session exists."""
|
287
|
+
return agent_id in self._sessions
|
288
|
+
|
289
|
+
|
290
|
+
class RequirementAnalysis(BaseModel):
|
291
|
+
"""Analysis of the user's requirement/goal."""
|
292
|
+
goal: str = Field(description="The main goal the user wants to achieve")
|
293
|
+
sub_goals: list[str] = Field(default_factory=list, description="Sub-goals that contribute to the main goal")
|
294
|
+
success_criteria: list[str] = Field(description="Criteria to determine if the goal is achieved")
|
295
|
+
constraints: list[str] = Field(default_factory=list, description="Any constraints or limitations to consider")
|
296
|
+
complexity_estimate: int = Field(ge=1, le=10, description="Complexity estimate from 1-10")
|
297
|
+
|
298
|
+
|
299
|
+
class ToolAnalysis(BaseModel):
|
300
|
+
"""Analysis of available tools and their relevance to the goal."""
|
301
|
+
relevant_tools: list[str] = Field(description="List of tool names relevant to the goal")
|
302
|
+
tool_mapping: dict[str, str] = Field(description="Mapping of tool names to their purpose for this goal")
|
303
|
+
missing_capabilities: list[str] = Field(default_factory=list, description="Capabilities needed but not available in tools")
|
304
|
+
|
305
|
+
|
306
|
+
class GoalEvaluation(BaseModel):
|
307
|
+
"""Evaluation of whether the goal has been achieved."""
|
308
|
+
goal_achieved: bool = Field(description="Whether the main goal has been achieved")
|
309
|
+
completion_percentage: int = Field(ge=0, le=100, description="Percentage of goal completion")
|
310
|
+
completed_criteria: list[str] = Field(description="Success criteria that have been met")
|
311
|
+
remaining_criteria: list[str] = Field(description="Success criteria that still need to be met")
|
312
|
+
feedback: str = Field(description="Detailed feedback on the goal achievement")
|
313
|
+
next_steps: list[str] = Field(default_factory=list, description="Next steps if goal is not fully achieved")
|
314
|
+
|
315
|
+
|
316
|
+
class AgentState(str, Enum):
|
317
|
+
"""Current state of the agent."""
|
318
|
+
ANALYZING_REQUIREMENT = "analyzing_requirement"
|
319
|
+
ANALYZING_TOOLS = "analyzing_tools"
|
320
|
+
GENERATING_TODO = "generating_todo"
|
321
|
+
EXECUTING_TASKS = "executing_tasks"
|
322
|
+
WAITING_FOR_HUMAN = "waiting_for_human"
|
323
|
+
EVALUATING_GOAL = "evaluating_goal"
|
324
|
+
COMPLETED = "completed"
|
325
|
+
FAILED = "failed"
|
326
|
+
|
327
|
+
|
328
|
+
class AgentResponse(BaseModel):
|
329
|
+
"""Response from the agent that can contain various states and information."""
|
330
|
+
agent_id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique identifier for this agent session")
|
331
|
+
state: AgentState = Field(description="Current state of the agent")
|
332
|
+
|
333
|
+
# Legacy support for existing questions-based flow
|
334
|
+
questions: list[Question] = Field(default_factory=list, description="List of questions for the user (legacy)")
|
335
|
+
|
336
|
+
# New architecture fields
|
337
|
+
requirement_analysis: Optional[RequirementAnalysis] = Field(default=None, description="Analysis of the requirement")
|
338
|
+
tool_analysis: Optional[ToolAnalysis] = Field(default=None, description="Analysis of available tools")
|
339
|
+
todo_list: Optional[TodoList] = Field(default=None, description="Current todo list")
|
340
|
+
current_task: Optional[Task] = Field(default=None, description="Currently executing task")
|
341
|
+
human_interaction_request: Optional[HumanInLoopRequest] = Field(default=None, description="Request for human interaction")
|
342
|
+
goal_evaluation: Optional[GoalEvaluation] = Field(default=None, description="Goal achievement evaluation")
|
343
|
+
|
344
|
+
# Final response
|
345
|
+
final_response: Optional[Any] = Field(default=None, description="The final response if processing is complete")
|
346
|
+
|
347
|
+
# Token usage tracking
|
348
|
+
token_summary: Optional[TokenSummary] = Field(default=None, description="Summary of token usage for this agent process")
|
349
|
+
|
350
|
+
def has_questions(self) -> bool:
|
351
|
+
"""Check if this response has questions that need to be answered (legacy)."""
|
352
|
+
return len(self.questions) > 0
|
353
|
+
|
354
|
+
def needs_human_interaction(self) -> bool:
|
355
|
+
"""Check if this response needs human interaction."""
|
356
|
+
return self.human_interaction_request is not None
|
357
|
+
|
358
|
+
def is_complete(self) -> bool:
|
359
|
+
"""Check if this response contains a final result."""
|
360
|
+
return self.state == AgentState.COMPLETED and self.final_response is not None
|
361
|
+
|
362
|
+
def is_waiting_for_human(self) -> bool:
|
363
|
+
"""Check if the agent is waiting for human input."""
|
364
|
+
return self.state == AgentState.WAITING_FOR_HUMAN
|
@@ -0,0 +1,344 @@
|
|
1
|
+
"""
|
2
|
+
Example demonstrating the enhanced Agent Client with the new 6-step process.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
from pydantic import BaseModel, Field
|
7
|
+
from mbxai import AgentClient, OpenRouterClient, ToolClient
|
8
|
+
from mbxai.agent.models import (
|
9
|
+
DialogOption, HumanInLoopResponse, HumanInteractionType,
|
10
|
+
TaskStatus, AgentState
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
class ProjectPlan(BaseModel):
|
15
|
+
"""A project plan response."""
|
16
|
+
project_name: str = Field(description="Name of the project")
|
17
|
+
description: str = Field(description="Project description")
|
18
|
+
technologies: list[str] = Field(description="Technologies to be used")
|
19
|
+
phases: list[str] = Field(description="Project phases")
|
20
|
+
estimated_duration: str = Field(description="Estimated project duration")
|
21
|
+
deliverables: list[str] = Field(description="Key deliverables")
|
22
|
+
risks: list[str] = Field(description="Potential risks and mitigation strategies")
|
23
|
+
|
24
|
+
|
25
|
+
class WeatherInfo(BaseModel):
|
26
|
+
"""Weather information response."""
|
27
|
+
location: str = Field(description="The location")
|
28
|
+
current_weather: str = Field(description="Current weather description")
|
29
|
+
temperature: str = Field(description="Current temperature")
|
30
|
+
recommendations: list[str] = Field(description="Recommendations based on weather")
|
31
|
+
|
32
|
+
|
33
|
+
def google_auth_dialog_option() -> str:
|
34
|
+
"""Simulate Google authentication."""
|
35
|
+
print("🔐 Simulating Google Authentication...")
|
36
|
+
return "Successfully authenticated with Google"
|
37
|
+
|
38
|
+
|
39
|
+
def slack_integration_dialog_option() -> str:
|
40
|
+
"""Simulate Slack integration setup."""
|
41
|
+
print("💬 Simulating Slack integration setup...")
|
42
|
+
return "Slack integration configured successfully"
|
43
|
+
|
44
|
+
|
45
|
+
def example_basic_agent():
|
46
|
+
"""Example of basic agent usage without human-in-the-loop."""
|
47
|
+
print("=== Basic Agent Example ===")
|
48
|
+
|
49
|
+
# Initialize client
|
50
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY", "demo-token"))
|
51
|
+
agent = AgentClient(openrouter_client, human_in_loop=False)
|
52
|
+
|
53
|
+
# Create a project plan
|
54
|
+
prompt = "Create a project plan for building a simple weather app using React and Node.js"
|
55
|
+
|
56
|
+
print(f"🚀 Starting agent with prompt: {prompt}")
|
57
|
+
response = agent.agent(prompt, final_response_structure=ProjectPlan)
|
58
|
+
|
59
|
+
print(f"📊 Agent state: {response.state}")
|
60
|
+
|
61
|
+
if response.requirement_analysis:
|
62
|
+
print(f"📋 Goal: {response.requirement_analysis.goal}")
|
63
|
+
print(f"📋 Complexity: {response.requirement_analysis.complexity_estimate}/10")
|
64
|
+
|
65
|
+
if response.todo_list:
|
66
|
+
print(f"📝 Generated {len(response.todo_list.tasks)} tasks:")
|
67
|
+
for task in response.todo_list.tasks:
|
68
|
+
print(f" - {task.title} (Status: {task.status.value})")
|
69
|
+
|
70
|
+
if response.is_complete():
|
71
|
+
plan = response.final_response
|
72
|
+
print(f"\n✅ Project Plan Generated:")
|
73
|
+
print(f"📦 Project: {plan.project_name}")
|
74
|
+
print(f"📄 Description: {plan.description}")
|
75
|
+
print(f"💻 Technologies: {', '.join(plan.technologies)}")
|
76
|
+
print(f"⏱️ Duration: {plan.estimated_duration}")
|
77
|
+
print(f"📋 Phases: {', '.join(plan.phases)}")
|
78
|
+
|
79
|
+
# Print token usage
|
80
|
+
if response.token_summary:
|
81
|
+
print(f"\n📊 Token usage: {response.token_summary.total_tokens} total")
|
82
|
+
|
83
|
+
return response.agent_id
|
84
|
+
|
85
|
+
|
86
|
+
def example_human_in_loop():
|
87
|
+
"""Example of agent with human-in-the-loop interactions."""
|
88
|
+
print("\n=== Human-in-the-Loop Agent Example ===")
|
89
|
+
|
90
|
+
# Initialize client with dialog options
|
91
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY", "demo-token"))
|
92
|
+
|
93
|
+
dialog_options = [
|
94
|
+
DialogOption(
|
95
|
+
id="google_auth",
|
96
|
+
title="Google Authentication",
|
97
|
+
description="Authenticate with Google services",
|
98
|
+
function=google_auth_dialog_option
|
99
|
+
),
|
100
|
+
DialogOption(
|
101
|
+
id="slack_integration",
|
102
|
+
title="Slack Integration",
|
103
|
+
description="Set up Slack integration",
|
104
|
+
function=slack_integration_dialog_option
|
105
|
+
)
|
106
|
+
]
|
107
|
+
|
108
|
+
agent = AgentClient(
|
109
|
+
openrouter_client,
|
110
|
+
human_in_loop=True,
|
111
|
+
dialog_options=dialog_options,
|
112
|
+
max_task_iterations=5
|
113
|
+
)
|
114
|
+
|
115
|
+
# Start with a complex task
|
116
|
+
prompt = "Set up a complete CI/CD pipeline for a web application with automated testing, deployment, and monitoring"
|
117
|
+
|
118
|
+
print(f"🚀 Starting agent with human-in-loop: {prompt}")
|
119
|
+
response = agent.agent(prompt, final_response_structure=ProjectPlan)
|
120
|
+
|
121
|
+
print(f"📊 Agent state: {response.state}")
|
122
|
+
|
123
|
+
# Simulate human interactions
|
124
|
+
iteration = 0
|
125
|
+
max_iterations = 3
|
126
|
+
|
127
|
+
while not response.is_complete() and iteration < max_iterations:
|
128
|
+
iteration += 1
|
129
|
+
print(f"\n--- Iteration {iteration} ---")
|
130
|
+
|
131
|
+
if response.needs_human_interaction():
|
132
|
+
request = response.human_interaction_request
|
133
|
+
print(f"👤 Human interaction needed: {request.interaction_type.value}")
|
134
|
+
print(f"💬 Prompt: {request.prompt}")
|
135
|
+
|
136
|
+
if request.interaction_type == HumanInteractionType.DECISION:
|
137
|
+
print(f"📋 Options: {', '.join(request.options)}")
|
138
|
+
# Simulate user choosing to proceed
|
139
|
+
human_response = HumanInLoopResponse(
|
140
|
+
interaction_id=request.id,
|
141
|
+
response_type=HumanInteractionType.DECISION,
|
142
|
+
decision="proceed",
|
143
|
+
additional_context="User decided to proceed with the current approach"
|
144
|
+
)
|
145
|
+
elif request.interaction_type == HumanInteractionType.QUESTION:
|
146
|
+
# Simulate user answering a question
|
147
|
+
human_response = HumanInLoopResponse(
|
148
|
+
interaction_id=request.id,
|
149
|
+
response_type=HumanInteractionType.QUESTION,
|
150
|
+
answer="Use Docker for containerization and AWS for cloud services",
|
151
|
+
additional_context="User prefers cloud-native solutions"
|
152
|
+
)
|
153
|
+
elif request.interaction_type == HumanInteractionType.DIALOG_OPTION:
|
154
|
+
# Simulate user selecting a dialog option
|
155
|
+
print(f"🔧 Available dialog options:")
|
156
|
+
for option in request.dialog_options:
|
157
|
+
print(f" - {option.title}: {option.description}")
|
158
|
+
|
159
|
+
selected_option = request.dialog_options[0] if request.dialog_options else None
|
160
|
+
human_response = HumanInLoopResponse(
|
161
|
+
interaction_id=request.id,
|
162
|
+
response_type=HumanInteractionType.DIALOG_OPTION,
|
163
|
+
dialog_option_id=selected_option.id if selected_option else "none",
|
164
|
+
additional_context="User selected the first option"
|
165
|
+
)
|
166
|
+
else:
|
167
|
+
# Default response
|
168
|
+
human_response = HumanInLoopResponse(
|
169
|
+
interaction_id=request.id,
|
170
|
+
response_type=HumanInteractionType.QUESTION,
|
171
|
+
answer="Continue with default settings"
|
172
|
+
)
|
173
|
+
|
174
|
+
print(f"💭 Simulated user response: {human_response.answer or human_response.decision or 'dialog option selected'}")
|
175
|
+
|
176
|
+
# Continue with human response
|
177
|
+
response = agent.agent(
|
178
|
+
prompt="Continue with user input",
|
179
|
+
final_response_structure=ProjectPlan,
|
180
|
+
agent_id=response.agent_id,
|
181
|
+
human_response=human_response
|
182
|
+
)
|
183
|
+
|
184
|
+
elif response.state == AgentState.EXECUTING_TASKS:
|
185
|
+
print(f"⚡ Agent is executing tasks...")
|
186
|
+
if response.current_task:
|
187
|
+
print(f"📋 Current task: {response.current_task.title}")
|
188
|
+
|
189
|
+
# Continue execution
|
190
|
+
response = agent.agent(
|
191
|
+
prompt="Continue execution",
|
192
|
+
final_response_structure=ProjectPlan,
|
193
|
+
agent_id=response.agent_id
|
194
|
+
)
|
195
|
+
|
196
|
+
else:
|
197
|
+
print(f"📊 Agent state: {response.state}")
|
198
|
+
break
|
199
|
+
|
200
|
+
if response.is_complete():
|
201
|
+
plan = response.final_response
|
202
|
+
print(f"\n✅ CI/CD Plan Generated:")
|
203
|
+
print(f"📦 Project: {plan.project_name}")
|
204
|
+
print(f"📄 Description: {plan.description}")
|
205
|
+
print(f"💻 Technologies: {', '.join(plan.technologies)}")
|
206
|
+
|
207
|
+
if response.goal_evaluation:
|
208
|
+
print(f"\n🎯 Goal Achievement: {response.goal_evaluation.completion_percentage}%")
|
209
|
+
print(f"💭 Feedback: {response.goal_evaluation.feedback}")
|
210
|
+
|
211
|
+
return response.agent_id
|
212
|
+
|
213
|
+
|
214
|
+
def example_with_tools():
|
215
|
+
"""Example of agent with tools."""
|
216
|
+
print("\n=== Agent with Tools Example ===")
|
217
|
+
|
218
|
+
# Initialize tool client
|
219
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY", "demo-token"))
|
220
|
+
tool_client = ToolClient(openrouter_client)
|
221
|
+
|
222
|
+
# Register a weather tool
|
223
|
+
def get_weather(location: str) -> str:
|
224
|
+
"""Get current weather for a location."""
|
225
|
+
# This is a mock implementation
|
226
|
+
return f"The weather in {location} is sunny and 72°F with light winds"
|
227
|
+
|
228
|
+
def get_forecast(location: str, days: int = 5) -> str:
|
229
|
+
"""Get weather forecast for a location."""
|
230
|
+
return f"5-day forecast for {location}: Mostly sunny with temperatures ranging from 68-75°F"
|
231
|
+
|
232
|
+
agent = AgentClient(tool_client, human_in_loop=False)
|
233
|
+
|
234
|
+
# Register tools
|
235
|
+
agent.register_tool(
|
236
|
+
name="get_weather",
|
237
|
+
description="Get current weather information for a specific location",
|
238
|
+
function=get_weather
|
239
|
+
)
|
240
|
+
|
241
|
+
agent.register_tool(
|
242
|
+
name="get_forecast",
|
243
|
+
description="Get weather forecast for a location",
|
244
|
+
function=get_forecast
|
245
|
+
)
|
246
|
+
|
247
|
+
# Ask for weather information
|
248
|
+
prompt = "I'm planning a picnic in San Francisco this weekend. Can you help me understand the weather and give recommendations?"
|
249
|
+
|
250
|
+
print(f"🚀 Starting agent with tools: {prompt}")
|
251
|
+
response = agent.agent(prompt, final_response_structure=WeatherInfo)
|
252
|
+
|
253
|
+
print(f"📊 Agent state: {response.state}")
|
254
|
+
|
255
|
+
if response.tool_analysis:
|
256
|
+
print(f"🔧 Relevant tools: {', '.join(response.tool_analysis.relevant_tools)}")
|
257
|
+
|
258
|
+
if response.is_complete():
|
259
|
+
weather = response.final_response
|
260
|
+
print(f"\n🌤️ Weather Information:")
|
261
|
+
print(f"📍 Location: {weather.location}")
|
262
|
+
print(f"🌡️ Current: {weather.current_weather}")
|
263
|
+
print(f"🌡️ Temperature: {weather.temperature}")
|
264
|
+
print(f"💡 Recommendations:")
|
265
|
+
for rec in weather.recommendations:
|
266
|
+
print(f" - {rec}")
|
267
|
+
|
268
|
+
return response.agent_id
|
269
|
+
|
270
|
+
|
271
|
+
def example_session_management():
|
272
|
+
"""Example of session management and continuation."""
|
273
|
+
print("\n=== Session Management Example ===")
|
274
|
+
|
275
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY", "demo-token"))
|
276
|
+
agent = AgentClient(openrouter_client, human_in_loop=False)
|
277
|
+
|
278
|
+
# Start first task
|
279
|
+
prompt1 = "Create a basic project structure for a Python web API"
|
280
|
+
response1 = agent.agent(prompt1, final_response_structure=ProjectPlan)
|
281
|
+
agent_id = response1.agent_id
|
282
|
+
|
283
|
+
print(f"📋 First task completed: {response1.is_complete()}")
|
284
|
+
|
285
|
+
if response1.is_complete():
|
286
|
+
plan1 = response1.final_response
|
287
|
+
print(f"📦 First project: {plan1.project_name}")
|
288
|
+
|
289
|
+
# Continue with related task
|
290
|
+
prompt2 = "Now add authentication and user management to this API project"
|
291
|
+
response2 = agent.agent(prompt2, final_response_structure=ProjectPlan, agent_id=agent_id)
|
292
|
+
|
293
|
+
print(f"📋 Second task completed: {response2.is_complete()}")
|
294
|
+
|
295
|
+
if response2.is_complete():
|
296
|
+
plan2 = response2.final_response
|
297
|
+
print(f"📦 Enhanced project: {plan2.project_name}")
|
298
|
+
print(f"🔐 New features: {', '.join(plan2.technologies)}")
|
299
|
+
|
300
|
+
# List sessions
|
301
|
+
sessions = agent.list_sessions()
|
302
|
+
print(f"📊 Active sessions: {len(sessions)}")
|
303
|
+
|
304
|
+
# Get session info
|
305
|
+
if agent_id in sessions:
|
306
|
+
session_info = agent.get_session_info(agent_id)
|
307
|
+
print(f"💬 Conversation length: {session_info.get('conversation_length', 0)} messages")
|
308
|
+
|
309
|
+
# Clean up
|
310
|
+
deleted = agent.delete_session(agent_id)
|
311
|
+
print(f"🗑️ Session deleted: {deleted}")
|
312
|
+
|
313
|
+
return agent_id
|
314
|
+
|
315
|
+
|
316
|
+
def main():
|
317
|
+
"""Run all examples."""
|
318
|
+
print("🤖 Enhanced Agent Client Examples")
|
319
|
+
print("=" * 50)
|
320
|
+
|
321
|
+
try:
|
322
|
+
# Basic agent without human interaction
|
323
|
+
basic_id = example_basic_agent()
|
324
|
+
|
325
|
+
# Agent with human-in-the-loop
|
326
|
+
hitl_id = example_human_in_loop()
|
327
|
+
|
328
|
+
# Agent with tools
|
329
|
+
tools_id = example_with_tools()
|
330
|
+
|
331
|
+
# Session management
|
332
|
+
session_id = example_session_management()
|
333
|
+
|
334
|
+
print(f"\n✅ All examples completed successfully!")
|
335
|
+
print(f"📋 Generated agent IDs: {basic_id}, {hitl_id}, {tools_id}, {session_id}")
|
336
|
+
|
337
|
+
except Exception as e:
|
338
|
+
print(f"❌ Error running examples: {e}")
|
339
|
+
import traceback
|
340
|
+
traceback.print_exc()
|
341
|
+
|
342
|
+
|
343
|
+
if __name__ == "__main__":
|
344
|
+
main()
|