agentx-kit 0.5.1__tar.gz → 0.7.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.
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/PKG-INFO +2 -2
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/pyproject.toml +2 -2
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/__init__.py +1 -1
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/cache.py +6 -3
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/cli.py +3 -1
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/build.py +5 -4
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/generator.py +38 -5
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/spec.py +12 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/README.md.j2 +69 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/ci.yml.j2 +9 -3
- agentx_kit-0.7.0/src/agentx/scaffold/templates/env.example.j2 +25 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/evals/run_evals.py.j2 +3 -9
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/mcp_servers.json.j2 +1 -1
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/agents.py.j2 +21 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/crew.py.j2 +23 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/graph.py.j2 +172 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/main.py.j2 +45 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/memory.py.j2 +25 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/nodes.py.j2 +219 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/rag.py.j2 +15 -4
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/server.py.j2 +101 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/state.py.j2 +17 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/tasks.py.j2 +18 -0
- agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/tools.py.j2 +26 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/wizard.py +26 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_enterprise.py +2 -1
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_scaffold.py +15 -5
- agentx_kit-0.5.1/src/agentx/scaffold/templates/README.md.j2 +0 -46
- agentx_kit-0.5.1/src/agentx/scaffold/templates/env.example.j2 +0 -12
- agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/agents.py.j2 +0 -77
- agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/main.py.j2 +0 -87
- agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/memory.py.j2 +0 -17
- agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/server.py.j2 +0 -85
- agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/tools.py.j2 +0 -16
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.agentx/llm_cache.sqlite +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.claude-plugin/marketplace.json +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.github/workflows/publish.yml +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.gitignore +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/DESIGN.md +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/LICENSE +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/README.md +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/RESEARCH.md +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/examples/README.md +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/examples/demo_local.sh +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/examples/demo_mcp.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/.claude-plugin/plugin.json +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/.mcp.json +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/README.md +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/commands/new-agent.md +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/.vscodeignore +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/README.md +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/extension.js +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/package.json +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/config.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/recommend.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/server.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/dashboard/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/dashboard/app.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/frameworks/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/frameworks/crewai_agent.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/frameworks/langchain_agent.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/guardrails.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/analyze.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/log.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/optimize.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/tokens.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/memory/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/memory/store.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/observability.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/prompts/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/prompts/templates.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/base.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/factory.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/registry.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/rag/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/rag/pipeline.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/reliability.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/prompts_store.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/Dockerfile.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/docker-compose.yml.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/dockerignore.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/evals/dataset.json.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/gitignore.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/__init__.py.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/config.py.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/guardrails.py.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/observability.py.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/prompts.py.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pyproject.toml.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/skills_seed.json.j2 +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/skills/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/skills/registry.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/structured.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/tools/__init__.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/tools/builtin.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/tools/mcp.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_cache.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_connector.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_insights.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_prompts.py +0 -0
- {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_providers.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: An open-source, provider-agnostic agentic framework + interactive project scaffolder for LangChain and CrewAI. Pick your LLM provider, agents, RAG, memory, MCP tools and skills — generate a ready-to-run uv project.
|
|
5
5
|
Project-URL: Homepage, https://github.com/muhammadyahiya/agentx-kit
|
|
6
6
|
Project-URL: Repository, https://github.com/muhammadyahiya/agentx-kit
|
|
7
7
|
Project-URL: Issues, https://github.com/muhammadyahiya/agentx-kit/issues
|
|
8
|
-
Author:
|
|
8
|
+
Author: OptimumAI
|
|
9
9
|
License: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: agents,azure,bedrock,crewai,gemini,langchain,llm,mcp,openrouter,rag,scaffold,vertex
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
# PyPI distribution name (import name + CLI stay `agentx`; `agentx` was taken).
|
|
3
3
|
name = "agentx-kit"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.7.0"
|
|
5
5
|
description = "An open-source, provider-agnostic agentic framework + interactive project scaffolder for LangChain and CrewAI. Pick your LLM provider, agents, RAG, memory, MCP tools and skills — generate a ready-to-run uv project."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.10,<3.14"
|
|
8
8
|
license = { text = "MIT" }
|
|
9
|
-
authors = [{ name = "
|
|
9
|
+
authors = [{ name = "OptimumAI" }]
|
|
10
10
|
keywords = ["agents", "llm", "langchain", "crewai", "rag", "mcp", "scaffold", "openrouter", "bedrock", "vertex", "azure", "gemini"]
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Development Status :: 4 - Beta",
|
|
@@ -23,6 +23,8 @@ import time
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import Any
|
|
25
25
|
|
|
26
|
+
from langchain_core.caches import BaseCache
|
|
27
|
+
|
|
26
28
|
_DEFAULT_PATH = ".agentx/llm_cache.sqlite"
|
|
27
29
|
_lock = threading.Lock()
|
|
28
30
|
|
|
@@ -39,11 +41,12 @@ def _model_from_llm_string(llm_string: str) -> str:
|
|
|
39
41
|
return "gpt-4o-mini"
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
class AgentXCache:
|
|
44
|
+
class AgentXCache(BaseCache):
|
|
43
45
|
"""A LangChain ``BaseCache`` backed by SQLite, with TTL + savings stats.
|
|
44
46
|
|
|
45
|
-
Implements ``lookup``/``update
|
|
46
|
-
``
|
|
47
|
+
Implements ``lookup``/``update``/``clear``; the async ``alookup``/``aupdate``
|
|
48
|
+
paths are inherited from ``BaseCache`` (which runs the sync methods in an
|
|
49
|
+
executor), so it works under both ``invoke`` and ``ainvoke``.
|
|
47
50
|
"""
|
|
48
51
|
|
|
49
52
|
def __init__(self, path: str | Path = _DEFAULT_PATH, ttl: int | None = None):
|
|
@@ -153,6 +153,7 @@ def new(
|
|
|
153
153
|
provider: str = typer.Option("openai", help="Provider id (with --yes)."),
|
|
154
154
|
model: str = typer.Option("", help="Model id (with --yes; blank = provider default)."),
|
|
155
155
|
agents: int = typer.Option(1, help="Number of agents (with --yes)."),
|
|
156
|
+
orchestration: str = typer.Option("supervisor", help="supervisor|sequential|parallel — how agents connect (with --yes, only for LangGraph with >1 agents)."),
|
|
156
157
|
prompt: str = typer.Option("", "--prompt", "-p", help="System prompt for the first agent (with --yes)."),
|
|
157
158
|
role: str = typer.Option("Helpful Assistant", help="Role for the first agent (with --yes)."),
|
|
158
159
|
goal: str = typer.Option("Help the user accomplish their task accurately.", help="Goal for the first agent (with --yes)."),
|
|
@@ -187,7 +188,8 @@ def new(
|
|
|
187
188
|
agent_specs.append(AgentSpec(name=a_name))
|
|
188
189
|
spec = ProjectSpec(
|
|
189
190
|
name=name or "my-agent", framework=framework, provider=provider, model=model,
|
|
190
|
-
agents=agent_specs,
|
|
191
|
+
agents=agent_specs, orchestration=orchestration,
|
|
192
|
+
use_rag=rag, memory=memory, use_mcp=mcp, use_skills=skills,
|
|
191
193
|
prompt_style="custom" if prompt else "default",
|
|
192
194
|
observability=observability, guardrails=guardrails, serve=serve,
|
|
193
195
|
docker=docker, ci=ci, evals=evals,
|
|
@@ -74,10 +74,11 @@ def build_project_from_statement(
|
|
|
74
74
|
# Collect a compact view for the calling LLM.
|
|
75
75
|
tree = sorted(str(p.relative_to(root)) for p in root.glob("**/*") if p.is_file())
|
|
76
76
|
pkg = spec.package
|
|
77
|
-
key_paths = [
|
|
78
|
-
|
|
79
|
-
f"src/{pkg}/
|
|
80
|
-
|
|
77
|
+
key_paths = ["pyproject.toml", "prompts.json", "agentx.json", f"src/{pkg}/main.py"]
|
|
78
|
+
if spec.framework == "langgraph":
|
|
79
|
+
key_paths += [f"src/{pkg}/graph.py", f"src/{pkg}/state.py", f"src/{pkg}/nodes.py"]
|
|
80
|
+
else:
|
|
81
|
+
key_paths += [f"src/{pkg}/crew.py", f"src/{pkg}/agents.py", f"src/{pkg}/tasks.py"]
|
|
81
82
|
if spec.serve:
|
|
82
83
|
key_paths.append(f"src/{pkg}/server.py")
|
|
83
84
|
key_files = {}
|
|
@@ -18,8 +18,8 @@ from .spec import ProjectSpec
|
|
|
18
18
|
|
|
19
19
|
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
|
20
20
|
|
|
21
|
-
# (template, output-relative-path
|
|
22
|
-
|
|
21
|
+
# (template, output-relative-path) — output paths use {pkg} placeholder.
|
|
22
|
+
_COMMON_FILES: list[tuple[str, str]] = [
|
|
23
23
|
("pyproject.toml.j2", "pyproject.toml"),
|
|
24
24
|
("README.md.j2", "README.md"),
|
|
25
25
|
("env.example.j2", ".env.example"),
|
|
@@ -27,10 +27,24 @@ _FILE_PLAN: list[tuple[str, str]] = [
|
|
|
27
27
|
("pkg/__init__.py.j2", "src/{pkg}/__init__.py"),
|
|
28
28
|
("pkg/config.py.j2", "src/{pkg}/config.py"),
|
|
29
29
|
("pkg/prompts.py.j2", "src/{pkg}/prompts.py"),
|
|
30
|
-
("pkg/agents.py.j2", "src/{pkg}/agents.py"),
|
|
31
30
|
("pkg/main.py.j2", "src/{pkg}/main.py"),
|
|
32
31
|
]
|
|
33
32
|
|
|
33
|
+
# A real LangGraph project: explicit state + nodes + graph + tool assembly.
|
|
34
|
+
_LANGGRAPH_FILES: list[tuple[str, str]] = [
|
|
35
|
+
("pkg/state.py.j2", "src/{pkg}/state.py"),
|
|
36
|
+
("pkg/tools.py.j2", "src/{pkg}/tools.py"),
|
|
37
|
+
("pkg/nodes.py.j2", "src/{pkg}/nodes.py"),
|
|
38
|
+
("pkg/graph.py.j2", "src/{pkg}/graph.py"),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# A real CrewAI project: agents + tasks + crew.
|
|
42
|
+
_CREWAI_FILES: list[tuple[str, str]] = [
|
|
43
|
+
("pkg/agents.py.j2", "src/{pkg}/agents.py"),
|
|
44
|
+
("pkg/tasks.py.j2", "src/{pkg}/tasks.py"),
|
|
45
|
+
("pkg/crew.py.j2", "src/{pkg}/crew.py"),
|
|
46
|
+
]
|
|
47
|
+
|
|
34
48
|
|
|
35
49
|
@dataclass
|
|
36
50
|
class GenerationResult:
|
|
@@ -70,6 +84,8 @@ def _context(spec: ProjectSpec) -> dict:
|
|
|
70
84
|
"provider_env": list(provider_spec.env_vars),
|
|
71
85
|
"extras": _extras(spec),
|
|
72
86
|
"extras_str": ",".join(_extras(spec)),
|
|
87
|
+
"multi_agent": len(spec.agents) > 1,
|
|
88
|
+
"orchestration": spec.orchestration,
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
|
|
@@ -85,12 +101,13 @@ def _env() -> Environment:
|
|
|
85
101
|
|
|
86
102
|
def _conditional_files(spec: ProjectSpec) -> list[tuple[str, str]]:
|
|
87
103
|
plan: list[tuple[str, str]] = []
|
|
104
|
+
# Framework-specific core (real project structure).
|
|
105
|
+
plan += _LANGGRAPH_FILES if spec.framework == "langgraph" else _CREWAI_FILES
|
|
88
106
|
if spec.use_rag:
|
|
89
107
|
plan.append(("pkg/rag.py.j2", "src/{pkg}/rag.py"))
|
|
90
108
|
if spec.needs_memory:
|
|
91
109
|
plan.append(("pkg/memory.py.j2", "src/{pkg}/memory.py"))
|
|
92
110
|
if spec.use_mcp:
|
|
93
|
-
plan.append(("pkg/tools.py.j2", "src/{pkg}/tools.py"))
|
|
94
111
|
plan.append(("mcp_servers.json.j2", "mcp_servers.json"))
|
|
95
112
|
if spec.use_skills:
|
|
96
113
|
plan.append(("skills_seed.json.j2", "data/skills/star-method.json"))
|
|
@@ -123,6 +140,7 @@ def _write_manifest(target: Path, spec: ProjectSpec) -> Path:
|
|
|
123
140
|
"model": spec.model or get_spec(spec.provider).default_model,
|
|
124
141
|
"python_version": ">=3.10,<3.14",
|
|
125
142
|
"agents": [a.name for a in spec.agents],
|
|
143
|
+
"orchestration": spec.orchestration,
|
|
126
144
|
"features": {
|
|
127
145
|
"rag": spec.use_rag,
|
|
128
146
|
"memory": spec.memory,
|
|
@@ -156,13 +174,28 @@ def generate_project(spec: ProjectSpec, target_dir: str | Path, overwrite: bool
|
|
|
156
174
|
written: list[Path] = []
|
|
157
175
|
messages: list[str] = []
|
|
158
176
|
|
|
159
|
-
for template_name, out_rel in
|
|
177
|
+
for template_name, out_rel in _COMMON_FILES + _conditional_files(spec):
|
|
160
178
|
out_path = target / out_rel.format(pkg=spec.package)
|
|
161
179
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
162
180
|
rendered = env.get_template(template_name).render(**ctx)
|
|
163
181
|
out_path.write_text(rendered, encoding="utf-8")
|
|
164
182
|
written.append(out_path)
|
|
165
183
|
|
|
184
|
+
# Seed a knowledge/ directory when RAG or the MCP filesystem server needs one
|
|
185
|
+
# (the restricted MCP server points at ./knowledge and RAG indexes it).
|
|
186
|
+
if spec.use_rag or spec.use_mcp:
|
|
187
|
+
knowledge_dir = target / "knowledge"
|
|
188
|
+
knowledge_dir.mkdir(parents=True, exist_ok=True)
|
|
189
|
+
seed = knowledge_dir / "README.md"
|
|
190
|
+
if not seed.exists():
|
|
191
|
+
seed.write_text(
|
|
192
|
+
f"# {spec.slug} knowledge base\n\n"
|
|
193
|
+
"Drop `.txt` / `.md` files here. They are indexed for RAG"
|
|
194
|
+
" and exposed (read-only) to the MCP filesystem tool.\n",
|
|
195
|
+
encoding="utf-8",
|
|
196
|
+
)
|
|
197
|
+
written.append(seed)
|
|
198
|
+
|
|
166
199
|
# The prompt source of truth — edited by hand or via `agentx prompt`.
|
|
167
200
|
written.append(prompts_store.write_prompts(target, spec))
|
|
168
201
|
# A single declarative manifest of the project (à la langgraph.json).
|
|
@@ -15,6 +15,12 @@ Framework = Literal["langgraph", "crewai"]
|
|
|
15
15
|
MemoryMode = Literal["none", "short", "long", "both"]
|
|
16
16
|
PromptStyle = Literal["default", "custom"]
|
|
17
17
|
|
|
18
|
+
# How multiple agents are wired together (LangGraph only; CrewAI always uses sequential crew).
|
|
19
|
+
# supervisor — an LLM router decides which worker acts next (dynamic, context-aware)
|
|
20
|
+
# sequential — agents run in order: agent_1 → agent_2 → … (pipeline / chain-of-thought)
|
|
21
|
+
# parallel — all agents handle the same input simultaneously; results are merged
|
|
22
|
+
OrchestrationMode = Literal["supervisor", "sequential", "parallel"]
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
def to_snake(name: str) -> str:
|
|
20
26
|
s = re.sub(r"[^0-9a-zA-Z]+", "_", name.strip().lower()).strip("_")
|
|
@@ -40,6 +46,8 @@ class ProjectSpec(BaseModel):
|
|
|
40
46
|
provider: str = "openai"
|
|
41
47
|
model: str = "" # blank → provider default
|
|
42
48
|
agents: list[AgentSpec] = Field(default_factory=lambda: [AgentSpec()])
|
|
49
|
+
# How agents are connected (only meaningful when len(agents) > 1 and framework == langgraph).
|
|
50
|
+
orchestration: OrchestrationMode = "supervisor"
|
|
43
51
|
use_rag: bool = False
|
|
44
52
|
memory: MemoryMode = "none"
|
|
45
53
|
use_mcp: bool = False
|
|
@@ -84,3 +92,7 @@ class ProjectSpec(BaseModel):
|
|
|
84
92
|
@property
|
|
85
93
|
def use_long_memory(self) -> bool:
|
|
86
94
|
return self.memory in ("long", "both")
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def multi_agent(self) -> bool:
|
|
98
|
+
return len(self.agents) > 1
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# {{ spec.slug }}
|
|
2
|
+
|
|
3
|
+
Agentic app generated by **AgentX**.
|
|
4
|
+
|
|
5
|
+
- **Framework:** {{ spec.framework }}
|
|
6
|
+
- **Provider:** {{ provider_label }} (`{{ model }}`)
|
|
7
|
+
- **Agents:** {% for a in spec.agents %}`{{ a.name }}`{% if not loop.last %}, {% endif %}{% endfor %}
|
|
8
|
+
|
|
9
|
+
- **RAG:** {{ "yes" if spec.use_rag else "no" }}
|
|
10
|
+
- **Memory:** {{ spec.memory }}
|
|
11
|
+
- **MCP tools:** {{ "yes" if spec.use_mcp else "no" }}
|
|
12
|
+
- **Skills:** {{ "yes" if spec.use_skills else "no" }}
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
```bash
|
|
16
|
+
uv sync # install dependencies into .venv
|
|
17
|
+
cp .env.example .env # then fill in your credentials
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
```bash
|
|
22
|
+
uv run {{ spec.slug }}
|
|
23
|
+
# or:
|
|
24
|
+
uv run python -m {{ pkg }}.main
|
|
25
|
+
{% if spec.serve %}# API server:
|
|
26
|
+
uv run uvicorn {{ pkg }}.server:app --reload
|
|
27
|
+
{% endif %}
|
|
28
|
+
```
|
|
29
|
+
{% if spec.framework == 'langgraph' %}
|
|
30
|
+
Uses LangGraph's dev server too: `uvx langgraph dev` (the compiled graph is
|
|
31
|
+
`{{ pkg }}.graph:graph`).
|
|
32
|
+
|
|
33
|
+
## Layout (LangGraph)
|
|
34
|
+
```
|
|
35
|
+
src/{{ pkg }}/
|
|
36
|
+
├── config.py # provider/model selection (pydantic-settings)
|
|
37
|
+
├── prompts.py # loads prompts.json (edit prompts there or via `agentx prompt`)
|
|
38
|
+
├── state.py # AgentState — the typed graph state (messages + reducers)
|
|
39
|
+
├── tools.py # tool assembly (web search{% if spec.use_rag %} + knowledge base{% endif %}{% if spec.use_mcp %} + MCP{% endif %})
|
|
40
|
+
├── nodes.py # graph nodes ({% if multi_agent %}supervisor + workers{% else %}the agent model call{% endif %})
|
|
41
|
+
├── graph.py # StateGraph workflow + compiled `graph` + run_text()
|
|
42
|
+
{% if spec.use_rag %}├── rag.py # knowledge base / retriever tool
|
|
43
|
+
{% endif %}
|
|
44
|
+
{% if spec.needs_memory %}├── memory.py # conversation / long-term memory helpers
|
|
45
|
+
{% endif %}
|
|
46
|
+
{% if spec.serve %}├── server.py # FastAPI (REST + SSE)
|
|
47
|
+
{% endif %}
|
|
48
|
+
└── main.py # interactive REPL
|
|
49
|
+
```
|
|
50
|
+
{% else %}
|
|
51
|
+
## Layout (CrewAI)
|
|
52
|
+
```
|
|
53
|
+
src/{{ pkg }}/
|
|
54
|
+
├── config.py # provider/model selection (pydantic-settings)
|
|
55
|
+
├── prompts.py # loads prompts.json
|
|
56
|
+
├── agents.py # CrewAI Agent definitions
|
|
57
|
+
├── tasks.py # Task definitions
|
|
58
|
+
├── crew.py # Crew assembly + run_text()
|
|
59
|
+
{% if spec.serve %}├── server.py # FastAPI (REST + SSE)
|
|
60
|
+
{% endif %}
|
|
61
|
+
└── main.py # interactive REPL
|
|
62
|
+
```
|
|
63
|
+
{% endif %}
|
|
64
|
+
{% if spec.use_rag %}
|
|
65
|
+
Add `.txt`/`.md` files under `knowledge/` to populate the RAG index.
|
|
66
|
+
{% endif %}
|
|
67
|
+
{% if spec.use_mcp %}
|
|
68
|
+
Edit `mcp_servers.json` to configure your MCP servers.
|
|
69
|
+
{% endif %}
|
|
@@ -33,9 +33,15 @@ jobs:
|
|
|
33
33
|
- name: Install
|
|
34
34
|
run: uv pip install --system -e .
|
|
35
35
|
- name: Run eval gate
|
|
36
|
+
# Non-blocking: the eval harness needs a reachable model (an API key or a
|
|
37
|
+
# local model server). Add your provider secret(s) below and drop
|
|
38
|
+
# `continue-on-error` to make this a hard CI gate.
|
|
39
|
+
continue-on-error: true
|
|
36
40
|
env:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
AGENTX_PROVIDER: {{ spec.provider }}
|
|
42
|
+
AGENTX_MODEL: {{ model }}
|
|
43
|
+
{% for v in provider_env %}
|
|
44
|
+
{{ v }}: {% raw %}${{ secrets.{% endraw %}{{ v }}{% raw %} }}{% endraw %}
|
|
45
|
+
{% endfor %}
|
|
40
46
|
run: python evals/run_evals.py
|
|
41
47
|
{% endif %}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# --- Provider selection (read by agentx) ---
|
|
2
|
+
AGENTX_PROVIDER={{ spec.provider }}
|
|
3
|
+
AGENTX_MODEL={{ model }}
|
|
4
|
+
AGENTX_TEMPERATURE=0.3
|
|
5
|
+
|
|
6
|
+
# --- Credentials for {{ provider_label }} ---
|
|
7
|
+
{% for v in provider_env %}
|
|
8
|
+
{{ v }}=
|
|
9
|
+
{% endfor %}
|
|
10
|
+
{% if not provider_env %}
|
|
11
|
+
# {{ provider_label }} is local — no API key required.
|
|
12
|
+
{% if spec.provider == 'ollama' %}
|
|
13
|
+
# Start it first: ollama serve && ollama pull {{ model }}
|
|
14
|
+
# OLLAMA_BASE_URL=http://localhost:11434
|
|
15
|
+
{% endif %}
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if spec.observability %}
|
|
18
|
+
|
|
19
|
+
# --- Observability (optional) ---
|
|
20
|
+
AGENTX_TELEMETRY=true
|
|
21
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
22
|
+
# LANGFUSE_PUBLIC_KEY=
|
|
23
|
+
# LANGFUSE_SECRET_KEY=
|
|
24
|
+
# LANGFUSE_HOST=https://cloud.langfuse.com
|
|
25
|
+
{% endif %}
|
|
@@ -17,10 +17,9 @@ from dotenv import load_dotenv
|
|
|
17
17
|
|
|
18
18
|
from agentx import get_chat_model
|
|
19
19
|
{% if spec.framework == 'langgraph' %}
|
|
20
|
-
from
|
|
21
|
-
from {{ pkg }}.agents import build_agents
|
|
20
|
+
from {{ pkg }}.graph import run_text
|
|
22
21
|
{% else %}
|
|
23
|
-
from {{ pkg }}.
|
|
22
|
+
from {{ pkg }}.crew import run_text
|
|
24
23
|
{% endif %}
|
|
25
24
|
|
|
26
25
|
THRESHOLD = 0.6
|
|
@@ -35,12 +34,7 @@ JUDGE_PROMPT = (
|
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
def _agent_reply(message: str) -> str:
|
|
38
|
-
|
|
39
|
-
agent = next(iter(build_agents().values()))
|
|
40
|
-
return run_agent(agent, message)
|
|
41
|
-
{% else %}
|
|
42
|
-
return str(build_project_crew(message).kickoff())
|
|
43
|
-
{% endif %}
|
|
37
|
+
return run_text(message)
|
|
44
38
|
|
|
45
39
|
|
|
46
40
|
def _score(judge, case: dict, output: str) -> float:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Agent definitions (CrewAI) for {{ spec.slug }} — built from prompts.json."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from agentx.frameworks import build_crewai_agent
|
|
5
|
+
from .config import MODEL, PROVIDER
|
|
6
|
+
from .prompts import agents_meta, resolved_system_prompts
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_agents() -> dict:
|
|
10
|
+
"""Return {name: CrewAI Agent} — one per entry in prompts.json."""
|
|
11
|
+
prompts = resolved_system_prompts()
|
|
12
|
+
agents: dict = {}
|
|
13
|
+
for name, meta in agents_meta().items():
|
|
14
|
+
agents[name] = build_crewai_agent(
|
|
15
|
+
role=meta.get("role", name),
|
|
16
|
+
goal=meta.get("goal", "Help the user accomplish their task accurately."),
|
|
17
|
+
backstory=prompts.get(name, ""),
|
|
18
|
+
provider=PROVIDER,
|
|
19
|
+
model=MODEL,
|
|
20
|
+
)
|
|
21
|
+
return agents
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Crew assembly (CrewAI) for {{ spec.slug }}."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from agentx.frameworks import build_crew
|
|
5
|
+
from .agents import build_agents
|
|
6
|
+
from .tasks import build_tasks
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_project_crew(user_input: str):
|
|
10
|
+
"""Assemble a sequential crew of all agents for the user's request."""
|
|
11
|
+
agents = build_agents()
|
|
12
|
+
tasks = build_tasks(user_input, agents)
|
|
13
|
+
return build_crew(list(agents.values()), tasks)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_text(message: str, thread_id: str = "default") -> str:
|
|
17
|
+
"""Run the crew on a message and return the final text (used by main/server).
|
|
18
|
+
|
|
19
|
+
``thread_id`` is accepted for API-compatibility with the server; a CrewAI
|
|
20
|
+
kickoff is stateless per call, so it is not used to partition memory here.
|
|
21
|
+
"""
|
|
22
|
+
del thread_id
|
|
23
|
+
return str(build_project_crew(message).kickoff())
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""LangGraph workflow for {{ spec.slug }}.
|
|
2
|
+
|
|
3
|
+
Exposes a module-level compiled ``graph`` (the LangGraph convention, also picked
|
|
4
|
+
up by ``langgraph dev``) and ``run_text`` / ``stream_text`` helpers.
|
|
5
|
+
|
|
6
|
+
{% if multi_agent %}
|
|
7
|
+
Orchestration: {{ orchestration }}
|
|
8
|
+
{% if orchestration == "supervisor" %} An LLM router decides which specialist to call next.{% endif %}
|
|
9
|
+
{% if orchestration == "sequential" %} Agents run in order: {% for a in spec.agents %}{{ a.name }}{% if not loop.last %} → {% endif %}{% endfor %}.{% endif %}
|
|
10
|
+
{% if orchestration == "parallel" %} All agents answer simultaneously; results are merged.{% endif %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
19
|
+
from langgraph.graph import END, START, StateGraph
|
|
20
|
+
|
|
21
|
+
from .state import AgentState
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
RECURSION_LIMIT = 25
|
|
26
|
+
{% if not multi_agent %}
|
|
27
|
+
from langgraph.prebuilt import ToolNode, tools_condition
|
|
28
|
+
|
|
29
|
+
from .nodes import call_model
|
|
30
|
+
from .tools import get_tools
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_graph():
|
|
34
|
+
"""Build a ReAct agent graph: agent ⇄ tools loop, with checkpointed memory."""
|
|
35
|
+
builder = StateGraph(AgentState)
|
|
36
|
+
builder.add_node("agent", call_model)
|
|
37
|
+
builder.add_node("tools", ToolNode(get_tools()))
|
|
38
|
+
|
|
39
|
+
builder.add_edge(START, "agent")
|
|
40
|
+
builder.add_conditional_edges("agent", tools_condition)
|
|
41
|
+
builder.add_edge("tools", "agent")
|
|
42
|
+
|
|
43
|
+
return builder.compile(checkpointer=MemorySaver())
|
|
44
|
+
{% elif orchestration == "supervisor" %}
|
|
45
|
+
from .nodes import WORKERS, make_worker, supervisor
|
|
46
|
+
from .prompts import resolved_system_prompts
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build_graph():
|
|
50
|
+
"""Supervisor graph: an LLM router picks the next specialist each turn."""
|
|
51
|
+
builder = StateGraph(AgentState)
|
|
52
|
+
builder.add_node("supervisor", supervisor)
|
|
53
|
+
for name, system_prompt in resolved_system_prompts().items():
|
|
54
|
+
builder.add_node(name, make_worker(name, system_prompt))
|
|
55
|
+
|
|
56
|
+
builder.add_edge(START, "supervisor")
|
|
57
|
+
builder.add_conditional_edges(
|
|
58
|
+
"supervisor",
|
|
59
|
+
lambda state: state.get("next", "FINISH"),
|
|
60
|
+
{**{name: name for name in WORKERS}, "FINISH": END},
|
|
61
|
+
)
|
|
62
|
+
for name in WORKERS:
|
|
63
|
+
builder.add_edge(name, "supervisor")
|
|
64
|
+
|
|
65
|
+
return builder.compile(checkpointer=MemorySaver())
|
|
66
|
+
{% elif orchestration == "sequential" %}
|
|
67
|
+
from .nodes import WORKERS, make_worker
|
|
68
|
+
from .prompts import resolved_system_prompts
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_graph():
|
|
72
|
+
"""Sequential pipeline: agents run in order, each seeing the previous output."""
|
|
73
|
+
builder = StateGraph(AgentState)
|
|
74
|
+
prompts = resolved_system_prompts()
|
|
75
|
+
names = list(prompts)
|
|
76
|
+
|
|
77
|
+
for name in names:
|
|
78
|
+
builder.add_node(name, make_worker(name, prompts[name]))
|
|
79
|
+
|
|
80
|
+
builder.add_edge(START, names[0])
|
|
81
|
+
for i in range(len(names) - 1):
|
|
82
|
+
builder.add_edge(names[i], names[i + 1])
|
|
83
|
+
builder.add_edge(names[-1], END)
|
|
84
|
+
|
|
85
|
+
return builder.compile(checkpointer=MemorySaver())
|
|
86
|
+
{% elif orchestration == "parallel" %}
|
|
87
|
+
from langgraph.types import Send
|
|
88
|
+
|
|
89
|
+
from .nodes import WORKERS, make_worker, merge_results
|
|
90
|
+
from .prompts import resolved_system_prompts
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _fan_out(state: AgentState) -> list[Send]:
|
|
94
|
+
"""Send the same user message to every worker simultaneously."""
|
|
95
|
+
return [Send(name, state) for name in WORKERS]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_graph():
|
|
99
|
+
"""Parallel graph: all agents answer simultaneously, then results are merged."""
|
|
100
|
+
builder = StateGraph(AgentState)
|
|
101
|
+
for name, system_prompt in resolved_system_prompts().items():
|
|
102
|
+
builder.add_node(name, make_worker(name, system_prompt))
|
|
103
|
+
builder.add_node("merge", merge_results)
|
|
104
|
+
|
|
105
|
+
builder.add_conditional_edges(START, _fan_out, WORKERS)
|
|
106
|
+
for name in WORKERS:
|
|
107
|
+
builder.add_edge(name, "merge")
|
|
108
|
+
builder.add_edge("merge", END)
|
|
109
|
+
|
|
110
|
+
return builder.compile(checkpointer=MemorySaver())
|
|
111
|
+
{% endif %}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
graph = build_graph()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _config(thread_id: str) -> dict:
|
|
118
|
+
return {"configurable": {"thread_id": thread_id}, "recursion_limit": RECURSION_LIMIT}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def arun_text(message: str, thread_id: str = "default") -> str:
|
|
122
|
+
"""Async invoke; runs async-only tools (MCP) correctly.
|
|
123
|
+
|
|
124
|
+
Pass a unique ``thread_id`` per user/session — callers that omit it share
|
|
125
|
+
one conversation history (fine for a single CLI session, wrong for a server).
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
result = await graph.ainvoke(
|
|
129
|
+
{"messages": [{"role": "user", "content": message}]}, config=_config(thread_id)
|
|
130
|
+
)
|
|
131
|
+
except Exception as exc: # noqa: BLE001
|
|
132
|
+
logger.exception("graph invocation failed")
|
|
133
|
+
return f"Sorry, something went wrong: {exc}"
|
|
134
|
+
messages = result.get("messages", [])
|
|
135
|
+
return getattr(messages[-1], "content", "") if messages else ""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
_loop: asyncio.AbstractEventLoop | None = None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _sync_loop() -> asyncio.AbstractEventLoop:
|
|
142
|
+
"""Reuse one loop across calls so MCP stdio sessions don't break on turn 2."""
|
|
143
|
+
global _loop
|
|
144
|
+
if _loop is None or _loop.is_closed():
|
|
145
|
+
_loop = asyncio.new_event_loop()
|
|
146
|
+
return _loop
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def run_text(message: str, thread_id: str = "default") -> str:
|
|
150
|
+
"""Sync wrapper for CLI / REPL use. Do NOT call from an async context."""
|
|
151
|
+
return _sync_loop().run_until_complete(arun_text(message, thread_id))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def stream_text(message: str, thread_id: str = "default"):
|
|
155
|
+
"""Yield reply tokens as the model produces them."""
|
|
156
|
+
try:
|
|
157
|
+
async for _ns, (chunk, meta) in graph.astream(
|
|
158
|
+
{"messages": [{"role": "user", "content": message}]},
|
|
159
|
+
config=_config(thread_id),
|
|
160
|
+
stream_mode="messages",
|
|
161
|
+
subgraphs=True,
|
|
162
|
+
):
|
|
163
|
+
{% if multi_agent and orchestration == "supervisor" %}
|
|
164
|
+
if meta.get("langgraph_node") == "supervisor":
|
|
165
|
+
continue
|
|
166
|
+
{% endif %}
|
|
167
|
+
text = getattr(chunk, "content", "")
|
|
168
|
+
if text:
|
|
169
|
+
yield text
|
|
170
|
+
except Exception as exc: # noqa: BLE001
|
|
171
|
+
logger.exception("graph streaming failed")
|
|
172
|
+
yield f"Sorry, something went wrong: {exc}"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Entry point for {{ spec.slug }} — an interactive REPL over the agent graph."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
{% if spec.observability %}from .observability import init_observability
|
|
6
|
+
{% endif %}
|
|
7
|
+
{% if spec.guardrails %}from .guardrails import guard_input, guard_output
|
|
8
|
+
{% endif %}
|
|
9
|
+
{% if spec.use_cache %}from agentx.cache import enable_caching
|
|
10
|
+
{% endif %}
|
|
11
|
+
{% if spec.framework == 'langgraph' %}from .graph import run_text
|
|
12
|
+
{% else %}from .crew import run_text
|
|
13
|
+
{% endif %}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> None:
|
|
17
|
+
load_dotenv()
|
|
18
|
+
{% if spec.observability %}
|
|
19
|
+
init_observability()
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% if spec.use_cache %}
|
|
22
|
+
enable_caching() # cache LLM responses → lower cost & latency
|
|
23
|
+
{% endif %}
|
|
24
|
+
print("🧬 {{ spec.slug }} ready. Type 'quit' to exit.\n")
|
|
25
|
+
while True:
|
|
26
|
+
try:
|
|
27
|
+
user = input("you > ").strip()
|
|
28
|
+
except (EOFError, KeyboardInterrupt):
|
|
29
|
+
break
|
|
30
|
+
if user.lower() in {"quit", "exit"}:
|
|
31
|
+
break
|
|
32
|
+
if not user:
|
|
33
|
+
continue
|
|
34
|
+
{% if spec.guardrails %}
|
|
35
|
+
user = guard_input(user).text
|
|
36
|
+
{% endif %}
|
|
37
|
+
reply = run_text(user)
|
|
38
|
+
{% if spec.guardrails %}
|
|
39
|
+
reply = guard_output(reply).text
|
|
40
|
+
{% endif %}
|
|
41
|
+
print(f"\nbot > {reply}\n")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main()
|