mbxai 2.2.0__tar.gz → 2.3.1__tar.gz
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-2.3.1/PKG-INFO +1191 -0
- mbxai-2.3.1/README.md +1156 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/pyproject.toml +1 -1
- {mbxai-2.2.0 → mbxai-2.3.1}/setup.py +1 -1
- mbxai-2.3.1/src/mbxai/__init__.py +43 -0
- mbxai-2.3.1/src/mbxai/agent/__init__.py +19 -0
- mbxai-2.3.1/src/mbxai/agent/client.py +1015 -0
- mbxai-2.3.1/src/mbxai/agent/models.py +364 -0
- mbxai-2.3.1/src/mbxai/examples/enhanced_agent_example.py +344 -0
- mbxai-2.3.1/src/mbxai/examples/redis_session_handler_example.py +248 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/mcp/server.py +1 -1
- mbxai-2.3.1/test_enhanced_agent.py +259 -0
- mbxai-2.2.0/PKG-INFO +0 -492
- mbxai-2.2.0/README.md +0 -457
- mbxai-2.2.0/src/mbxai/__init__.py +0 -22
- mbxai-2.2.0/src/mbxai/agent/__init__.py +0 -8
- mbxai-2.2.0/src/mbxai/agent/models.py +0 -131
- {mbxai-2.2.0 → mbxai-2.3.1}/.gitignore +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/LICENSE +0 -0
- /mbxai-2.2.0/src/mbxai/agent/client.py → /mbxai-2.3.1/src/mbxai/agent/client_legacy.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/core.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/agent_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/agent_iterations_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/agent_logging_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/agent_tool_registration_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/agent_validation_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/auto_schema_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/conversation_history_test.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/dialog_agent_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/mcp/mcp_client_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/mcp/mcp_server_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/openrouter_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/optional_prompt_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/parse_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/parse_tool_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/request.json +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/response.json +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/send_request.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/simple_agent_test.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/tool_client_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/examples/unified_interface_example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/mcp/__init__.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/mcp/client.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/mcp/example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/openrouter/__init__.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/openrouter/client.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/openrouter/config.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/openrouter/models.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/openrouter/schema.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/tools/__init__.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/tools/client.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/tools/example.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/src/mbxai/tools/types.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/tests/test_mcp_tool_registration.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/tests/test_real_mcp_schema.py +0 -0
- {mbxai-2.2.0 → mbxai-2.3.1}/tests/test_schema_conversion.py +0 -0
mbxai-2.3.1/PKG-INFO
ADDED
@@ -0,0 +1,1191 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: mbxai
|
3
|
+
Version: 2.3.1
|
4
|
+
Summary: MBX AI SDK
|
5
|
+
Project-URL: Homepage, https://www.mibexx.de
|
6
|
+
Project-URL: Documentation, https://www.mibexx.de
|
7
|
+
Project-URL: Repository, https://github.com/yourusername/mbxai.git
|
8
|
+
Author: MBX AI
|
9
|
+
License: MIT
|
10
|
+
License-File: LICENSE
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Classifier: Programming Language :: Python
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
|
+
Requires-Python: >=3.12
|
16
|
+
Requires-Dist: fastapi>=0.115.12
|
17
|
+
Requires-Dist: httpx>=0.27.0
|
18
|
+
Requires-Dist: mcp>=1.7.1
|
19
|
+
Requires-Dist: openai>=1.77.0
|
20
|
+
Requires-Dist: pydantic-settings>=2.9.1
|
21
|
+
Requires-Dist: pydantic>=2.9.1
|
22
|
+
Requires-Dist: python-multipart>=0.0.20
|
23
|
+
Requires-Dist: sse-starlette>=2.3.4
|
24
|
+
Requires-Dist: starlette>=0.46.2
|
25
|
+
Requires-Dist: typing-inspection<=0.4.0
|
26
|
+
Requires-Dist: uvicorn>=0.34.2
|
27
|
+
Provides-Extra: dev
|
28
|
+
Requires-Dist: black>=24.3.0; extra == 'dev'
|
29
|
+
Requires-Dist: isort>=5.13.2; extra == 'dev'
|
30
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
31
|
+
Requires-Dist: pytest-asyncio>=0.26.0; extra == 'dev'
|
32
|
+
Requires-Dist: pytest-cov>=6.1.1; extra == 'dev'
|
33
|
+
Requires-Dist: pytest>=8.3.5; extra == 'dev'
|
34
|
+
Description-Content-Type: text/markdown
|
35
|
+
|
36
|
+
# MBX AI
|
37
|
+
|
38
|
+
A comprehensive Python library for building intelligent AI applications with Large Language Models (LLMs), structured responses, tool integration, and agent-based thinking.
|
39
|
+
|
40
|
+
## 🚀 Features
|
41
|
+
|
42
|
+
- **🔗 Multiple AI Client Types**: OpenRouter integration with tool-enabled and MCP-enabled variants
|
43
|
+
- **🤖 Enhanced Agent System**: 6-step intelligent process with human-in-the-loop, task management, and goal evaluation
|
44
|
+
- **💾 Pluggable Session Storage**: Custom session handlers (Redis, Database, File System) for scalable, distributed sessions
|
45
|
+
- **🛠️ Tool Integration**: Easy function registration with automatic schema generation
|
46
|
+
- **🔌 MCP Support**: Full Model Context Protocol (MCP) client and server implementation
|
47
|
+
- **📋 Structured Responses**: Type-safe responses using Pydantic models
|
48
|
+
- **🔄 Quality Iteration**: Built-in response improvement through AI-powered quality checks
|
49
|
+
- **💬 Conversation Memory**: Persistent dialog sessions with history management
|
50
|
+
- **⚡ Automatic Retry**: Built-in retry logic with exponential backoff for robust connections
|
51
|
+
|
52
|
+
## 📦 Installation
|
53
|
+
|
54
|
+
```bash
|
55
|
+
pip install mbxai
|
56
|
+
```
|
57
|
+
|
58
|
+
## 🏗️ Architecture Overview
|
59
|
+
|
60
|
+
MBX AI provides four main client types, each building upon the previous:
|
61
|
+
|
62
|
+
1. **OpenRouterClient** - Basic LLM interactions with structured responses
|
63
|
+
2. **ToolClient** - Adds function calling capabilities
|
64
|
+
3. **MCPClient** - Adds Model Context Protocol server integration
|
65
|
+
4. **AgentClient** - Adds intelligent dialog-based thinking (wraps any of the above)
|
66
|
+
|
67
|
+
| Client | Structured Responses | Function Calling | MCP Integration | Agent Thinking |
|
68
|
+
|--------|---------------------|------------------|-----------------|----------------|
|
69
|
+
| OpenRouterClient | ✅ | ❌ | ❌ | ❌ |
|
70
|
+
| ToolClient | ✅ | ✅ | ❌ | ❌ |
|
71
|
+
| MCPClient | ✅ | ✅ | ✅ | ❌ |
|
72
|
+
| AgentClient | ✅ | ✅* | ✅* | ✅ |
|
73
|
+
|
74
|
+
*AgentClient capabilities depend on the wrapped client
|
75
|
+
|
76
|
+
## 🚀 Quick Start
|
77
|
+
|
78
|
+
### Basic OpenRouter Client
|
79
|
+
|
80
|
+
```python
|
81
|
+
import os
|
82
|
+
from mbxai import OpenRouterClient
|
83
|
+
from pydantic import BaseModel, Field
|
84
|
+
|
85
|
+
# Initialize client
|
86
|
+
client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY"))
|
87
|
+
|
88
|
+
# Simple chat
|
89
|
+
response = client.create([
|
90
|
+
{"role": "user", "content": "What is the capital of France?"}
|
91
|
+
])
|
92
|
+
print(response.choices[0].message.content)
|
93
|
+
|
94
|
+
# Structured response
|
95
|
+
class CityInfo(BaseModel):
|
96
|
+
name: str = Field(description="City name")
|
97
|
+
population: int = Field(description="Population count")
|
98
|
+
country: str = Field(description="Country name")
|
99
|
+
|
100
|
+
response = client.parse(
|
101
|
+
messages=[{"role": "user", "content": "Tell me about Paris"}],
|
102
|
+
response_format=CityInfo
|
103
|
+
)
|
104
|
+
city = response.choices[0].message.parsed
|
105
|
+
print(f"{city.name}, {city.country} - Population: {city.population:,}")
|
106
|
+
```
|
107
|
+
|
108
|
+
### Tool Client with Automatic Schema Generation
|
109
|
+
|
110
|
+
```python
|
111
|
+
import os
|
112
|
+
from mbxai import ToolClient, OpenRouterClient
|
113
|
+
|
114
|
+
# Initialize clients
|
115
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY"))
|
116
|
+
tool_client = ToolClient(openrouter_client)
|
117
|
+
|
118
|
+
# Define a function - schema is auto-generated!
|
119
|
+
def get_weather(location: str, unit: str = "celsius") -> dict:
|
120
|
+
"""Get weather information for a location.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
location: The city or location name
|
124
|
+
unit: Temperature unit (celsius or fahrenheit)
|
125
|
+
"""
|
126
|
+
return {
|
127
|
+
"location": location,
|
128
|
+
"temperature": 22,
|
129
|
+
"unit": unit,
|
130
|
+
"condition": "Sunny"
|
131
|
+
}
|
132
|
+
|
133
|
+
# Register tool (schema automatically generated from function signature)
|
134
|
+
tool_client.register_tool(
|
135
|
+
name="get_weather",
|
136
|
+
description="Get current weather for a location",
|
137
|
+
function=get_weather
|
138
|
+
# No schema needed - automatically generated!
|
139
|
+
)
|
140
|
+
|
141
|
+
# Use the tool
|
142
|
+
response = tool_client.chat([
|
143
|
+
{"role": "user", "content": "What's the weather like in Tokyo?"}
|
144
|
+
])
|
145
|
+
print(response.choices[0].message.content)
|
146
|
+
```
|
147
|
+
|
148
|
+
### MCP Client for Server Integration
|
149
|
+
|
150
|
+
```python
|
151
|
+
import os
|
152
|
+
from mbxai import MCPClient, OpenRouterClient
|
153
|
+
|
154
|
+
# Initialize MCP client
|
155
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY"))
|
156
|
+
mcp_client = MCPClient(openrouter_client)
|
157
|
+
|
158
|
+
# Register MCP server (automatically loads all tools)
|
159
|
+
mcp_client.register_mcp_server("data-analysis", "http://localhost:8000")
|
160
|
+
|
161
|
+
# Chat with MCP tools available
|
162
|
+
response = mcp_client.chat([
|
163
|
+
{"role": "user", "content": "Analyze the sales data from the server"}
|
164
|
+
])
|
165
|
+
print(response.choices[0].message.content)
|
166
|
+
```
|
167
|
+
|
168
|
+
### Enhanced Agent Client - 6-Step Intelligent Process
|
169
|
+
|
170
|
+
The AgentClient provides a structured 6-step process: requirement analysis, tool analysis, todo generation, task execution, human-in-the-loop interactions, and goal evaluation.
|
171
|
+
|
172
|
+
```python
|
173
|
+
import os
|
174
|
+
from mbxai import AgentClient, OpenRouterClient
|
175
|
+
from mbxai.agent.models import DialogOption, HumanInLoopResponse, HumanInteractionType
|
176
|
+
from pydantic import BaseModel, Field
|
177
|
+
|
178
|
+
class ProjectPlan(BaseModel):
|
179
|
+
project_name: str = Field(description="Name of the project")
|
180
|
+
technologies: list[str] = Field(description="Technologies to be used")
|
181
|
+
phases: list[str] = Field(description="Project phases")
|
182
|
+
estimated_duration: str = Field(description="Estimated duration")
|
183
|
+
|
184
|
+
# Basic usage without human-in-the-loop
|
185
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY"))
|
186
|
+
agent = AgentClient(openrouter_client, human_in_loop=False)
|
187
|
+
|
188
|
+
response = agent.agent(
|
189
|
+
"Create a project plan for a React web application",
|
190
|
+
final_response_structure=ProjectPlan
|
191
|
+
)
|
192
|
+
|
193
|
+
print(f"Agent State: {response.state}")
|
194
|
+
if response.requirement_analysis:
|
195
|
+
print(f"Goal: {response.requirement_analysis.goal}")
|
196
|
+
print(f"Complexity: {response.requirement_analysis.complexity_estimate}/10")
|
197
|
+
|
198
|
+
if response.todo_list:
|
199
|
+
print(f"Generated {len(response.todo_list.tasks)} tasks:")
|
200
|
+
for task in response.todo_list.tasks:
|
201
|
+
print(f" - {task.title} (Status: {task.status.value})")
|
202
|
+
|
203
|
+
if response.is_complete():
|
204
|
+
plan = response.final_response
|
205
|
+
print(f"Project: {plan.project_name}")
|
206
|
+
print(f"Technologies: {', '.join(plan.technologies)}")
|
207
|
+
```
|
208
|
+
|
209
|
+
### Human-in-the-Loop Agent
|
210
|
+
|
211
|
+
```python
|
212
|
+
# Define dialog options for UI-aware authentication flows
|
213
|
+
# These don't execute functions directly - they provide structured
|
214
|
+
# communication patterns that the UI can handle
|
215
|
+
|
216
|
+
dialog_options = [
|
217
|
+
DialogOption(
|
218
|
+
id="jira_auth",
|
219
|
+
title="Jira Authentication",
|
220
|
+
description="Authenticate with Atlassian Jira",
|
221
|
+
# No function - this is handled by the UI
|
222
|
+
parameters={"jira_url": "https://company.atlassian.net"}
|
223
|
+
),
|
224
|
+
DialogOption(
|
225
|
+
id="github_auth",
|
226
|
+
title="GitHub Authentication",
|
227
|
+
description="Authenticate with GitHub",
|
228
|
+
parameters={"scopes": ["repo", "user"]}
|
229
|
+
),
|
230
|
+
DialogOption(
|
231
|
+
id="approval_request",
|
232
|
+
title="Deployment Approval",
|
233
|
+
description="Request approval for production deployment",
|
234
|
+
parameters={"environment": "production", "risk_level": "medium"}
|
235
|
+
)
|
236
|
+
]
|
237
|
+
|
238
|
+
# Initialize agent with human-in-the-loop
|
239
|
+
agent = AgentClient(
|
240
|
+
openrouter_client,
|
241
|
+
human_in_loop=True,
|
242
|
+
dialog_options=dialog_options,
|
243
|
+
max_task_iterations=10
|
244
|
+
)
|
245
|
+
|
246
|
+
# Start task that requires authentication
|
247
|
+
response = agent.agent(
|
248
|
+
"Create a summary of the Jira story PROJ-123 from https://company.atlassian.net",
|
249
|
+
final_response_structure=ProjectPlan
|
250
|
+
)
|
251
|
+
|
252
|
+
# Handle human interactions
|
253
|
+
while not response.is_complete():
|
254
|
+
if response.needs_human_interaction():
|
255
|
+
request = response.human_interaction_request
|
256
|
+
print(f"Human input needed: {request.prompt}")
|
257
|
+
|
258
|
+
if request.interaction_type == HumanInteractionType.DECISION:
|
259
|
+
# Present options and get user choice
|
260
|
+
print(f"Options: {', '.join(request.options)}")
|
261
|
+
user_choice = "proceed" # Simulate user input
|
262
|
+
|
263
|
+
human_response = HumanInLoopResponse(
|
264
|
+
interaction_id=request.id,
|
265
|
+
response_type=HumanInteractionType.DECISION,
|
266
|
+
decision=user_choice
|
267
|
+
)
|
268
|
+
|
269
|
+
elif request.interaction_type == HumanInteractionType.QUESTION:
|
270
|
+
# Get user answer
|
271
|
+
user_answer = "Use AWS for deployment" # Simulate user input
|
272
|
+
|
273
|
+
human_response = HumanInLoopResponse(
|
274
|
+
interaction_id=request.id,
|
275
|
+
response_type=HumanInteractionType.QUESTION,
|
276
|
+
answer=user_answer
|
277
|
+
)
|
278
|
+
|
279
|
+
elif request.interaction_type == HumanInteractionType.DIALOG_OPTION:
|
280
|
+
# UI presents structured authentication dialog
|
281
|
+
# User sees "Login to Atlassian" button, completes OAuth flow
|
282
|
+
# UI receives auth token and sends it back to agent
|
283
|
+
|
284
|
+
# Simulate UI completing authentication
|
285
|
+
auth_token = "oauth_token_abc123" # Retrieved by UI from OAuth flow
|
286
|
+
|
287
|
+
human_response = HumanInLoopResponse(
|
288
|
+
interaction_id=request.id,
|
289
|
+
response_type=HumanInteractionType.DIALOG_OPTION,
|
290
|
+
dialog_option_id="jira_auth",
|
291
|
+
additional_context=f"auth_token:{auth_token}"
|
292
|
+
)
|
293
|
+
|
294
|
+
# Continue with human response
|
295
|
+
response = agent.agent(
|
296
|
+
"Continue with user input",
|
297
|
+
final_response_structure=ProjectPlan,
|
298
|
+
agent_id=response.agent_id,
|
299
|
+
human_response=human_response
|
300
|
+
)
|
301
|
+
else:
|
302
|
+
# Continue execution
|
303
|
+
response = agent.agent(
|
304
|
+
"Continue processing",
|
305
|
+
final_response_structure=ProjectPlan,
|
306
|
+
agent_id=response.agent_id
|
307
|
+
)
|
308
|
+
|
309
|
+
# Final result
|
310
|
+
if response.is_complete():
|
311
|
+
plan = response.final_response
|
312
|
+
print(f"✅ Project completed: {plan.project_name}")
|
313
|
+
|
314
|
+
if response.goal_evaluation:
|
315
|
+
print(f"Goal Achievement: {response.goal_evaluation.completion_percentage}%")
|
316
|
+
print(f"Feedback: {response.goal_evaluation.feedback}")
|
317
|
+
```
|
318
|
+
|
319
|
+
### Agent with Tool Integration
|
320
|
+
|
321
|
+
```python
|
322
|
+
from mbxai import AgentClient, ToolClient, OpenRouterClient
|
323
|
+
|
324
|
+
# Setup tool-enabled agent
|
325
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY"))
|
326
|
+
tool_client = ToolClient(openrouter_client)
|
327
|
+
agent = AgentClient(tool_client)
|
328
|
+
|
329
|
+
# Register tools via agent (proxy method)
|
330
|
+
def search_flights(origin: str, destination: str, date: str) -> dict:
|
331
|
+
"""Search for flights between cities."""
|
332
|
+
return {
|
333
|
+
"flights": [
|
334
|
+
{"airline": "Example Air", "price": "$450", "duration": "3h 15m"}
|
335
|
+
]
|
336
|
+
}
|
337
|
+
|
338
|
+
agent.register_tool(
|
339
|
+
name="search_flights",
|
340
|
+
description="Search for flights between cities",
|
341
|
+
function=search_flights
|
342
|
+
)
|
343
|
+
|
344
|
+
# Agent automatically uses tools when needed
|
345
|
+
class FlightInfo(BaseModel):
|
346
|
+
flights: list[dict] = Field(description="Available flights")
|
347
|
+
recommendation: str = Field(description="Flight recommendation")
|
348
|
+
|
349
|
+
response = agent.agent(
|
350
|
+
prompt="Find flights from New York to Los Angeles for tomorrow",
|
351
|
+
final_response_structure=FlightInfo,
|
352
|
+
ask_questions=False
|
353
|
+
)
|
354
|
+
|
355
|
+
flight_info = response.final_response
|
356
|
+
print(f"Found {len(flight_info.flights)} flights")
|
357
|
+
print(f"Recommendation: {flight_info.recommendation}")
|
358
|
+
```
|
359
|
+
|
360
|
+
### New Models and Types
|
361
|
+
|
362
|
+
The enhanced agent client introduces several new models and types:
|
363
|
+
|
364
|
+
```python
|
365
|
+
# Import new agent models
|
366
|
+
from mbxai.agent.models import (
|
367
|
+
Task, TodoList, TaskStatus, # Task management
|
368
|
+
DialogOption, HumanInLoopRequest, # Human interactions
|
369
|
+
HumanInLoopResponse, HumanInteractionType,
|
370
|
+
RequirementAnalysis, ToolAnalysis, # Analysis models
|
371
|
+
GoalEvaluation, AgentState, # State management
|
372
|
+
SessionHandler, InMemorySessionHandler, # Session storage
|
373
|
+
TokenSummary, TokenUsage # Token tracking
|
374
|
+
)
|
375
|
+
|
376
|
+
# Or import from main package
|
377
|
+
from mbxai import (
|
378
|
+
Task, TodoList, DialogOption, HumanInLoopRequest,
|
379
|
+
AgentState, TaskStatus, HumanInteractionType,
|
380
|
+
SessionHandler, InMemorySessionHandler
|
381
|
+
)
|
382
|
+
```
|
383
|
+
|
384
|
+
## 📚 Detailed Documentation
|
385
|
+
|
386
|
+
### OpenRouterClient
|
387
|
+
|
388
|
+
The base client for OpenRouter API integration with structured response support.
|
389
|
+
|
390
|
+
#### Key Features:
|
391
|
+
- **Multiple Models**: Support for GPT-4, Claude, Llama, and other models via OpenRouter
|
392
|
+
- **Structured Responses**: Type-safe responses using Pydantic models
|
393
|
+
- **Retry Logic**: Automatic retry with exponential backoff
|
394
|
+
- **Error Handling**: Comprehensive error handling with detailed logging
|
395
|
+
|
396
|
+
#### Methods:
|
397
|
+
- `create()` - Basic chat completion
|
398
|
+
- `parse()` - Chat completion with structured response
|
399
|
+
|
400
|
+
#### Configuration:
|
401
|
+
```python
|
402
|
+
client = OpenRouterClient(
|
403
|
+
token="your-api-key",
|
404
|
+
model="openai/gpt-4-turbo", # or use OpenRouterModel enum
|
405
|
+
max_retries=3,
|
406
|
+
retry_initial_delay=1.0,
|
407
|
+
retry_max_delay=10.0
|
408
|
+
)
|
409
|
+
```
|
410
|
+
|
411
|
+
### ToolClient
|
412
|
+
|
413
|
+
Extends OpenRouterClient with function calling capabilities.
|
414
|
+
|
415
|
+
#### Key Features:
|
416
|
+
- **Automatic Schema Generation**: Generate JSON schemas from Python function signatures
|
417
|
+
- **Tool Registration**: Simple function registration
|
418
|
+
- **Tool Execution**: Automatic tool calling and response handling
|
419
|
+
- **Error Recovery**: Graceful handling of tool execution errors
|
420
|
+
|
421
|
+
#### Usage:
|
422
|
+
```python
|
423
|
+
tool_client = ToolClient(openrouter_client)
|
424
|
+
|
425
|
+
# Register with automatic schema
|
426
|
+
tool_client.register_tool("function_name", "description", function)
|
427
|
+
|
428
|
+
# Register with custom schema
|
429
|
+
tool_client.register_tool("function_name", "description", function, custom_schema)
|
430
|
+
```
|
431
|
+
|
432
|
+
### MCPClient
|
433
|
+
|
434
|
+
Extends ToolClient with Model Context Protocol (MCP) server integration.
|
435
|
+
|
436
|
+
#### Key Features:
|
437
|
+
- **MCP Server Integration**: Connect to MCP servers and load their tools
|
438
|
+
- **Tool Discovery**: Automatically discover and register tools from MCP servers
|
439
|
+
- **HTTP Client Management**: Built-in HTTP client for MCP communication
|
440
|
+
- **Schema Conversion**: Convert MCP schemas to OpenAI function format
|
441
|
+
|
442
|
+
#### Usage:
|
443
|
+
```python
|
444
|
+
mcp_client = MCPClient(openrouter_client)
|
445
|
+
mcp_client.register_mcp_server("server-name", "http://localhost:8000")
|
446
|
+
```
|
447
|
+
|
448
|
+
### Enhanced AgentClient
|
449
|
+
|
450
|
+
Wraps any client with a structured 6-step intelligent process and human-in-the-loop capabilities.
|
451
|
+
|
452
|
+
#### Key Features:
|
453
|
+
- **6-Step Process**: Requirement analysis → Tool analysis → Todo generation → Task execution → Human interaction → Goal evaluation
|
454
|
+
- **Human-in-the-Loop**: Support for decisions, questions, and custom dialog options
|
455
|
+
- **Task Management**: Intelligent todo list generation with dependencies and complexity assessment
|
456
|
+
- **Goal Evaluation**: Automatic assessment of goal achievement with feedback
|
457
|
+
- **Conversation Memory**: Maintains full conversation history and context
|
458
|
+
- **Tool Integration**: Seamlessly works with any underlying client (OpenRouter, Tool, MCP)
|
459
|
+
|
460
|
+
#### Configuration Options:
|
461
|
+
```python
|
462
|
+
from mbxai import AgentClient, InMemorySessionHandler
|
463
|
+
|
464
|
+
agent = AgentClient(
|
465
|
+
ai_client=any_supported_client,
|
466
|
+
human_in_loop=False, # Enable human-in-the-loop
|
467
|
+
dialog_options=[], # Custom dialog options
|
468
|
+
max_task_iterations=10, # Max task execution cycles
|
469
|
+
session_handler=InMemorySessionHandler() # Session storage (optional)
|
470
|
+
)
|
471
|
+
```
|
472
|
+
|
473
|
+
**Session Storage Options:**
|
474
|
+
- **InMemorySessionHandler** (default): Single-instance memory storage
|
475
|
+
- **Custom handlers**: Redis, Database, File System for distributed/persistent sessions
|
476
|
+
|
477
|
+
#### The 6-Step Process:
|
478
|
+
1. **Requirement Analysis**: Understand goals, success criteria, constraints, and complexity
|
479
|
+
2. **Tool Analysis**: Map available tools to goals, identify missing capabilities
|
480
|
+
3. **Todo Generation**: Create specific, actionable tasks with dependencies
|
481
|
+
4. **Task Execution**: Execute tasks step-by-step with status tracking
|
482
|
+
5. **Human Interaction**: Dialog for decisions, questions, or custom actions (if enabled)
|
483
|
+
6. **Goal Evaluation**: Assess achievement, provide feedback, generate new todos if needed
|
484
|
+
|
485
|
+
#### Human Interaction Types:
|
486
|
+
- **DECISION**: Multiple choice with predefined options
|
487
|
+
- **QUESTION**: Free-text input for clarification
|
488
|
+
- **DIALOG_OPTION**: Structured UI-aware communication patterns (authentication, integrations, etc.)
|
489
|
+
|
490
|
+
#### Tools vs Dialog Options:
|
491
|
+
- **Tools**: Functions the agent executes directly (fetch data, generate documents, setup systems)
|
492
|
+
- **Dialog Options**: Structured human-in-the-loop patterns that the UI can handle (authentication flows, approvals, integrations)
|
493
|
+
|
494
|
+
#### New Response Properties:
|
495
|
+
```python
|
496
|
+
response = agent.agent("Create a web app", ProjectPlan)
|
497
|
+
|
498
|
+
# Enhanced state information
|
499
|
+
response.state # Current agent state
|
500
|
+
response.requirement_analysis # Goal breakdown
|
501
|
+
response.tool_analysis # Tool mapping
|
502
|
+
response.todo_list # Generated tasks
|
503
|
+
response.current_task # Currently executing task
|
504
|
+
response.human_interaction_request # Human input needed
|
505
|
+
response.goal_evaluation # Achievement assessment
|
506
|
+
|
507
|
+
# Check states
|
508
|
+
response.is_complete() # Final response ready
|
509
|
+
response.needs_human_interaction() # Human input required
|
510
|
+
response.is_waiting_for_human() # Waiting for human response
|
511
|
+
```
|
512
|
+
|
513
|
+
#### Session Management:
|
514
|
+
```python
|
515
|
+
# List active sessions
|
516
|
+
sessions = agent.list_sessions()
|
517
|
+
|
518
|
+
# Get session info
|
519
|
+
info = agent.get_session_info(agent_id)
|
520
|
+
|
521
|
+
# Delete session
|
522
|
+
agent.delete_session(agent_id)
|
523
|
+
```
|
524
|
+
|
525
|
+
## 🏃♂️ Advanced Examples
|
526
|
+
|
527
|
+
### Custom Model Registration
|
528
|
+
|
529
|
+
```python
|
530
|
+
from mbxai import OpenRouterClient, OpenRouterModel
|
531
|
+
|
532
|
+
# Register custom model
|
533
|
+
OpenRouterClient.register_model("CUSTOM_MODEL", "provider/model-name")
|
534
|
+
|
535
|
+
# Use custom model
|
536
|
+
client = OpenRouterClient(token="your-key", model="CUSTOM_MODEL")
|
537
|
+
```
|
538
|
+
|
539
|
+
### Enhanced Agent Session Continuation
|
540
|
+
|
541
|
+
```python
|
542
|
+
# Start a complex project
|
543
|
+
response1 = agent.agent(
|
544
|
+
"Create a microservices architecture for an e-commerce platform",
|
545
|
+
final_response_structure=ProjectPlan
|
546
|
+
)
|
547
|
+
agent_id = response1.agent_id
|
548
|
+
|
549
|
+
# Continue with additional requirements
|
550
|
+
response2 = agent.agent(
|
551
|
+
"Add authentication and payment processing to the architecture",
|
552
|
+
final_response_structure=ProjectPlan,
|
553
|
+
agent_id=agent_id # Continues previous session
|
554
|
+
)
|
555
|
+
|
556
|
+
# The agent remembers the full context and builds upon previous work
|
557
|
+
print(f"Updated project: {response2.final_response.project_name}")
|
558
|
+
print(f"Goal achievement: {response2.goal_evaluation.completion_percentage}%")
|
559
|
+
```
|
560
|
+
|
561
|
+
### Complete Example: Jira Integration with Authentication
|
562
|
+
|
563
|
+
```python
|
564
|
+
from mbxai import AgentClient, ToolClient, OpenRouterClient
|
565
|
+
from mbxai.agent.models import DialogOption, HumanInLoopResponse, HumanInteractionType
|
566
|
+
from pydantic import BaseModel, Field
|
567
|
+
|
568
|
+
class JiraSummary(BaseModel):
|
569
|
+
story_id: str = Field(description="Jira story ID")
|
570
|
+
title: str = Field(description="Story title")
|
571
|
+
description: str = Field(description="Story description")
|
572
|
+
status: str = Field(description="Current status")
|
573
|
+
assignee: str = Field(description="Assigned person")
|
574
|
+
summary: str = Field(description="AI-generated summary")
|
575
|
+
|
576
|
+
# Tool for fetching Jira data (agent executes this)
|
577
|
+
def fetch_jira_story(story_id: str, jira_url: str, auth_token: str) -> dict:
|
578
|
+
"""Fetch a Jira story using the API."""
|
579
|
+
# Your Jira API integration logic here
|
580
|
+
return {
|
581
|
+
"id": story_id,
|
582
|
+
"title": "Implement user authentication",
|
583
|
+
"description": "Add OAuth2 authentication to the application",
|
584
|
+
"status": "In Progress",
|
585
|
+
"assignee": "john.doe@company.com"
|
586
|
+
}
|
587
|
+
|
588
|
+
# Dialog option for authentication (UI handles this)
|
589
|
+
dialog_options = [
|
590
|
+
DialogOption(
|
591
|
+
id="jira_auth",
|
592
|
+
title="Jira Authentication",
|
593
|
+
description="Authenticate with Atlassian Jira",
|
594
|
+
# No function - this tells the UI to handle authentication
|
595
|
+
parameters={
|
596
|
+
"jira_url": "https://company.atlassian.net",
|
597
|
+
"scopes": ["read:jira-work", "read:jira-user"]
|
598
|
+
}
|
599
|
+
)
|
600
|
+
]
|
601
|
+
|
602
|
+
# Setup agent with both tools and dialog options
|
603
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY"))
|
604
|
+
tool_client = ToolClient(openrouter_client)
|
605
|
+
agent = AgentClient(
|
606
|
+
tool_client,
|
607
|
+
human_in_loop=True,
|
608
|
+
dialog_options=dialog_options
|
609
|
+
)
|
610
|
+
|
611
|
+
# Register the Jira tool
|
612
|
+
agent.register_tool(
|
613
|
+
name="fetch_jira_story",
|
614
|
+
description="Fetch Jira story details using API",
|
615
|
+
function=fetch_jira_story
|
616
|
+
)
|
617
|
+
|
618
|
+
# Agent process
|
619
|
+
response = agent.agent(
|
620
|
+
"Create a summary of Jira story PROJ-123 from https://company.atlassian.net",
|
621
|
+
final_response_structure=JiraSummary
|
622
|
+
)
|
623
|
+
|
624
|
+
# Handle authentication dialog
|
625
|
+
if response.needs_human_interaction():
|
626
|
+
request = response.human_interaction_request
|
627
|
+
|
628
|
+
if request.interaction_type == HumanInteractionType.DIALOG_OPTION:
|
629
|
+
# UI shows "Login to Atlassian" button
|
630
|
+
# User completes OAuth flow
|
631
|
+
# UI receives auth token and sends it back
|
632
|
+
|
633
|
+
auth_token = "oauth_token_from_ui" # Provided by UI after OAuth
|
634
|
+
|
635
|
+
human_response = HumanInLoopResponse(
|
636
|
+
interaction_id=request.id,
|
637
|
+
response_type=HumanInteractionType.DIALOG_OPTION,
|
638
|
+
dialog_option_id="jira_auth",
|
639
|
+
additional_context=f"auth_token:{auth_token}"
|
640
|
+
)
|
641
|
+
|
642
|
+
# Continue with authentication
|
643
|
+
response = agent.agent(
|
644
|
+
"Continue with Jira authentication",
|
645
|
+
final_response_structure=JiraSummary,
|
646
|
+
agent_id=response.agent_id,
|
647
|
+
human_response=human_response
|
648
|
+
)
|
649
|
+
|
650
|
+
# Now agent can use the auth token with the fetch_jira_story tool
|
651
|
+
if response.is_complete():
|
652
|
+
summary = response.final_response
|
653
|
+
print(f"Story: {summary.story_id} - {summary.title}")
|
654
|
+
print(f"Status: {summary.status}")
|
655
|
+
print(f"Summary: {summary.summary}")
|
656
|
+
```
|
657
|
+
|
658
|
+
### Key Distinction: Tools vs Dialog Options
|
659
|
+
|
660
|
+
This example demonstrates the important difference:
|
661
|
+
|
662
|
+
**Tools** (Agent-Executed Functions):
|
663
|
+
- Functions that the agent calls directly to perform tasks
|
664
|
+
- Examples: `fetch_jira_story`, `generate_document`, `setup_database`
|
665
|
+
- Executed server-side by the agent
|
666
|
+
- Used for data processing, API calls, system operations
|
667
|
+
|
668
|
+
**Dialog Options** (UI-Handled Patterns):
|
669
|
+
- Structured communication patterns that the UI can understand and handle
|
670
|
+
- Examples: `jira_auth`, `github_oauth`, `approval_request`
|
671
|
+
- NOT executed by the agent - they're instructions for the UI
|
672
|
+
- Used for authentication flows, user approvals, secure interactions
|
673
|
+
- Parameters help the UI know how to handle the interaction
|
674
|
+
- Responses provide the agent with the results (tokens, approvals, etc.)
|
675
|
+
|
676
|
+
**Benefits**:
|
677
|
+
- **Security**: No sensitive data (auth tokens) passed as plain text
|
678
|
+
- **User Experience**: UI can provide proper authentication flows, not just text input
|
679
|
+
- **Structured**: Both agent and UI know exactly what type of interaction is needed
|
680
|
+
- **Flexible**: UI can handle complex flows (OAuth, 2FA, file uploads) appropriately
|
681
|
+
|
682
|
+
### Multiple Human Interactions
|
683
|
+
|
684
|
+
For complex workflows requiring multiple human interactions, you can provide responses as a list:
|
685
|
+
|
686
|
+
```python
|
687
|
+
from mbxai.agent.models import HumanInLoopResponseBatch, HumanInLoopResponse, HumanInteractionType
|
688
|
+
|
689
|
+
# Multiple individual responses
|
690
|
+
responses = [
|
691
|
+
HumanInLoopResponse(
|
692
|
+
interaction_id="auth_req_1",
|
693
|
+
response_type=HumanInteractionType.DIALOG_OPTION,
|
694
|
+
dialog_option_id="github_auth",
|
695
|
+
additional_context="auth_token:github_oauth_token_123"
|
696
|
+
),
|
697
|
+
HumanInLoopResponse(
|
698
|
+
interaction_id="approval_req_1",
|
699
|
+
response_type=HumanInteractionType.DECISION,
|
700
|
+
decision="approve",
|
701
|
+
additional_context="approved_by:john.doe@company.com"
|
702
|
+
)
|
703
|
+
]
|
704
|
+
|
705
|
+
# Send multiple responses at once
|
706
|
+
response = agent.agent(
|
707
|
+
prompt="Continue with multiple inputs",
|
708
|
+
final_response_structure=ProjectPlan,
|
709
|
+
agent_id=agent_id,
|
710
|
+
human_response=responses # List of responses
|
711
|
+
)
|
712
|
+
|
713
|
+
# Or use the batch model
|
714
|
+
response_batch = HumanInLoopResponseBatch(responses=responses)
|
715
|
+
response = agent.agent(
|
716
|
+
prompt="Continue with batch input",
|
717
|
+
final_response_structure=ProjectPlan,
|
718
|
+
agent_id=agent_id,
|
719
|
+
human_response=response_batch # Batch object
|
720
|
+
)
|
721
|
+
```
|
722
|
+
|
723
|
+
This is particularly useful when:
|
724
|
+
- The agent requests multiple approvals simultaneously
|
725
|
+
- Complex workflows need multiple authentication steps
|
726
|
+
- Batch processing user decisions for efficiency
|
727
|
+
|
728
|
+
### Custom Session Handlers
|
729
|
+
|
730
|
+
The `AgentClient` supports pluggable session storage through the `SessionHandler` protocol. This enables distributed, persistent, and scalable session management.
|
731
|
+
|
732
|
+
#### Built-in Session Handlers
|
733
|
+
|
734
|
+
**InMemorySessionHandler (Default):**
|
735
|
+
```python
|
736
|
+
from mbxai import AgentClient, InMemorySessionHandler
|
737
|
+
|
738
|
+
# Default in-memory storage (single instance)
|
739
|
+
agent = AgentClient(ai_client, session_handler=InMemorySessionHandler())
|
740
|
+
```
|
741
|
+
|
742
|
+
**Custom Redis Session Handler:**
|
743
|
+
```python
|
744
|
+
import redis
|
745
|
+
from mbxai import AgentClient, SessionHandler
|
746
|
+
from typing import Dict, Any, Optional
|
747
|
+
|
748
|
+
class RedisSessionHandler:
|
749
|
+
def __init__(self, redis_client: redis.Redis = None, ttl_seconds: int = 86400):
|
750
|
+
self.redis_client = redis_client or redis.Redis()
|
751
|
+
self.ttl_seconds = ttl_seconds
|
752
|
+
|
753
|
+
def get_session(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
754
|
+
session_json = self.redis_client.get(f"agent:{agent_id}")
|
755
|
+
return json.loads(session_json) if session_json else None
|
756
|
+
|
757
|
+
def set_session(self, agent_id: str, session_data: Dict[str, Any]) -> None:
|
758
|
+
session_json = json.dumps(session_data, default=str)
|
759
|
+
self.redis_client.setex(f"agent:{agent_id}", self.ttl_seconds, session_json)
|
760
|
+
|
761
|
+
def delete_session(self, agent_id: str) -> bool:
|
762
|
+
return self.redis_client.delete(f"agent:{agent_id}") > 0
|
763
|
+
|
764
|
+
def list_sessions(self) -> list[str]:
|
765
|
+
keys = self.redis_client.keys("agent:*")
|
766
|
+
return [key.decode().replace("agent:", "") for key in keys]
|
767
|
+
|
768
|
+
def session_exists(self, agent_id: str) -> bool:
|
769
|
+
return self.redis_client.exists(f"agent:{agent_id}") > 0
|
770
|
+
|
771
|
+
# Use Redis for distributed session storage
|
772
|
+
redis_handler = RedisSessionHandler(ttl_seconds=3600) # 1 hour TTL
|
773
|
+
agent = AgentClient(ai_client, session_handler=redis_handler)
|
774
|
+
```
|
775
|
+
|
776
|
+
#### Benefits of Custom Session Handlers
|
777
|
+
|
778
|
+
- **Distributed**: Share sessions across multiple application instances
|
779
|
+
- **Persistent**: Sessions survive application restarts
|
780
|
+
- **Scalable**: Handle thousands of concurrent sessions
|
781
|
+
- **Flexible**: Database, file system, cloud storage integration
|
782
|
+
- **TTL Support**: Automatic session cleanup
|
783
|
+
- **High Availability**: Redis Sentinel/Cluster support
|
784
|
+
|
785
|
+
#### Production Setup Example
|
786
|
+
|
787
|
+
```python
|
788
|
+
from mbxai import AgentClient
|
789
|
+
import redis.sentinel
|
790
|
+
|
791
|
+
# Redis Sentinel for high availability
|
792
|
+
sentinels = [('sentinel1', 26379), ('sentinel2', 26379)]
|
793
|
+
sentinel = redis.sentinel.Sentinel(sentinels)
|
794
|
+
redis_client = sentinel.master_for('mymaster', decode_responses=True)
|
795
|
+
|
796
|
+
class ProductionRedisHandler:
|
797
|
+
def __init__(self):
|
798
|
+
self.redis_client = redis_client
|
799
|
+
self.ttl_seconds = 24 * 60 * 60 # 24 hours
|
800
|
+
|
801
|
+
# ... implement SessionHandler methods
|
802
|
+
|
803
|
+
# Production agent with Redis clustering
|
804
|
+
agent = AgentClient(
|
805
|
+
ai_client=ai_client,
|
806
|
+
session_handler=ProductionRedisHandler()
|
807
|
+
)
|
808
|
+
```
|
809
|
+
|
810
|
+
### Real-World Example: Separate UI and Agent Systems
|
811
|
+
|
812
|
+
This example shows how the UI and Agent run as separate services with their own endpoints:
|
813
|
+
|
814
|
+
**UI Service (FastAPI - Container 1):**
|
815
|
+
```python
|
816
|
+
from fastapi import FastAPI, HTTPException
|
817
|
+
from pydantic import BaseModel
|
818
|
+
import httpx
|
819
|
+
from typing import Optional
|
820
|
+
|
821
|
+
app = FastAPI()
|
822
|
+
|
823
|
+
# Models for UI service
|
824
|
+
class ChatMessage(BaseModel):
|
825
|
+
message: str
|
826
|
+
user_id: str
|
827
|
+
|
828
|
+
class AgentRequest(BaseModel):
|
829
|
+
prompt: str
|
830
|
+
final_response_structure: str = "OrderInfo"
|
831
|
+
agent_id: Optional[str] = None
|
832
|
+
human_response: Optional[dict] = None
|
833
|
+
|
834
|
+
class CredentialsForm(BaseModel):
|
835
|
+
username: str
|
836
|
+
password: str
|
837
|
+
shop_url: str
|
838
|
+
|
839
|
+
# In-memory session storage (use Redis in production)
|
840
|
+
active_sessions = {}
|
841
|
+
|
842
|
+
@app.post("/chat")
|
843
|
+
async def chat_endpoint(message: ChatMessage):
|
844
|
+
"""Main chat endpoint for the UI"""
|
845
|
+
|
846
|
+
# Send user message to Agent service
|
847
|
+
agent_request = AgentRequest(
|
848
|
+
prompt=message.message,
|
849
|
+
final_response_structure="OrderInfo"
|
850
|
+
)
|
851
|
+
|
852
|
+
async with httpx.AsyncClient() as client:
|
853
|
+
response = await client.post(
|
854
|
+
"http://agent-service:8001/agent",
|
855
|
+
json=agent_request.dict()
|
856
|
+
)
|
857
|
+
agent_response = response.json()
|
858
|
+
|
859
|
+
# Check if agent needs human interaction
|
860
|
+
if agent_response.get("needs_human_interaction"):
|
861
|
+
request = agent_response["human_interaction_request"]
|
862
|
+
|
863
|
+
if request["interaction_type"] == "dialog_option" and request.get("dialog_option_id") == "shop_credentials":
|
864
|
+
# Store session and ask user for credentials
|
865
|
+
session_id = agent_response["agent_id"]
|
866
|
+
active_sessions[session_id] = {
|
867
|
+
"interaction_id": request["id"],
|
868
|
+
"shop_url": request["parameters"]["shop_url"]
|
869
|
+
}
|
870
|
+
|
871
|
+
return {
|
872
|
+
"type": "credentials_form",
|
873
|
+
"message": "Please provide your shop credentials",
|
874
|
+
"shop_url": request["parameters"]["shop_url"],
|
875
|
+
"session_id": session_id
|
876
|
+
}
|
877
|
+
|
878
|
+
# Return final result if available
|
879
|
+
if agent_response.get("is_complete"):
|
880
|
+
order = agent_response["final_response"]
|
881
|
+
return {
|
882
|
+
"type": "order_result",
|
883
|
+
"message": f"Your last order: #{order['order_number']} from {order['date']} (ID: {order['id']})"
|
884
|
+
}
|
885
|
+
|
886
|
+
return {"type": "message", "message": "Processing your request..."}
|
887
|
+
|
888
|
+
@app.post("/submit_credentials")
|
889
|
+
async def submit_credentials(credentials: CredentialsForm, session_id: str):
|
890
|
+
"""Handle user credentials submission"""
|
891
|
+
|
892
|
+
if session_id not in active_sessions:
|
893
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
894
|
+
|
895
|
+
session = active_sessions[session_id]
|
896
|
+
|
897
|
+
# Send credentials back to agent
|
898
|
+
human_response = {
|
899
|
+
"interaction_id": session["interaction_id"],
|
900
|
+
"response_type": "dialog_option",
|
901
|
+
"dialog_option_id": "shop_credentials",
|
902
|
+
"additional_context": f"username:{credentials.username},password:{credentials.password}"
|
903
|
+
}
|
904
|
+
|
905
|
+
agent_request = AgentRequest(
|
906
|
+
prompt="Continue with shop credentials",
|
907
|
+
final_response_structure="OrderInfo",
|
908
|
+
agent_id=session_id,
|
909
|
+
human_response=human_response
|
910
|
+
)
|
911
|
+
|
912
|
+
async with httpx.AsyncClient() as client:
|
913
|
+
response = await client.post(
|
914
|
+
"http://agent-service:8001/agent",
|
915
|
+
json=agent_request.dict()
|
916
|
+
)
|
917
|
+
agent_response = response.json()
|
918
|
+
|
919
|
+
# Clean up session
|
920
|
+
del active_sessions[session_id]
|
921
|
+
|
922
|
+
if agent_response.get("is_complete"):
|
923
|
+
order = agent_response["final_response"]
|
924
|
+
return {
|
925
|
+
"type": "order_result",
|
926
|
+
"message": f"Your last order: #{order['order_number']} from {order['date']} (ID: {order['id']})"
|
927
|
+
}
|
928
|
+
|
929
|
+
return {"type": "message", "message": "Processing your order request..."}
|
930
|
+
```
|
931
|
+
|
932
|
+
**Agent Service (FastAPI - Container 2):**
|
933
|
+
```python
|
934
|
+
from fastapi import FastAPI
|
935
|
+
from pydantic import BaseModel, Field
|
936
|
+
from mbxai import AgentClient, ToolClient, OpenRouterClient
|
937
|
+
from mbxai.agent.models import DialogOption, HumanInLoopResponse, HumanInteractionType
|
938
|
+
import os
|
939
|
+
from typing import Optional
|
940
|
+
|
941
|
+
app = FastAPI()
|
942
|
+
|
943
|
+
# Models for Agent service
|
944
|
+
class OrderInfo(BaseModel):
|
945
|
+
id: str = Field(description="Order ID")
|
946
|
+
order_number: str = Field(description="Order number")
|
947
|
+
date: str = Field(description="Order date")
|
948
|
+
|
949
|
+
class AgentRequest(BaseModel):
|
950
|
+
prompt: str
|
951
|
+
final_response_structure: str
|
952
|
+
agent_id: Optional[str] = None
|
953
|
+
human_response: Optional[dict] = None
|
954
|
+
|
955
|
+
# Shop integration tool (agent executes this)
|
956
|
+
def list_orders_from_shop(username: str, password: str, shop_url: str) -> list:
|
957
|
+
"""Fetch orders from shop using credentials."""
|
958
|
+
# Mock shop API call
|
959
|
+
if username == "demo" and password == "demo123":
|
960
|
+
return [
|
961
|
+
{"id": "ord_001", "order_number": "ORD-2024-001", "date": "2024-01-15"},
|
962
|
+
{"id": "ord_002", "order_number": "ORD-2024-002", "date": "2024-01-20"},
|
963
|
+
{"id": "ord_003", "order_number": "ORD-2024-003", "date": "2024-01-25"}
|
964
|
+
]
|
965
|
+
else:
|
966
|
+
return []
|
967
|
+
|
968
|
+
# Dialog option for shop credentials (UI handles this)
|
969
|
+
dialog_options = [
|
970
|
+
DialogOption(
|
971
|
+
id="shop_credentials",
|
972
|
+
title="Shop Credentials",
|
973
|
+
description="Provide shop login credentials",
|
974
|
+
parameters={"shop_url": "https://shop.example.com"}
|
975
|
+
)
|
976
|
+
]
|
977
|
+
|
978
|
+
# Initialize agent
|
979
|
+
openrouter_client = OpenRouterClient(token=os.getenv("OPENROUTER_API_KEY"))
|
980
|
+
tool_client = ToolClient(openrouter_client)
|
981
|
+
agent = AgentClient(
|
982
|
+
tool_client,
|
983
|
+
human_in_loop=True,
|
984
|
+
dialog_options=dialog_options
|
985
|
+
)
|
986
|
+
|
987
|
+
# Register the shop tool
|
988
|
+
agent.register_tool(
|
989
|
+
name="list_orders_from_shop",
|
990
|
+
description="List orders from online shop using user credentials",
|
991
|
+
function=list_orders_from_shop
|
992
|
+
)
|
993
|
+
|
994
|
+
@app.post("/agent")
|
995
|
+
async def agent_endpoint(request: AgentRequest):
|
996
|
+
"""Main agent processing endpoint"""
|
997
|
+
|
998
|
+
# Process human response if provided
|
999
|
+
human_response = None
|
1000
|
+
if request.human_response:
|
1001
|
+
human_response = HumanInLoopResponse(
|
1002
|
+
interaction_id=request.human_response["interaction_id"],
|
1003
|
+
response_type=HumanInteractionType(request.human_response["response_type"]),
|
1004
|
+
dialog_option_id=request.human_response.get("dialog_option_id"),
|
1005
|
+
additional_context=request.human_response.get("additional_context", "")
|
1006
|
+
)
|
1007
|
+
|
1008
|
+
# Call agent
|
1009
|
+
response = agent.agent(
|
1010
|
+
prompt=request.prompt,
|
1011
|
+
final_response_structure=OrderInfo,
|
1012
|
+
agent_id=request.agent_id,
|
1013
|
+
human_response=human_response
|
1014
|
+
)
|
1015
|
+
|
1016
|
+
# Convert response to JSON-serializable format
|
1017
|
+
result = {
|
1018
|
+
"agent_id": response.agent_id,
|
1019
|
+
"state": response.state.value,
|
1020
|
+
"is_complete": response.is_complete(),
|
1021
|
+
"needs_human_interaction": response.needs_human_interaction()
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
if response.needs_human_interaction():
|
1025
|
+
request = response.human_interaction_request
|
1026
|
+
result["human_interaction_request"] = {
|
1027
|
+
"id": request.id,
|
1028
|
+
"interaction_type": request.interaction_type.value,
|
1029
|
+
"prompt": request.prompt,
|
1030
|
+
"dialog_option_id": "shop_credentials" if request.dialog_options else None,
|
1031
|
+
"parameters": {"shop_url": "https://shop.example.com"}
|
1032
|
+
}
|
1033
|
+
|
1034
|
+
if response.is_complete():
|
1035
|
+
# Get the last order (most recent)
|
1036
|
+
order_data = response.final_response
|
1037
|
+
result["final_response"] = {
|
1038
|
+
"id": order_data.id,
|
1039
|
+
"order_number": order_data.order_number,
|
1040
|
+
"date": order_data.date
|
1041
|
+
}
|
1042
|
+
|
1043
|
+
return result
|
1044
|
+
|
1045
|
+
@app.get("/health")
|
1046
|
+
async def health_check():
|
1047
|
+
return {"status": "healthy"}
|
1048
|
+
```
|
1049
|
+
|
1050
|
+
**Flow Explanation:**
|
1051
|
+
|
1052
|
+
1. **User**: "Give me my last order from this online shop"
|
1053
|
+
2. **UI**: Sends message to Agent service `/agent` endpoint
|
1054
|
+
3. **Agent**: Analyzes request, realizes it needs shop credentials
|
1055
|
+
4. **Agent**: Returns `needs_human_interaction=True` with `shop_credentials` dialog option
|
1056
|
+
5. **UI**: Recognizes dialog option, shows credentials form to user
|
1057
|
+
6. **User**: Enters username/password in UI form
|
1058
|
+
7. **UI**: Sends credentials to Agent via `/agent` endpoint with `human_response`
|
1059
|
+
8. **Agent**: Uses `list_orders_from_shop` tool with provided credentials
|
1060
|
+
9. **Agent**: Returns last order information
|
1061
|
+
10. **UI**: Displays order to user
|
1062
|
+
|
1063
|
+
**Key Points:**
|
1064
|
+
- **Separation**: UI handles user interaction, Agent handles business logic
|
1065
|
+
- **Security**: Credentials flow through structured dialog, not plain text
|
1066
|
+
- **Scalability**: Services can be scaled independently
|
1067
|
+
- **Flexibility**: UI can customize credential forms, Agent focuses on order processing
|
1068
|
+
|
1069
|
+
### Error Handling and Logging
|
1070
|
+
|
1071
|
+
```python
|
1072
|
+
import logging
|
1073
|
+
|
1074
|
+
# Configure logging
|
1075
|
+
logging.basicConfig(level=logging.DEBUG)
|
1076
|
+
|
1077
|
+
try:
|
1078
|
+
response = client.create(messages)
|
1079
|
+
except OpenRouterAPIError as e:
|
1080
|
+
print(f"API Error: {e}")
|
1081
|
+
except OpenRouterConnectionError as e:
|
1082
|
+
print(f"Connection Error: {e}")
|
1083
|
+
except Exception as e:
|
1084
|
+
print(f"Unexpected Error: {e}")
|
1085
|
+
```
|
1086
|
+
|
1087
|
+
### Streaming Responses
|
1088
|
+
|
1089
|
+
```python
|
1090
|
+
# Streaming with OpenRouterClient
|
1091
|
+
response = client.create(messages, stream=True)
|
1092
|
+
for chunk in response:
|
1093
|
+
if chunk.choices[0].delta.content:
|
1094
|
+
print(chunk.choices[0].delta.content, end="")
|
1095
|
+
|
1096
|
+
# Streaming with ToolClient (tools execute before streaming)
|
1097
|
+
response = tool_client.chat(messages, stream=True)
|
1098
|
+
for chunk in response:
|
1099
|
+
if chunk.choices[0].delta.content:
|
1100
|
+
print(chunk.choices[0].delta.content, end="")
|
1101
|
+
```
|
1102
|
+
|
1103
|
+
## 🧪 Testing
|
1104
|
+
|
1105
|
+
Run the test suite:
|
1106
|
+
|
1107
|
+
```bash
|
1108
|
+
# Install development dependencies using uv
|
1109
|
+
uv sync
|
1110
|
+
|
1111
|
+
# Run tests with uv
|
1112
|
+
uv run pytest tests/
|
1113
|
+
|
1114
|
+
# Run with coverage
|
1115
|
+
uv run pytest tests/ --cov=mbxai --cov-report=html
|
1116
|
+
|
1117
|
+
# Test enhanced agent examples
|
1118
|
+
uv run python src/mbxai/examples/enhanced_agent_example.py
|
1119
|
+
|
1120
|
+
# Test Redis session handler (requires Redis)
|
1121
|
+
uv run python src/mbxai/examples/redis_session_handler_example.py
|
1122
|
+
```
|
1123
|
+
|
1124
|
+
## 🔧 Development Setup
|
1125
|
+
|
1126
|
+
1. Clone the repository:
|
1127
|
+
```bash
|
1128
|
+
git clone https://github.com/yourusername/mbxai.git
|
1129
|
+
cd mbxai/packages
|
1130
|
+
```
|
1131
|
+
|
1132
|
+
2. Install using uv (recommended):
|
1133
|
+
```bash
|
1134
|
+
# Install uv if not already installed
|
1135
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
1136
|
+
|
1137
|
+
# Install dependencies and create virtual environment
|
1138
|
+
uv sync
|
1139
|
+
|
1140
|
+
# Activate virtual environment (optional, uv run handles this automatically)
|
1141
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
1142
|
+
```
|
1143
|
+
|
1144
|
+
3. Alternatively, use pip:
|
1145
|
+
```bash
|
1146
|
+
python -m venv .venv
|
1147
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
1148
|
+
pip install -e ".[dev]"
|
1149
|
+
```
|
1150
|
+
|
1151
|
+
4. Set up environment variables:
|
1152
|
+
```bash
|
1153
|
+
export OPENROUTER_API_KEY="your-api-key"
|
1154
|
+
```
|
1155
|
+
|
1156
|
+
## 📄 License
|
1157
|
+
|
1158
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
1159
|
+
|
1160
|
+
## 🤝 Contributing
|
1161
|
+
|
1162
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
1163
|
+
|
1164
|
+
## 🔗 Links
|
1165
|
+
|
1166
|
+
- **Homepage**: [https://www.mibexx.de](https://www.mibexx.de)
|
1167
|
+
- **Documentation**: [https://www.mibexx.de](https://www.mibexx.de)
|
1168
|
+
- **Repository**: [https://github.com/yourusername/mbxai](https://github.com/yourusername/mbxai)
|
1169
|
+
|
1170
|
+
## 📊 Version Information
|
1171
|
+
|
1172
|
+
Current version: **2.3.1**
|
1173
|
+
|
1174
|
+
### What's New in 2.3.1:
|
1175
|
+
- **🎯 Enhanced Agent Client**: Complete rewrite with 6-step intelligent process
|
1176
|
+
- **💾 Pluggable Session Storage**: Custom session handlers for Redis, Database, File System
|
1177
|
+
- **👤 Human-in-the-Loop**: Interactive decision making, questions, and custom dialog options
|
1178
|
+
- **📋 Task Management**: Intelligent todo list generation with dependencies and status tracking
|
1179
|
+
- **🎯 Goal Evaluation**: Automatic assessment of goal achievement with iterative improvement
|
1180
|
+
- **🔧 Dialog Options**: Custom functions for authentication, integrations, and workflows
|
1181
|
+
- **📊 Enhanced State Management**: Full visibility into agent process and progress
|
1182
|
+
- **🧠 Requirement Analysis**: Intelligent goal breakdown and complexity assessment
|
1183
|
+
- **🛠️ Tool Analysis**: Smart mapping of available tools to goals
|
1184
|
+
- **🌐 Distributed Sessions**: Scale across multiple instances with persistent session storage
|
1185
|
+
|
1186
|
+
### Requirements:
|
1187
|
+
- Python 3.12+ required
|
1188
|
+
- Built with modern async/await patterns
|
1189
|
+
- Type-safe with Pydantic v2
|
1190
|
+
- Compatible with OpenAI SDK v1.77+
|
1191
|
+
- Recommended: Use `uv` for dependency management
|