RouteKitAI 0.1.0__py3-none-any.whl → 0.2.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.
routekitai/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """RouteKitAI: An agent development + orchestration framework."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.1"
4
4
 
5
5
  # Import hooks first to ensure ToolFilter is defined before Agent
6
6
  from routekitai.core import (
@@ -1,12 +1,12 @@
1
1
  """Message primitive for RouteKit."""
2
2
 
3
- from enum import Enum
3
+ from enum import StrEnum
4
4
  from typing import Any
5
5
 
6
6
  from pydantic import BaseModel, Field
7
7
 
8
8
 
9
- class MessageRole(str, Enum):
9
+ class MessageRole(StrEnum):
10
10
  """Message role types."""
11
11
 
12
12
  SYSTEM = "system"
@@ -25,7 +25,8 @@ class ReActPolicy(Policy):
25
25
  Simple loop: model -> decide tool -> tool -> model -> final
26
26
  """
27
27
 
28
- max_iterations: int = 10
28
+ def __init__(self, max_iterations: int = 10) -> None:
29
+ self.max_iterations = max_iterations
29
30
 
30
31
  async def plan(self, state: dict[str, Any]) -> list[Action]:
31
32
  """Plan next action in ReAct loop.
@@ -57,6 +58,7 @@ class ReActPolicy(Policy):
57
58
  ToolAction(
58
59
  tool_name=tc["name"],
59
60
  tool_input=tc.get("arguments", {}),
61
+ tool_call_id=tc.get("id", ""),
60
62
  )
61
63
  for tc in last_message.tool_calls
62
64
  ]
@@ -120,6 +122,7 @@ class FunctionCallingPolicy(Policy):
120
122
  ToolAction(
121
123
  tool_name=tool_name,
122
124
  tool_input=tc.get("arguments", {}),
125
+ tool_call_id=tc.get("id", ""),
123
126
  )
124
127
  )
125
128
 
@@ -178,6 +181,10 @@ class GraphPolicy(Policy, BaseModel):
178
181
  # Execute graph
179
182
  graph_result = await executor.execute(input_data=input_data)
180
183
 
184
+ # Store on runtime so callers can inspect steps and state (e.g. for logging)
185
+ if runtime is not None:
186
+ runtime._last_graph_result = graph_result
187
+
181
188
  # Return final output as result
182
189
  final_output = graph_result.get("state", {}).get("output") or graph_result.get(
183
190
  "state", {}
@@ -302,19 +309,18 @@ class SupervisorPolicy(Policy, BaseModel):
302
309
  if iteration >= self.max_iterations:
303
310
  return [Final(output=Message.assistant("Max iterations reached"))]
304
311
 
305
- # Check if we're waiting for a sub-agent result
306
- if state.get("waiting_for_subagent"):
307
- # Sub-agent has completed, merge result
308
- subagent_result = state.get("subagent_result")
309
- if subagent_result:
310
- # Create message with sub-agent result
311
- result_message = Message.assistant(
312
- f"Sub-agent completed: {subagent_result.get('output', 'Task completed') if isinstance(subagent_result, dict) else str(subagent_result)}"
313
- )
314
- # Note: State mutations should be done via reflect(), not here
315
- # But we need to signal completion, so we'll let the runtime handle it
316
- # Supervisor processes the result
317
- return [ModelAction(messages=[*messages, result_message])]
312
+ # Sub-agent just completed: finalize with its result (runtime set waiting_for_subagent=False)
313
+ subagent_result = state.get("subagent_result")
314
+ if subagent_result and not state.get("waiting_for_subagent"):
315
+ if isinstance(subagent_result, dict) and "output" in subagent_result:
316
+ content = subagent_result["output"]
317
+ if hasattr(content, "content"):
318
+ content = content.content
319
+ else:
320
+ content = str(content)
321
+ else:
322
+ content = str(subagent_result)
323
+ return [Final(output=Message.assistant(content))]
318
324
 
319
325
  # If no messages, supervisor decides which agent to use
320
326
  if not messages:
routekitai/core/policy.py CHANGED
@@ -28,6 +28,9 @@ class ToolAction(Action):
28
28
  action_type: str = Field(default="tool", description="Action type")
29
29
  tool_name: str = Field(..., description="Tool name to execute")
30
30
  tool_input: dict[str, Any] = Field(..., description="Tool input arguments")
31
+ tool_call_id: str = Field(
32
+ default="", description="Tool call ID from assistant message (for API compatibility)"
33
+ )
31
34
 
32
35
 
33
36
  class Parallel(Action):
@@ -107,6 +107,7 @@ class PolicyAdapter(RuntimePolicy):
107
107
  input_data={
108
108
  "tool_name": action.tool_name,
109
109
  "tool_args": action.tool_input,
110
+ "tool_call_id": action.tool_call_id,
110
111
  },
111
112
  )
112
113
  )
@@ -122,12 +123,26 @@ class PolicyAdapter(RuntimePolicy):
122
123
  input_data={
123
124
  "tool_name": sub_action.tool_name,
124
125
  "tool_args": sub_action.tool_input,
126
+ "tool_call_id": sub_action.tool_call_id,
125
127
  },
126
128
  )
127
129
  )
128
130
  # Other action types in parallel not yet supported
129
131
  elif isinstance(action, Final):
130
- # Final action - return empty to signal completion
131
- return []
132
+ # Final action - return a "final" step so runtime uses this output
133
+ steps.append(
134
+ Step(
135
+ step_id=str(uuid.uuid4()),
136
+ step_type="final",
137
+ input_data={"output": action.output},
138
+ )
139
+ )
140
+ return steps
132
141
 
133
142
  return steps
143
+
144
+ async def reflect(self, state: dict[str, Any], observation: dict[str, Any]) -> dict[str, Any]:
145
+ """Delegate to the underlying policy's reflect if it has one."""
146
+ if hasattr(self.policy, "reflect") and callable(self.policy.reflect):
147
+ return await self.policy.reflect(state, observation)
148
+ return state.copy()
@@ -107,6 +107,8 @@ class Runtime(BaseModel):
107
107
  self._current_step: int = 0
108
108
  self._total_steps: int = 0
109
109
  self._progress_callbacks: list[Callable[[dict[str, Any]], None]] = []
110
+ # Last graph execution result (set by GraphPolicy for callers to inspect steps/state)
111
+ self._last_graph_result: dict[str, Any] | None = None
110
112
 
111
113
  def register_agent(self, agent: "Agent") -> None:
112
114
  """Register an agent with the runtime.
@@ -244,16 +246,10 @@ class Runtime(BaseModel):
244
246
  }
245
247
  trace.add_event("run_completed", {"trace_id": trace_id, "result": result_dict})
246
248
 
247
- # Export trace asynchronously (fire and forget)
249
+ # Export trace so it is on disk before returning (needed for replay and tests)
248
250
  if exporter and self.trace_dir:
249
- # Ensure directory exists before async export
250
251
  self.trace_dir.mkdir(parents=True, exist_ok=True)
251
- # Create background task for trace export
252
- export_task = asyncio.create_task(exporter.export(trace))
253
- # Store task reference for potential cleanup
254
- # Note: Task will complete in background, errors are logged by exporter
255
- # For testing, we could await here, but in production we want fire-and-forget
256
- # The exporter now creates the directory itself, so this should work
252
+ await exporter.export(trace)
257
253
 
258
254
  return result
259
255
  except asyncio.CancelledError:
@@ -394,7 +390,13 @@ class Runtime(BaseModel):
394
390
  step_results = await self._execute_steps_parallel(steps, agent, trace)
395
391
 
396
392
  # Process step results
393
+ final_output_message: Message | None = None
394
+ last_model_response: ModelResponse | None = None
397
395
  for step_result in step_results:
396
+ if step_result.step_type == "final":
397
+ if step_result.output_data and "output" in step_result.output_data:
398
+ final_output_message = step_result.output_data["output"]
399
+ break
398
400
  if step_result.step_type == "model_call":
399
401
  # Handle model response
400
402
  if (
@@ -413,6 +415,11 @@ class Runtime(BaseModel):
413
415
  if isinstance(response_data, dict)
414
416
  else []
415
417
  )
418
+ last_model_response = ModelResponse(
419
+ content=content,
420
+ tool_calls=None,
421
+ usage=None,
422
+ )
416
423
 
417
424
  # Create assistant message with tool calls
418
425
  tool_calls: list[dict[str, Any]] | None = None
@@ -445,13 +452,22 @@ class Runtime(BaseModel):
445
452
  if step_result.input_data
446
453
  else ""
447
454
  )
455
+ tool_call_id = (
456
+ step_result.input_data.get("tool_call_id", "")
457
+ if step_result.input_data
458
+ else ""
459
+ )
448
460
  tool_result = step_result.output_data["result"]
449
461
 
450
- # Add tool result message
462
+ # Add tool result message (tool_call_id required by OpenAI API)
451
463
  messages.append(
452
464
  Message.tool(
453
465
  f"Tool {tool_name} executed",
454
- {"result": tool_result, "tool": tool_name},
466
+ {
467
+ "result": tool_result,
468
+ "tool": tool_name,
469
+ "tool_call_id": tool_call_id,
470
+ },
455
471
  )
456
472
  )
457
473
 
@@ -495,6 +511,24 @@ class Runtime(BaseModel):
495
511
  }
496
512
  state["waiting_for_subagent"] = False
497
513
 
514
+ # Let policy reflect on step results (e.g. PlanExecutePolicy updates phase/plan)
515
+ if (
516
+ last_model_response is not None
517
+ and hasattr(policy, "reflect")
518
+ and callable(policy.reflect)
519
+ ):
520
+ state.setdefault(
521
+ "phase", "planning"
522
+ ) # so policies can detect first (planning) phase
523
+ observation = {"result": last_model_response}
524
+ reflected = await policy.reflect(state, observation)
525
+ if isinstance(reflected, dict):
526
+ state.update(reflected)
527
+
528
+ if final_output_message is not None:
529
+ output_message = final_output_message
530
+ break
531
+
498
532
  iteration += 1
499
533
 
500
534
  # Finalize if we hit max iterations
@@ -509,6 +543,14 @@ class Runtime(BaseModel):
509
543
  output_message = Message.assistant(final_response.content)
510
544
  messages.append(output_message)
511
545
 
546
+ # If final output has no content (e.g. model returned empty after tool use),
547
+ # use the last assistant message that had content so the run result is useful.
548
+ if not (output_message.content or "").strip() and messages:
549
+ for msg in reversed(messages):
550
+ if msg.role == MessageRole.ASSISTANT and (msg.content or "").strip():
551
+ output_message = msg
552
+ break
553
+
512
554
  # Import here to avoid circular import
513
555
  from routekitai.core.agent import RunResult
514
556
 
@@ -600,7 +642,10 @@ class Runtime(BaseModel):
600
642
  )
601
643
 
602
644
  try:
603
- if step.step_type == "model_call":
645
+ if step.step_type == "final":
646
+ # Policy produced a final output (e.g. GraphPolicy); pass it through
647
+ step.output_data = {"output": step.input_data.get("output")}
648
+ elif step.step_type == "model_call":
604
649
  # Check if in replay mode
605
650
  if self._replay_mode and self._replay_trace:
606
651
  # Match by sequential order using step_completed events
@@ -1394,6 +1439,7 @@ class DefaultPolicy(Policy):
1394
1439
  input_data={
1395
1440
  "tool_name": tool_call["name"],
1396
1441
  "tool_args": tool_call.get("arguments", {}),
1442
+ "tool_call_id": tool_call.get("id", ""),
1397
1443
  },
1398
1444
  )
1399
1445
  )
routekitai/core/tool.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Tool primitive for RouteKit."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from enum import Enum
4
+ from enum import StrEnum
5
5
  from typing import Any, TypeVar
6
6
 
7
7
  from pydantic import BaseModel, Field, create_model
@@ -12,7 +12,7 @@ TInput = TypeVar("TInput", bound=BaseModel)
12
12
  TOutput = TypeVar("TOutput", bound=BaseModel)
13
13
 
14
14
 
15
- class ToolPermission(str, Enum):
15
+ class ToolPermission(StrEnum):
16
16
  """Tool permission types."""
17
17
 
18
18
  NETWORK = "network"
@@ -1,13 +1,13 @@
1
1
  """Graph definition for agent orchestration."""
2
2
 
3
3
  from collections.abc import Callable
4
- from enum import Enum
4
+ from enum import StrEnum
5
5
  from typing import Any
6
6
 
7
7
  from pydantic import BaseModel, Field
8
8
 
9
9
 
10
- class NodeType(str, Enum):
10
+ class NodeType(StrEnum):
11
11
  """Type of graph node."""
12
12
 
13
13
  MODEL = "model" # Execute a model call
routekitai/message.py CHANGED
@@ -1,12 +1,12 @@
1
1
  """Message primitive for RouteKit."""
2
2
 
3
- from enum import Enum
3
+ from enum import StrEnum
4
4
  from typing import Any
5
5
 
6
6
  from pydantic import BaseModel, Field
7
7
 
8
8
 
9
- class MessageRole(str, Enum):
9
+ class MessageRole(StrEnum):
10
10
  """Message role types."""
11
11
 
12
12
  USER = "user"
@@ -1,9 +1,11 @@
1
1
  """Model providers for RouteKit."""
2
2
 
3
+ from routekitai.providers.anthropic import AnthropicModel
3
4
  from routekitai.providers.local import FakeModel
4
5
  from routekitai.providers.openai import OpenAIChatModel
5
6
 
6
7
  __all__ = [
8
+ "AnthropicModel",
7
9
  "OpenAIChatModel",
8
10
  "FakeModel",
9
11
  ]
@@ -75,7 +75,11 @@ class AnthropicModel(Model):
75
75
  return self._client
76
76
 
77
77
  def _message_to_anthropic(self, message: Message) -> dict[str, Any]:
78
- """Convert routkitai Message to Anthropic format."""
78
+ """Convert routkitai Message to Anthropic format.
79
+
80
+ Anthropic requires all messages to have non-empty content except the
81
+ optional final assistant message. We ensure content is never empty.
82
+ """
79
83
  role_map = {
80
84
  MessageRole.SYSTEM: "system",
81
85
  MessageRole.USER: "user",
@@ -84,13 +88,15 @@ class AnthropicModel(Model):
84
88
  # Anthropic doesn't support tool role messages directly
85
89
  if message.role == MessageRole.TOOL:
86
90
  # Convert tool messages to user messages with tool result
87
- return {
88
- "role": "user",
89
- "content": f"Tool result: {message.content}",
90
- }
91
+ content = (
92
+ f"Tool result: {message.content}" if message.content else "Tool result: (no output)"
93
+ )
94
+ return {"role": "user", "content": content}
95
+ # API rejects empty or whitespace-only content; use a non-whitespace placeholder
96
+ content = message.content if (message.content and message.content.strip()) else "(no text)"
91
97
  return {
92
98
  "role": role_map.get(message.role, "user"),
93
- "content": message.content,
99
+ "content": content,
94
100
  }
95
101
 
96
102
  def _tools_to_anthropic(self, tools: list[Tool]) -> list[dict[str, Any]]:
@@ -208,9 +214,19 @@ class AnthropicModel(Model):
208
214
  )
209
215
 
210
216
  except httpx.HTTPStatusError as e:
211
- raise ModelError(
212
- f"Anthropic API error: {e.response.status_code} - {e.response.text}"
213
- ) from e
217
+ msg = f"Anthropic API error: {e.response.status_code}"
218
+ try:
219
+ body = e.response.json()
220
+ err = body.get("error") or {}
221
+ if isinstance(err, dict) and err.get("message"):
222
+ msg = f"{msg} - {err['message']}"
223
+ else:
224
+ msg = f"{msg} - {e.response.text}"
225
+ except Exception:
226
+ msg = f"{msg} - {e.response.text}"
227
+ if e.response.status_code == 404 and "model" in msg.lower():
228
+ msg += " Use a model ID from https://docs.anthropic.com/en/api/models or set ANTHROPIC_MODEL."
229
+ raise ModelError(msg) from e
214
230
  except Exception as e:
215
231
  raise ModelError(f"Failed to call Anthropic API: {e}") from e
216
232
 
@@ -1,13 +1,13 @@
1
1
  """Permission management for tool execution."""
2
2
 
3
- from enum import Enum
3
+ from enum import StrEnum
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
7
  from routekitai.core.tool import ToolPermission
8
8
 
9
9
 
10
- class PermissionLevel(str, Enum):
10
+ class PermissionLevel(StrEnum):
11
11
  """Permission levels for sandbox execution."""
12
12
 
13
13
  NONE = "none"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RouteKitAI
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: An agent development + orchestration framework
5
5
  Author: Mohamed Ghassen Brahim
6
6
  License: MIT
@@ -41,6 +41,9 @@ Requires-Dist: sentence-transformers>=2.2.0; extra == "optional"
41
41
  Requires-Dist: faiss-cpu>=1.7.4; extra == "optional"
42
42
  Requires-Dist: openai>=1.0.0; extra == "optional"
43
43
  Requires-Dist: nest-asyncio>=1.5.0; extra == "optional"
44
+ Provides-Extra: docs
45
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
46
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
44
47
  Dynamic: license-file
45
48
 
46
49
  # RouteKitAI
@@ -60,6 +63,8 @@ Dynamic: license-file
60
63
 
61
64
  ---
62
65
 
66
+ > **⚠️ Early stage**: RouteKitAI is still in very early development. APIs may change, and some features are experimental. Use with caution in production.
67
+
63
68
  RouteKitAI is a Python framework for building AI agents with **graph-based orchestration**, **built-in tracing**, and **deterministic replay**. Unlike other frameworks, RouteKitAI treats observability and testability as first-class features from day one.
64
69
 
65
70
  ## ✨ Features
@@ -195,50 +200,54 @@ asyncio.run(main())
195
200
 
196
201
  - **[Architecture Guide](docs/architecture.md)**: Deep dive into RouteKitAI's design
197
202
  - **[Security & Governance](docs/security-and-governance.md)**: Security features and best practices
198
- - **[API Reference](https://RouteKitAI.readthedocs.io)**: Complete API documentation (coming soon)
203
+ - **[Full documentation](https://routekitai.readthedocs.io)**: Architecture, security, and guides (Read the Docs)
199
204
 
200
205
  ## 🎓 Examples
201
206
 
202
207
  Check out the [`examples/`](examples/) directory for complete examples:
203
208
 
204
- - **[Basic Agent](examples/hello_RouteKitAI.py)**: Simple agent with tools
205
- - **[Graph Orchestration](examples/graph_agent.py)**: Multi-agent workflow
206
- - **[Supervisor Pattern](examples/supervisor_agent.py)**: Supervisor delegating to sub-agents
209
+ - **[Basic Agent](examples/basic.py)** / **[Hello RouteKit](examples/hello_routekit.py)**: Simple agent with tools
210
+ - **[Real agent with Anthropic](examples/anthropic_agent.py)**: Live Claude agent with tool use (requires `ANTHROPIC_API_KEY`)
211
+ - **[Graph Orchestration](examples/graph_agent.py)** / **[Graph Policy](examples/graph_policy.py)**: Multi-agent workflows
212
+ - **[Supervisor Pattern](examples/supervisor_agent.py)** / **[Supervisor Policy](examples/supervisor_policy.py)**: Supervisor delegating to sub-agents
213
+ - **[ReAct / Plan–Execute / Function Calling](examples/react_policy.py)**, **[Plan–Execute](examples/plan_execute_policy.py)**, **[Function Calling](examples/function_calling_policy.py)**: Policy examples
207
214
  - **[Evaluation Harness](examples/eval_regression.py)**: Testing agents with datasets
208
215
 
216
+ Run all examples: `./scripts/run_examples.sh` (or `bash scripts/run_examples.sh`).
217
+
209
218
  ## 🛠️ CLI Commands
210
219
 
211
- RouteKitAI provides a CLI for common operations:
220
+ RouteKitAI provides a CLI (`routekitai`) for common operations:
212
221
 
213
222
  ```bash
214
223
  # Run an agent script
215
- RouteKitAI run agent_script.py
224
+ routekitai run agent_script.py
216
225
 
217
226
  # View a trace (multiple formats available)
218
- RouteKitAI trace <trace_id> # Table view (default)
219
- RouteKitAI trace <trace_id> --format timeline # Timeline visualization
220
- RouteKitAI trace <trace_id> --format steps # Step-by-step execution
221
- RouteKitAI trace <trace_id> --format json # JSON output
222
- RouteKitAI trace <trace_id> --format raw # Raw JSONL
227
+ routekitai trace <trace_id> # Table view (default)
228
+ routekitai trace <trace_id> --format timeline # Timeline visualization
229
+ routekitai trace <trace_id> --format steps # Step-by-step execution
230
+ routekitai trace <trace_id> --format json # JSON output
231
+ routekitai trace <trace_id> --format raw # Raw JSONL
223
232
 
224
233
  # Analyze trace metrics
225
- RouteKitAI trace-analyze <trace_id> # Performance metrics, token usage, costs
234
+ routekitai trace-analyze <trace_id> # Performance metrics, token usage, costs
226
235
 
227
236
  # Search traces
228
- RouteKitAI trace-search "error" # Search all traces for "error"
229
- RouteKitAI trace-search "model" --trace-id abc # Search specific trace
230
- RouteKitAI trace-search "tool" --event-type tool_called # Filter by event type
237
+ routekitai trace-search "error" # Search all traces for "error"
238
+ routekitai trace-search "model" --trace-id abc # Search specific trace
239
+ routekitai trace-search "tool" --event-type tool_called # Filter by event type
231
240
 
232
241
  # Replay a trace
233
- RouteKitAI replay <trace_id> --agent my_agent
242
+ routekitai replay <trace_id> --agent my_agent
234
243
 
235
244
  # Start web UI for trace visualization
236
- RouteKitAI serve # Start on default port 8080
237
- RouteKitAI serve --port 3000 # Custom port
238
- RouteKitAI serve --host 0.0.0.0 # Make accessible from network
245
+ routekitai serve # Start on default port 8080
246
+ routekitai serve --port 3000 # Custom port
247
+ routekitai serve --host 0.0.0.0 # Make accessible from network
239
248
 
240
249
  # Run sanity checks
241
- RouteKitAI test-agent
250
+ routekitai test-agent
242
251
  ```
243
252
 
244
253
  ## 🏗️ Core Primitives
@@ -271,7 +280,7 @@ pip install -e ".[dev]"
271
280
  pytest
272
281
 
273
282
  # Run with coverage
274
- pytest --cov=RouteKitAI --cov-report=html
283
+ pytest --cov=routekitai --cov-report=html
275
284
 
276
285
  # Run specific test file
277
286
  pytest tests/test_runtime.py
@@ -284,15 +293,15 @@ pytest tests/test_runtime.py
284
293
  mypy src/
285
294
 
286
295
  # Linting
287
- ruff check src/
296
+ ruff check src/ tests/ examples/
288
297
 
289
298
  # Format code
290
- ruff format src/
299
+ ruff format src/ tests/ examples/
291
300
  ```
292
301
 
293
302
  ## 🤝 Contributing
294
303
 
295
- Contributions are welcome! Please read our contributing guidelines (coming soon) and:
304
+ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) and:
296
305
 
297
306
  1. Fork the repository
298
307
  2. Create a feature branch (`git checkout -b feature/amazing-feature`)
@@ -302,8 +311,29 @@ Contributions are welcome! Please read our contributing guidelines (coming soon)
302
311
 
303
312
  ## 📋 Requirements
304
313
 
305
- - Python 3.11 or higher
306
- - Pydantic 2.0+
314
+ **Core (always installed):**
315
+
316
+ - Python 3.11+
317
+ - [Pydantic](https://docs.pydantic.dev/) 2.x
318
+ - [NumPy](https://numpy.org/) 1.24+
319
+ - [httpx](https://www.python-httpx.org/) 0.24+
320
+
321
+ **Optional extras:**
322
+
323
+ | Extra | Purpose |
324
+ |-------|---------|
325
+ | `[dev]` | CLI, tests, linting, type checking (pytest, mypy, ruff, rich, typer, safety, bandit) |
326
+ | `[ui]` | Web UI for traces: `routekitai serve` (FastAPI, Uvicorn) |
327
+ | `[optional]` | Vector memory, OpenAI adapter (sentence-transformers, faiss, openai, nest-asyncio) |
328
+ | `[docs]` | Build documentation locally (MkDocs, MkDocs Material) |
329
+
330
+ Examples:
331
+
332
+ ```bash
333
+ pip install "RouteKitAI[dev]" # development + CLI
334
+ pip install "RouteKitAI[dev,ui]" # + trace web UI
335
+ pip install "RouteKitAI[docs]" # build docs: mkdocs build / mkdocs serve
336
+ ```
307
337
 
308
338
  ## 📄 License
309
339
 
@@ -316,7 +346,7 @@ RouteKitAI is inspired by the need for testable, observable AI agent frameworks.
316
346
  ## 🔗 Links
317
347
 
318
348
  - **GitHub**: [https://github.com/MedGhassen/RouteKitAI](https://github.com/MedGhassen/RouteKitAI)
319
- - **Documentation**: [https://RouteKitAI.readthedocs.io](https://RouteKitAI.readthedocs.io) (coming soon)
349
+ - **Documentation**: [https://routekitai.readthedocs.io](https://routekitai.readthedocs.io)
320
350
  - **Issues**: [https://github.com/MedGhassen/RouteKitAI/issues](https://github.com/MedGhassen/RouteKitAI/issues)
321
351
 
322
352
  ---
@@ -1,5 +1,5 @@
1
- routekitai/__init__.py,sha256=n5oNaNxhqdqDFCBCLBhtG0qoNz3SCWP4l3S1ublAqVc,1091
2
- routekitai/message.py,sha256=HvUTT9UdOlbHwoQAX7tcWCX51GD-AGanUwsb2WwKEXM,829
1
+ routekitai/__init__.py,sha256=155KUL2u8F-wn5wV7oT4bwzpJ84l2OWhmsU-hk-MM3A,1091
2
+ routekitai/message.py,sha256=owTgRvHctqquWROng2w06c6sMrarKlB6avxHoPhuC3k,830
3
3
  routekitai/model.py,sha256=FfGla6Hep-L1bz-rC89leGdbtQed433FZ1yz4eF0wko,1357
4
4
  routekitai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  routekitai/tool.py,sha256=o3fOyz_iOyrbYcrc7NYrI7m7JXOLEBZ5WkaKYjsvbF8,1027
@@ -17,13 +17,13 @@ routekitai/core/agent.py,sha256=z1mkIekJ8bbWebmzNADl3vMQURXDMx82eo2O_GC-TFk,1230
17
17
  routekitai/core/errors.py,sha256=YN7uo6B7R7KRk5Lwmk4JclJNBQbKOe5YzXLQtvTLKa8,1224
18
18
  routekitai/core/hooks.py,sha256=lnp5tK_amFDUbkYWxvWnIDwQ2uI4em2Go0KLb1QtD7c,5644
19
19
  routekitai/core/memory.py,sha256=szDTIr3tCgM4aeFO19tOlGj6a8lLPBHTv4KByeUxC2k,1429
20
- routekitai/core/message.py,sha256=kMQIQ6GgYT5Wet7qfkVbqtQyxqCOoo-2UOSEyNVbWTw,3470
20
+ routekitai/core/message.py,sha256=-nc3rPahQFADkeoA_Ipaq4ELPI14e23JAPIskIvM7js,3471
21
21
  routekitai/core/model.py,sha256=vghe66IRXmRO5vNtxQl_L2aVrODE_5Fyj0g_BLCaSMQ,3158
22
- routekitai/core/policies.py,sha256=uQYagWB3LPRiP_mE963H_o7mbt7QmAobLAH_ILIFbf4,13765
23
- routekitai/core/policy.py,sha256=94XiqPm2fKvW7Z8o_6YwrSQUm202kFZwXbKhKy5_PXk,2683
24
- routekitai/core/policy_adapter.py,sha256=Nv_gf7lo9hvOnv-ojRm6idaf-GDoju_Z6vApGTIRtqw,5130
25
- routekitai/core/runtime.py,sha256=nXfIbJRwDXMmi-V7PY78Pu-qMB-neato2uD79cLRrG4,59624
26
- routekitai/core/tool.py,sha256=lN_LtV5dHok3kokt8TUGtWhvkqVow__3JhlaGq7_fRw,4958
22
+ routekitai/core/policies.py,sha256=uNkeArb__aJdNCmdwVa8gWDZVTbSxW-MJHFgBInQFUw,13946
23
+ routekitai/core/policy.py,sha256=CscLNg2Z4U_XV-xC0mf7QlZN-QCJ3zewSz8uPE5NIR8,2814
24
+ routekitai/core/policy_adapter.py,sha256=GeVVQBfd4Bo3Ijzgcq8fdn1CRtC9XOVwb7f4SkfylYY,5887
25
+ routekitai/core/runtime.py,sha256=vOngcUlpJWq2t1ZgdJqQV2gChmlZBQtg9or6Frg3Bkk,61839
26
+ routekitai/core/tool.py,sha256=96zHRidFV8NX9x8fWvbNyT3Up2UL-0BjaNLBl_P79_o,4959
27
27
  routekitai/core/tools.py,sha256=9jye7xa1NcJnSARRarRAIMsbF6gmyI4DlyCCv7HuRYM,5215
28
28
  routekitai/evals/__init__.py,sha256=9J7kySk46qCOApOlIMvOsPSjc3_NzvxCSuZ8-OejBNU,336
29
29
  routekitai/evals/dataset.py,sha256=yivm0NKUjV91OY1jBXdueS64CusHZWnB_f-XyVRTDNA,2512
@@ -31,7 +31,7 @@ routekitai/evals/metrics.py,sha256=_sSkQxWpXVc_cC8FFzriFyNEQce8aGbx-q5rcqkesEM,3
31
31
  routekitai/evals/runner.py,sha256=cUpGjfgQy_biq2O1mOf9mZ61jMk6Y6G6Y7Cpv6RZuzQ,6980
32
32
  routekitai/graphs/__init__.py,sha256=jCmCWWtV29uDBy47U5OrBwwie6eqMC-uS-AGrXLuonM,274
33
33
  routekitai/graphs/executors.py,sha256=1wrZ5mXUMRf6TGbvfddUUm9h8ruyNjMJB6xlZDdRxh0,18329
34
- routekitai/graphs/graph.py,sha256=__Jl7YYqTLRTPOLDzePJAx4aiiCGRTLp34Q-s4aQ5Xc,6301
34
+ routekitai/graphs/graph.py,sha256=vX70acCsKWs2vv7ccuUWhqhDVB1fJ-L5tKMVhpFhQLM,6302
35
35
  routekitai/memory/__init__.py,sha256=Y4QrSbzzQuutlbfnTqOwBBp6Q0ODtcoVkvXOM6eERqk,343
36
36
  routekitai/memory/episodic.py,sha256=iGWpeWo5KikTBHhWZsk41YWuT12JJ139I7Dz54LTdhk,8166
37
37
  routekitai/memory/kv.py,sha256=3w883l7MpLmDz3VhX-fzQQXtxpZhElxgCJv3RczgzE8,785
@@ -47,18 +47,18 @@ routekitai/observability/exporters/__init__.py,sha256=g_YIeoJ_YWANEtETQgF7zEBNtW
47
47
  routekitai/observability/exporters/base.py,sha256=zhkCem3xXqKmrB38QsFaM1HAbxECPOyLoPuO5Jbd-nA,672
48
48
  routekitai/observability/exporters/jsonl.py,sha256=wBHgzfvBSksQQ_WLYXnPF9OH6_a7ldgbTlTUzTs0U7E,2621
49
49
  routekitai/observability/exporters/otel.py,sha256=Cl25rA1robwJVaOtoHRT2W3VTGvY3ibJ0eoqXQwbsb8,4249
50
- routekitai/providers/__init__.py,sha256=jE24VQsKG5nloM6xjdBazvptblaKRBTusTc42EW5km4,197
51
- routekitai/providers/anthropic.py,sha256=_Zo4tN4WQ6jZcmMD7WP4izhka4yT8qr0cnrSfApuQUM,7687
50
+ routekitai/providers/__init__.py,sha256=-43vXwysU7NNuluD2QcmNFNmlfKMgOq_5uCnWm6ptyY,277
51
+ routekitai/providers/anthropic.py,sha256=v8aSHz4zvCYW958tV-VjGN5joya8U4LCtSUJ86pNBfs,8634
52
52
  routekitai/providers/azure_openai.py,sha256=2iTvaLFKTseKy41E4D1EOT4NvwsxK5BSo5G0xaJjd7Y,8312
53
53
  routekitai/providers/local.py,sha256=Ii959ycdAOhnUucAlaBPCNP5YOxgFgRmuCbu_XibQwY,7693
54
54
  routekitai/providers/openai.py,sha256=ImTGXOiHyNtXUNF2tzBF4Dkv8r_aPrmQDUH6Cu7-zi4,12641
55
55
  routekitai/sandbox/__init__.py,sha256=erkRO6xnq7NcOmv_rV444IRJl0WmhRWoVX1Sx8XiqXg,352
56
56
  routekitai/sandbox/filesystem.py,sha256=5bLMhrutoJQVfOM49v1L1ykVpjFee50dERdyY4VaiGM,4744
57
57
  routekitai/sandbox/network.py,sha256=cFK--kZ88irTREWgTWhXDBDvynzq9XdHjFn_jGcF3HI,4668
58
- routekitai/sandbox/permissions.py,sha256=p2Kgqpe_HgfTv7_JEUVLGhFn5UbsFErkmtyzBqFucWk,2093
59
- routekitai-0.1.0.dist-info/licenses/LICENSE,sha256=TftL8ryIHKESApEmr5TBlRg5mengWfrzzjlr7nz46iE,1079
60
- routekitai-0.1.0.dist-info/METADATA,sha256=0Kf-5L9YrKiTseYOd1IJgBo3EnxbWnUQho_AhSCSPK4,10113
61
- routekitai-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
62
- routekitai-0.1.0.dist-info/entry_points.txt,sha256=pHUdSYPaXtmUY5LmHWKcgh2dR-t0DRrgUZ3AO0Pk9L0,56
63
- routekitai-0.1.0.dist-info/top_level.txt,sha256=lbYswJspf7-baehrsDZboTTxhrabTTPf4mGYC_jXKaY,11
64
- routekitai-0.1.0.dist-info/RECORD,,
58
+ routekitai/sandbox/permissions.py,sha256=5rGeF_4JcpetrsVYZuAw-S-rV5vsic1OSJAh0we-uDs,2094
59
+ routekitai-0.2.1.dist-info/licenses/LICENSE,sha256=TftL8ryIHKESApEmr5TBlRg5mengWfrzzjlr7nz46iE,1079
60
+ routekitai-0.2.1.dist-info/METADATA,sha256=goTgbouS7za77-MZnmXGGZWc4bSh_tmAIbzATdBbi24,11747
61
+ routekitai-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
62
+ routekitai-0.2.1.dist-info/entry_points.txt,sha256=pHUdSYPaXtmUY5LmHWKcgh2dR-t0DRrgUZ3AO0Pk9L0,56
63
+ routekitai-0.2.1.dist-info/top_level.txt,sha256=lbYswJspf7-baehrsDZboTTxhrabTTPf4mGYC_jXKaY,11
64
+ routekitai-0.2.1.dist-info/RECORD,,