runa-ai 0.1.0__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.
Files changed (85) hide show
  1. runa_ai-0.1.0/LICENSE +21 -0
  2. runa_ai-0.1.0/PKG-INFO +97 -0
  3. runa_ai-0.1.0/README.md +71 -0
  4. runa_ai-0.1.0/pyproject.toml +87 -0
  5. runa_ai-0.1.0/pyproject.toml.orig +76 -0
  6. runa_ai-0.1.0/src/runa/__init__.py +79 -0
  7. runa_ai-0.1.0/src/runa/_graph.py +63 -0
  8. runa_ai-0.1.0/src/runa/_models/__init__.py +33 -0
  9. runa_ai-0.1.0/src/runa/_models/anthropic.py +284 -0
  10. runa_ai-0.1.0/src/runa/_models/interface.py +91 -0
  11. runa_ai-0.1.0/src/runa/_models/multi_provider.py +113 -0
  12. runa_ai-0.1.0/src/runa/_models/openai_chatcompletions.py +177 -0
  13. runa_ai-0.1.0/src/runa/_types.py +166 -0
  14. runa_ai-0.1.0/src/runa/agent.py +493 -0
  15. runa_ai-0.1.0/src/runa/approval.py +32 -0
  16. runa_ai-0.1.0/src/runa/cache.py +155 -0
  17. runa_ai-0.1.0/src/runa/cli/__init__.py +1 -0
  18. runa_ai-0.1.0/src/runa/cli/_project.py +116 -0
  19. runa_ai-0.1.0/src/runa/cli/agents.py +82 -0
  20. runa_ai-0.1.0/src/runa/cli/chat.py +171 -0
  21. runa_ai-0.1.0/src/runa/cli/eval.py +56 -0
  22. runa_ai-0.1.0/src/runa/cli/generate.py +426 -0
  23. runa_ai-0.1.0/src/runa/cli/main.py +345 -0
  24. runa_ai-0.1.0/src/runa/cli/new.py +121 -0
  25. runa_ai-0.1.0/src/runa/cli/sessions.py +112 -0
  26. runa_ai-0.1.0/src/runa/cli/test.py +59 -0
  27. runa_ai-0.1.0/src/runa/cli/traces.py +43 -0
  28. runa_ai-0.1.0/src/runa/cli/ui.py +20 -0
  29. runa_ai-0.1.0/src/runa/compact.py +57 -0
  30. runa_ai-0.1.0/src/runa/db/__init__.py +5 -0
  31. runa_ai-0.1.0/src/runa/db/postgres.py +367 -0
  32. runa_ai-0.1.0/src/runa/db/redis.py +62 -0
  33. runa_ai-0.1.0/src/runa/db/sqlite.py +40 -0
  34. runa_ai-0.1.0/src/runa/embeddings.py +63 -0
  35. runa_ai-0.1.0/src/runa/eval/__init__.py +24 -0
  36. runa_ai-0.1.0/src/runa/eval/case.py +28 -0
  37. runa_ai-0.1.0/src/runa/eval/dataset.py +45 -0
  38. runa_ai-0.1.0/src/runa/eval/evaluate.py +78 -0
  39. runa_ai-0.1.0/src/runa/eval/evaluation/__init__.py +15 -0
  40. runa_ai-0.1.0/src/runa/eval/evaluation/core.py +40 -0
  41. runa_ai-0.1.0/src/runa/eval/evaluation/defaults.py +14 -0
  42. runa_ai-0.1.0/src/runa/eval/evaluation/deterministic.py +38 -0
  43. runa_ai-0.1.0/src/runa/eval/evaluation/semantic.py +240 -0
  44. runa_ai-0.1.0/src/runa/eval/judge.py +74 -0
  45. runa_ai-0.1.0/src/runa/eval/report.py +110 -0
  46. runa_ai-0.1.0/src/runa/eval/storage.py +152 -0
  47. runa_ai-0.1.0/src/runa/eval/tracing/__init__.py +5 -0
  48. runa_ai-0.1.0/src/runa/eval/tracing/adapter.py +78 -0
  49. runa_ai-0.1.0/src/runa/exceptions.py +168 -0
  50. runa_ai-0.1.0/src/runa/guardrail.py +333 -0
  51. runa_ai-0.1.0/src/runa/handoff.py +88 -0
  52. runa_ai-0.1.0/src/runa/knowledge.py +270 -0
  53. runa_ai-0.1.0/src/runa/lifecycle.py +204 -0
  54. runa_ai-0.1.0/src/runa/mcp.py +139 -0
  55. runa_ai-0.1.0/src/runa/memory.py +314 -0
  56. runa_ai-0.1.0/src/runa/result.py +75 -0
  57. runa_ai-0.1.0/src/runa/run.py +35 -0
  58. runa_ai-0.1.0/src/runa/run_config.py +29 -0
  59. runa_ai-0.1.0/src/runa/run_internal/__init__.py +15 -0
  60. runa_ai-0.1.0/src/runa/run_internal/agent_runner_helpers.py +150 -0
  61. runa_ai-0.1.0/src/runa/run_internal/guardrails.py +101 -0
  62. runa_ai-0.1.0/src/runa/run_internal/run_loop.py +477 -0
  63. runa_ai-0.1.0/src/runa/run_internal/spans.py +56 -0
  64. runa_ai-0.1.0/src/runa/run_internal/streaming.py +140 -0
  65. runa_ai-0.1.0/src/runa/run_internal/tool_execution.py +143 -0
  66. runa_ai-0.1.0/src/runa/run_state.py +293 -0
  67. runa_ai-0.1.0/src/runa/runner.py +84 -0
  68. runa_ai-0.1.0/src/runa/session.py +182 -0
  69. runa_ai-0.1.0/src/runa/stream_events.py +45 -0
  70. runa_ai-0.1.0/src/runa/this.py +28 -0
  71. runa_ai-0.1.0/src/runa/tool.py +166 -0
  72. runa_ai-0.1.0/src/runa/tracing/__init__.py +30 -0
  73. runa_ai-0.1.0/src/runa/tracing/config.py +178 -0
  74. runa_ai-0.1.0/src/runa/tracing/manual.py +101 -0
  75. runa_ai-0.1.0/src/runa/tracing/spans.py +45 -0
  76. runa_ai-0.1.0/src/runa/tracing/storage.py +175 -0
  77. runa_ai-0.1.0/src/runa/tracing/traces.py +120 -0
  78. runa_ai-0.1.0/src/runa/tracing/util.py +16 -0
  79. runa_ai-0.1.0/src/runa/web/__init__.py +5 -0
  80. runa_ai-0.1.0/src/runa/web/_html.py +163 -0
  81. runa_ai-0.1.0/src/runa/web/agents.py +46 -0
  82. runa_ai-0.1.0/src/runa/web/app.py +80 -0
  83. runa_ai-0.1.0/src/runa/web/evaluations.py +87 -0
  84. runa_ai-0.1.0/src/runa/web/sessions.py +50 -0
  85. runa_ai-0.1.0/src/runa/web/traces.py +141 -0
runa_ai-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Khalil Brahim Benyous
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
runa_ai-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: runa-ai
3
+ Version: 0.1.0
4
+ Summary: The application framework for agentic AI.
5
+ Author: Brahim Benyous
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Dist: anthropic>=0.75.0
9
+ Requires-Dist: graphviz>=0.21
10
+ Requires-Dist: httpx2>=2.12.0
11
+ Requires-Dist: mcp>=2.1.1
12
+ Requires-Dist: mkdocs-material>=9.7.7
13
+ Requires-Dist: pydantic>=2.9.0
14
+ Requires-Dist: pypdf>=6.0.0
15
+ Requires-Dist: sqlite-vec>=0.1.9
16
+ Requires-Dist: asyncpg>=0.30.0 ; extra == 'postgres'
17
+ Requires-Dist: pgvector>=0.3.6 ; extra == 'postgres'
18
+ Requires-Dist: redis>=5.2.0 ; extra == 'redis'
19
+ Requires-Dist: fastapi>=0.121.0 ; extra == 'ui'
20
+ Requires-Dist: uvicorn>=0.38.0 ; extra == 'ui'
21
+ Requires-Python: >=3.14
22
+ Provides-Extra: postgres
23
+ Provides-Extra: redis
24
+ Provides-Extra: ui
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Runa
28
+
29
+ ## What's Runa?
30
+
31
+ Runa is an opinionated Python framework for agentic AI. Instead of a wide
32
+ menu of configuration options, it gives you a small set of primitives,
33
+ with clear conventions:
34
+
35
+ - **Agents:** which are LLMs equipped with instructions and tools
36
+ - **Tools:** the actions an agent can take in the outside world, described clearly enough that the agent knows when and how to use them.
37
+ - **Guardrails:** checks that keep what goes into and comes out of an agent or tool within bounds.
38
+ - **Delegation:** how agents hand off work to each other, either passing a task along completely or asking for help and getting an answer back.
39
+ - **Context management:** what an agent remembers: session, memory, and domain knowledge.
40
+
41
+ Observability and evaluation come built in, not bolted on: every run is
42
+ traced automatically as a span tree, and evaluation grade
43
+ correctness with plain assertions or judged, dataset-driven scoring.
44
+
45
+
46
+ ## Getting Started
47
+
48
+ 1. Install [uv](https://docs.astral.sh/uv/), then Runa:
49
+
50
+ ```bash
51
+ uv venv --python 3.14
52
+ source .venv/bin/activate
53
+ uv add git+https://github.com/benybrahim/runa.git
54
+ ```
55
+
56
+ 2. Scaffold a new application:
57
+
58
+ ```bash
59
+ runa new myapp
60
+ ```
61
+
62
+ where `myapp` is the application name.
63
+
64
+
65
+ 3. Change directory to `myapp`, generate an agent, and talk to it:
66
+
67
+ ```bash
68
+ cd myapp
69
+ runa generate agent AssistantAgent --model gpt-5.4-nano
70
+ runa chat assistant_agent
71
+ ```
72
+
73
+ Run any subcommand with `--help` for options.
74
+
75
+
76
+ 4. Fill in the API key for whichever model you use in `.env`, and you're
77
+ running.
78
+
79
+
80
+ 5. Follow the guides to keep building your application. You may find the following resources handy:
81
+ * [Getting Started with Runa](https://benybrahim.github.io/runa/getting_started/)
82
+ * [Runa Guides](https://benybrahim.github.io/runa/guides/)
83
+ * [CLI Reference](https://benybrahim.github.io/runa/cli/)
84
+
85
+ ## Read the Zen of Runa
86
+
87
+ ```bash
88
+ python -c "from runa import this"
89
+ ```
90
+
91
+ ## Contributing
92
+
93
+ We encourage you to contribute to Runa! Check out [CONTRIBUTING.md](CONTRIBUTING.md)
94
+
95
+ ## License
96
+
97
+ Runa is released under the [MIT License](./LICENSE).
@@ -0,0 +1,71 @@
1
+ # Runa
2
+
3
+ ## What's Runa?
4
+
5
+ Runa is an opinionated Python framework for agentic AI. Instead of a wide
6
+ menu of configuration options, it gives you a small set of primitives,
7
+ with clear conventions:
8
+
9
+ - **Agents:** which are LLMs equipped with instructions and tools
10
+ - **Tools:** the actions an agent can take in the outside world, described clearly enough that the agent knows when and how to use them.
11
+ - **Guardrails:** checks that keep what goes into and comes out of an agent or tool within bounds.
12
+ - **Delegation:** how agents hand off work to each other, either passing a task along completely or asking for help and getting an answer back.
13
+ - **Context management:** what an agent remembers: session, memory, and domain knowledge.
14
+
15
+ Observability and evaluation come built in, not bolted on: every run is
16
+ traced automatically as a span tree, and evaluation grade
17
+ correctness with plain assertions or judged, dataset-driven scoring.
18
+
19
+
20
+ ## Getting Started
21
+
22
+ 1. Install [uv](https://docs.astral.sh/uv/), then Runa:
23
+
24
+ ```bash
25
+ uv venv --python 3.14
26
+ source .venv/bin/activate
27
+ uv add git+https://github.com/benybrahim/runa.git
28
+ ```
29
+
30
+ 2. Scaffold a new application:
31
+
32
+ ```bash
33
+ runa new myapp
34
+ ```
35
+
36
+ where `myapp` is the application name.
37
+
38
+
39
+ 3. Change directory to `myapp`, generate an agent, and talk to it:
40
+
41
+ ```bash
42
+ cd myapp
43
+ runa generate agent AssistantAgent --model gpt-5.4-nano
44
+ runa chat assistant_agent
45
+ ```
46
+
47
+ Run any subcommand with `--help` for options.
48
+
49
+
50
+ 4. Fill in the API key for whichever model you use in `.env`, and you're
51
+ running.
52
+
53
+
54
+ 5. Follow the guides to keep building your application. You may find the following resources handy:
55
+ * [Getting Started with Runa](https://benybrahim.github.io/runa/getting_started/)
56
+ * [Runa Guides](https://benybrahim.github.io/runa/guides/)
57
+ * [CLI Reference](https://benybrahim.github.io/runa/cli/)
58
+
59
+ ## Read the Zen of Runa
60
+
61
+ ```bash
62
+ python -c "from runa import this"
63
+ ```
64
+
65
+ ## Contributing
66
+
67
+ We encourage you to contribute to Runa! Check out [CONTRIBUTING.md](CONTRIBUTING.md)
68
+
69
+ ## License
70
+
71
+ Runa is released under the [MIT License](./LICENSE).
@@ -0,0 +1,87 @@
1
+ [project]
2
+ name = "runa-ai"
3
+ version = "0.1.0"
4
+ description = "The application framework for agentic AI."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14"
9
+ dependencies = [
10
+ "anthropic>=0.75.0",
11
+ "graphviz>=0.21",
12
+ "httpx2>=2.12.0",
13
+ "mcp>=2.1.1",
14
+ "mkdocs-material>=9.7.7",
15
+ "pydantic>=2.9.0",
16
+ "pypdf>=6.0.0",
17
+ "sqlite-vec>=0.1.9",
18
+ ]
19
+
20
+ [[project.authors]]
21
+ name = "Brahim Benyous"
22
+
23
+ [project.optional-dependencies]
24
+ postgres = [
25
+ "asyncpg>=0.30.0",
26
+ "pgvector>=0.3.6",
27
+ ]
28
+ redis = ["redis>=5.2.0"]
29
+ ui = [
30
+ "fastapi>=0.121.0",
31
+ "uvicorn>=0.38.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ runa = "runa.cli.main:main"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "pytest>=9.1.1",
40
+ "ruff>=0.16.4",
41
+ "pyright>=1.1.400",
42
+ "python-dotenv>=1.2.3",
43
+ "httpx>=0.28.0",
44
+ "runa[postgres,redis,ui]",
45
+ "mkdocs>=1.6.1",
46
+ ]
47
+
48
+ [build-system]
49
+ requires = ["uv_build >= 0.12.10, <0.13.0"]
50
+ build-backend = "uv_build"
51
+
52
+ [tool.uv.build-backend]
53
+ module-name = "runa"
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+
58
+ [tool.ruff]
59
+ line-length = 100
60
+ target-version = "py314"
61
+
62
+ [tool.ruff.lint]
63
+ select = [
64
+ "E",
65
+ "F",
66
+ "I",
67
+ "B",
68
+ "SIM",
69
+ "UP",
70
+ "D",
71
+ ]
72
+
73
+ [tool.ruff.lint.pydocstyle]
74
+ convention = "google"
75
+
76
+ [tool.ruff.format]
77
+ quote-style = "double"
78
+ indent-style = "space"
79
+ line-ending = "auto"
80
+
81
+ [tool.pyright]
82
+ include = [
83
+ "src",
84
+ "tests",
85
+ "examples",
86
+ ]
87
+ reportIncompatibleMethodOverride = false
@@ -0,0 +1,76 @@
1
+ [project]
2
+ name = "runa-ai"
3
+ version = "0.1.0"
4
+ authors = [
5
+ { name = "Brahim Benyous" },
6
+ ]
7
+ description = "The application framework for agentic AI."
8
+ readme = "README.md"
9
+ license = "MIT"
10
+ license-files = ["LICENSE"]
11
+ requires-python = ">=3.14"
12
+ dependencies = [
13
+ "anthropic>=0.75.0",
14
+ "graphviz>=0.21",
15
+ "httpx2>=2.12.0",
16
+ "mcp>=2.1.1",
17
+ "mkdocs-material>=9.7.7",
18
+ "pydantic>=2.9.0",
19
+ "pypdf>=6.0.0",
20
+ "sqlite-vec>=0.1.9",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ postgres = ["asyncpg>=0.30.0", "pgvector>=0.3.6"]
25
+ redis = ["redis>=5.2.0"]
26
+ ui = ["fastapi>=0.121.0", "uvicorn>=0.38.0"]
27
+
28
+ [project.scripts]
29
+ runa = "runa.cli.main:main"
30
+
31
+ [dependency-groups]
32
+ dev = [
33
+ "pytest>=9.1.1",
34
+ "ruff>=0.16.4",
35
+ "pyright>=1.1.400",
36
+ "python-dotenv>=1.2.3",
37
+ "httpx>=0.28.0",
38
+ "runa[postgres,redis,ui]",
39
+ "mkdocs>=1.6.1",
40
+ ]
41
+
42
+ [build-system]
43
+ requires = ["uv_build >= 0.12.10, <0.13.0"]
44
+ build-backend = "uv_build"
45
+
46
+ [tool.uv.build-backend]
47
+ module-name = "runa"
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py314"
55
+
56
+ [tool.ruff.lint]
57
+ select = [
58
+ "E", # pycodestyle errors
59
+ "F", # Pyflakes
60
+ "I", # isort
61
+ "B", # flake8-bugbear
62
+ "SIM", # flake8-simplify
63
+ "UP", # pyupgrade
64
+ "D", # docstring
65
+ ]
66
+ [tool.ruff.lint.pydocstyle]
67
+ convention = "google"
68
+
69
+ [tool.ruff.format]
70
+ quote-style = "double"
71
+ indent-style = "space"
72
+ line-ending = "auto"
73
+
74
+ [tool.pyright]
75
+ include = ["src", "tests", "examples"]
76
+ reportIncompatibleMethodOverride = false
@@ -0,0 +1,79 @@
1
+ """Runa: an opinionated framework for agentic AI."""
2
+
3
+ from runa.agent import Agent
4
+ from runa.approval import approval
5
+ from runa.cache import Cache, MemoryCache, SQLiteCache
6
+ from runa.eval import (
7
+ DEFAULT_THRESHOLDS,
8
+ Case,
9
+ CaseReport,
10
+ Dataset,
11
+ EvaluationResult,
12
+ Report,
13
+ Status,
14
+ )
15
+ from runa.guardrail import guardrail
16
+ from runa.knowledge import Knowledge, KnowledgeMatch
17
+ from runa.lifecycle import AgentHooks, LoggingAgentHooks, LoggingRunHooks, RunHooks
18
+ from runa.mcp import MCPServer, MCPServerStdio, MCPServerStreamableHttp
19
+ from runa.memory import Memory, MemoryMatch
20
+ from runa.result import RunResult, RunResultStreaming
21
+ from runa.run import Run
22
+ from runa.run_config import RunConfig
23
+ from runa.run_state import Interruption, RunState
24
+ from runa.runner import Runner
25
+ from runa.session import SQLiteSession
26
+ from runa.stream_events import (
27
+ AgentUpdatedStreamEvent,
28
+ RawResponsesStreamEvent,
29
+ RunItemStreamEvent,
30
+ StreamEvent,
31
+ )
32
+ from runa.tool import tool
33
+ from runa.tracing import ConsoleExporter, Span, SQLiteExporter, Trace, TraceExporter, observe
34
+
35
+ __all__ = [
36
+ "DEFAULT_THRESHOLDS",
37
+ "Agent",
38
+ "AgentHooks",
39
+ "AgentUpdatedStreamEvent",
40
+ "Cache",
41
+ "Case",
42
+ "CaseReport",
43
+ "ConsoleExporter",
44
+ "Dataset",
45
+ "EvaluationResult",
46
+ "Interruption",
47
+ "Knowledge",
48
+ "KnowledgeMatch",
49
+ "LoggingAgentHooks",
50
+ "LoggingRunHooks",
51
+ "MCPServer",
52
+ "MCPServerStdio",
53
+ "MCPServerStreamableHttp",
54
+ "Memory",
55
+ "MemoryCache",
56
+ "MemoryMatch",
57
+ "RawResponsesStreamEvent",
58
+ "Report",
59
+ "Run",
60
+ "RunConfig",
61
+ "RunHooks",
62
+ "RunItemStreamEvent",
63
+ "RunResult",
64
+ "RunResultStreaming",
65
+ "RunState",
66
+ "Runner",
67
+ "SQLiteCache",
68
+ "SQLiteExporter",
69
+ "SQLiteSession",
70
+ "Span",
71
+ "Status",
72
+ "StreamEvent",
73
+ "Trace",
74
+ "TraceExporter",
75
+ "approval",
76
+ "guardrail",
77
+ "observe",
78
+ "tool",
79
+ ]
@@ -0,0 +1,63 @@
1
+ """_graph.py: `draw_graph`, a small DOT-graph generator behind `Agent.graph`.
2
+
3
+ Not a port of `agents.extensions.visualization` -- just enough to show an agent's own tools and
4
+ handoffs (recursively) as a Graphviz digraph, since that's all `Agent.graph` ever promised.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from graphviz import Digraph, Source
13
+
14
+
15
+ def _agent_node_id(agent: Any) -> str:
16
+ return f"agent_{id(agent)}"
17
+
18
+
19
+ def _add_agent(dot: Digraph, agent: Any, seen: set[int]) -> None:
20
+ if id(agent) in seen:
21
+ return
22
+ seen.add(id(agent))
23
+ node_id = _agent_node_id(agent)
24
+ dot.node(node_id, agent.name, shape="ellipse", style="filled", fillcolor="lightblue")
25
+
26
+ for tool in getattr(agent, "tools", []) or []:
27
+ tool_id = f"tool_{id(tool)}"
28
+ dot.node(
29
+ tool_id,
30
+ getattr(tool, "name", str(tool)),
31
+ shape="box",
32
+ style="filled",
33
+ fillcolor="lightyellow",
34
+ )
35
+ dot.edge(node_id, tool_id)
36
+
37
+ for server in getattr(agent, "mcp_servers", []) or []:
38
+ server_id = f"mcp_{id(server)}"
39
+ dot.node(
40
+ server_id,
41
+ getattr(server, "name", "mcp"),
42
+ shape="box",
43
+ style="filled,dashed",
44
+ fillcolor="lightgrey",
45
+ )
46
+ dot.edge(node_id, server_id)
47
+
48
+ for handoff in getattr(agent, "handoffs", []) or []:
49
+ target = getattr(handoff, "agent", handoff)
50
+ dot.edge(node_id, _agent_node_id(target), style="dashed")
51
+ _add_agent(dot, target, seen)
52
+
53
+
54
+ def draw_graph(agent: Any) -> Source:
55
+ """Render `agent`, and its tools/handoffs/MCP servers (recursively), as a Graphviz `Source`."""
56
+ from graphviz import Digraph
57
+
58
+ dot = Digraph()
59
+ _add_agent(dot, agent, set())
60
+ return dot.unflatten(stagger=3)
61
+
62
+
63
+ __all__ = ["draw_graph"]
@@ -0,0 +1,33 @@
1
+ """`runa._models`: Runa's own model provider — two backends, no `openai-agents`, no `openai` SDK.
2
+
3
+ `ModelProvider.get_model` maps a model name's prefix to a backend. A name starting with `claude`
4
+ goes through `AnthropicModel`, talking to Anthropic's own SDK directly — Anthropic's Messages API
5
+ isn't chat-completions-shaped, so it's the one backend that needs real translation. Everything else
6
+ (`gpt-*`, `gemini-*`, `llama-*`, `deepseek-*`, `qwen-*`, or an unrecognized/bare name) goes through
7
+ `OpenAICompatibleModel`, which speaks the chat-completions wire format that OpenAI, Gemini, Llama,
8
+ DeepSeek, and Qwen all share — over plain HTTP via `httpx2`, not the `openai` package.
9
+
10
+ Conversation items are plain chat-completions-shaped message dicts everywhere in Runa (see
11
+ `runa._types.TResponseInputItem`); `AnthropicModel` is the only place that ever converts away from
12
+ that shape. Tools/handoffs are read structurally here (`.name`/`.description`/`.params_json_schema`
13
+ for a tool, `.tool_name`/`.tool_description` for a handoff) rather than importing their concrete
14
+ types, so this package has no dependency on `runa.tool`/`runa.handoff`.
15
+
16
+ Split by concern: `interface` (the `Model` protocol, `StreamDelta`, shared wire-format helpers),
17
+ `openai_chatcompletions` (the chat-completions backend), `anthropic` (the Claude backend), and
18
+ `multi_provider` (`ModelProvider`, routing a model name to one of the two).
19
+ """
20
+
21
+ from runa._models.anthropic import AnthropicModel
22
+ from runa._models.anthropic import _anthropic_deltas as _anthropic_deltas
23
+ from runa._models.anthropic import _check_plain_text_output as _check_plain_text_output
24
+ from runa._models.anthropic import _to_anthropic_messages as _to_anthropic_messages
25
+ from runa._models.anthropic import _to_anthropic_tool as _to_anthropic_tool
26
+ from runa._models.anthropic import _to_anthropic_tool_choice as _to_anthropic_tool_choice
27
+ from runa._models.anthropic import _to_chat_message as _to_chat_message
28
+ from runa._models.anthropic import _to_usage as _to_usage
29
+ from runa._models.interface import Model, StreamDelta
30
+ from runa._models.multi_provider import ModelProvider
31
+ from runa._models.openai_chatcompletions import OpenAICompatibleModel
32
+
33
+ __all__ = ["AnthropicModel", "Model", "ModelProvider", "OpenAICompatibleModel", "StreamDelta"]