RouteKitAI 0.2.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.2.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 (
@@ -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.
@@ -543,6 +543,14 @@ class Runtime(BaseModel):
543
543
  output_message = Message.assistant(final_response.content)
544
544
  messages.append(output_message)
545
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
+
546
554
  # Import here to avoid circular import
547
555
  from routekitai.core.agent import RunResult
548
556
 
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RouteKitAI
3
- Version: 0.2.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
@@ -202,6 +207,7 @@ asyncio.run(main())
202
207
  Check out the [`examples/`](examples/) directory for complete examples:
203
208
 
204
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`)
205
211
  - **[Graph Orchestration](examples/graph_agent.py)** / **[Graph Policy](examples/graph_policy.py)**: Multi-agent workflows
206
212
  - **[Supervisor Pattern](examples/supervisor_agent.py)** / **[Supervisor Policy](examples/supervisor_policy.py)**: Supervisor delegating to sub-agents
207
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
@@ -305,8 +311,29 @@ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) and:
305
311
 
306
312
  ## 📋 Requirements
307
313
 
308
- - Python 3.11 or higher
309
- - 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
+ ```
310
337
 
311
338
  ## 📄 License
312
339
 
@@ -1,4 +1,4 @@
1
- routekitai/__init__.py,sha256=tXiazo0NHxFeWcHWsKLCoDMn_fFBH_RQsF-vGeqTWpo,1091
1
+ routekitai/__init__.py,sha256=155KUL2u8F-wn5wV7oT4bwzpJ84l2OWhmsU-hk-MM3A,1091
2
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
@@ -19,10 +19,10 @@ routekitai/core/hooks.py,sha256=lnp5tK_amFDUbkYWxvWnIDwQ2uI4em2Go0KLb1QtD7c,5644
19
19
  routekitai/core/memory.py,sha256=szDTIr3tCgM4aeFO19tOlGj6a8lLPBHTv4KByeUxC2k,1429
20
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=bSC9v7clYZLxkHsiAgneCGEQfXs7Jzvpd_rK8lFQutE,13872
22
+ routekitai/core/policies.py,sha256=uNkeArb__aJdNCmdwVa8gWDZVTbSxW-MJHFgBInQFUw,13946
23
23
  routekitai/core/policy.py,sha256=CscLNg2Z4U_XV-xC0mf7QlZN-QCJ3zewSz8uPE5NIR8,2814
24
24
  routekitai/core/policy_adapter.py,sha256=GeVVQBfd4Bo3Ijzgcq8fdn1CRtC9XOVwb7f4SkfylYY,5887
25
- routekitai/core/runtime.py,sha256=rgSD88vxrLV0ZhhuF-q8jsuQprjAqm1z3iS-_lre0zk,61402
25
+ routekitai/core/runtime.py,sha256=vOngcUlpJWq2t1ZgdJqQV2gChmlZBQtg9or6Frg3Bkk,61839
26
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
@@ -47,8 +47,8 @@ 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
@@ -56,9 +56,9 @@ routekitai/sandbox/__init__.py,sha256=erkRO6xnq7NcOmv_rV444IRJl0WmhRWoVX1Sx8XiqX
56
56
  routekitai/sandbox/filesystem.py,sha256=5bLMhrutoJQVfOM49v1L1ykVpjFee50dERdyY4VaiGM,4744
57
57
  routekitai/sandbox/network.py,sha256=cFK--kZ88irTREWgTWhXDBDvynzq9XdHjFn_jGcF3HI,4668
58
58
  routekitai/sandbox/permissions.py,sha256=5rGeF_4JcpetrsVYZuAw-S-rV5vsic1OSJAh0we-uDs,2094
59
- routekitai-0.2.0.dist-info/licenses/LICENSE,sha256=TftL8ryIHKESApEmr5TBlRg5mengWfrzzjlr7nz46iE,1079
60
- routekitai-0.2.0.dist-info/METADATA,sha256=XS91zXumQ49sZuoJHxXX4kRu9FK5CC-5mRe0D671D8g,10592
61
- routekitai-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
62
- routekitai-0.2.0.dist-info/entry_points.txt,sha256=pHUdSYPaXtmUY5LmHWKcgh2dR-t0DRrgUZ3AO0Pk9L0,56
63
- routekitai-0.2.0.dist-info/top_level.txt,sha256=lbYswJspf7-baehrsDZboTTxhrabTTPf4mGYC_jXKaY,11
64
- routekitai-0.2.0.dist-info/RECORD,,
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,,