agentx-kit 0.2.0__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.
- agentx/__init__.py +55 -0
- agentx/cli.py +230 -0
- agentx/config.py +34 -0
- agentx/frameworks/__init__.py +5 -0
- agentx/frameworks/crewai_agent.py +52 -0
- agentx/frameworks/langchain_agent.py +43 -0
- agentx/guardrails.py +89 -0
- agentx/memory/__init__.py +4 -0
- agentx/memory/store.py +78 -0
- agentx/observability.py +103 -0
- agentx/prompts/__init__.py +8 -0
- agentx/prompts/templates.py +40 -0
- agentx/providers/__init__.py +15 -0
- agentx/providers/base.py +50 -0
- agentx/providers/factory.py +71 -0
- agentx/providers/registry.py +165 -0
- agentx/rag/__init__.py +8 -0
- agentx/rag/pipeline.py +121 -0
- agentx/reliability.py +112 -0
- agentx/scaffold/__init__.py +14 -0
- agentx/scaffold/generator.py +190 -0
- agentx/scaffold/prompts_store.py +99 -0
- agentx/scaffold/spec.py +85 -0
- agentx/scaffold/templates/Dockerfile.j2 +17 -0
- agentx/scaffold/templates/README.md.j2 +46 -0
- agentx/scaffold/templates/ci.yml.j2 +41 -0
- agentx/scaffold/templates/docker-compose.yml.j2 +9 -0
- agentx/scaffold/templates/dockerignore.j2 +11 -0
- agentx/scaffold/templates/env.example.j2 +12 -0
- agentx/scaffold/templates/evals/dataset.json.j2 +10 -0
- agentx/scaffold/templates/evals/run_evals.py.j2 +70 -0
- agentx/scaffold/templates/gitignore.j2 +8 -0
- agentx/scaffold/templates/mcp_servers.json.j2 +7 -0
- agentx/scaffold/templates/pkg/__init__.py.j2 +3 -0
- agentx/scaffold/templates/pkg/agents.py.j2 +77 -0
- agentx/scaffold/templates/pkg/config.py.j2 +25 -0
- agentx/scaffold/templates/pkg/guardrails.py.j2 +21 -0
- agentx/scaffold/templates/pkg/main.py.j2 +79 -0
- agentx/scaffold/templates/pkg/memory.py.j2 +17 -0
- agentx/scaffold/templates/pkg/observability.py.j2 +17 -0
- agentx/scaffold/templates/pkg/prompts.py.j2 +45 -0
- agentx/scaffold/templates/pkg/rag.py.j2 +37 -0
- agentx/scaffold/templates/pkg/server.py.j2 +85 -0
- agentx/scaffold/templates/pkg/tools.py.j2 +16 -0
- agentx/scaffold/templates/pyproject.toml.j2 +28 -0
- agentx/scaffold/templates/skills_seed.json.j2 +6 -0
- agentx/scaffold/wizard.py +125 -0
- agentx/skills/__init__.py +4 -0
- agentx/skills/registry.py +63 -0
- agentx/structured.py +37 -0
- agentx/tools/__init__.py +5 -0
- agentx/tools/builtin.py +45 -0
- agentx/tools/mcp.py +64 -0
- agentx_kit-0.2.0.dist-info/METADATA +289 -0
- agentx_kit-0.2.0.dist-info/RECORD +58 -0
- agentx_kit-0.2.0.dist-info/WHEEL +4 -0
- agentx_kit-0.2.0.dist-info/entry_points.txt +2 -0
- agentx_kit-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""LLM-as-judge eval harness for {{ spec.slug }}.
|
|
2
|
+
|
|
3
|
+
Runs each case in evals/dataset.json through the agent, scores the output with a
|
|
4
|
+
judge LLM (0-1) against the case's criteria, and exits non-zero if the mean
|
|
5
|
+
score is below THRESHOLD — so it can gate CI.
|
|
6
|
+
|
|
7
|
+
python evals/run_evals.py
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from dotenv import load_dotenv
|
|
17
|
+
|
|
18
|
+
from agentx import get_chat_model
|
|
19
|
+
{% if spec.framework == 'langgraph' %}
|
|
20
|
+
from agentx.frameworks import run_agent
|
|
21
|
+
from {{ pkg }}.agents import build_agents
|
|
22
|
+
{% else %}
|
|
23
|
+
from {{ pkg }}.agents import build_project_crew
|
|
24
|
+
{% endif %}
|
|
25
|
+
|
|
26
|
+
THRESHOLD = 0.6
|
|
27
|
+
DATASET = Path(__file__).parent / "dataset.json"
|
|
28
|
+
|
|
29
|
+
JUDGE_PROMPT = (
|
|
30
|
+
"You are a strict evaluator. Given a user INPUT, the agent's OUTPUT, and the "
|
|
31
|
+
"success CRITERIA, reply with ONLY a number between 0 and 1 (one decimal) for "
|
|
32
|
+
"how well OUTPUT satisfies CRITERIA.\n\n"
|
|
33
|
+
"INPUT: {input}\nOUTPUT: {output}\nCRITERIA: {criteria}\nSCORE:"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _agent_reply(message: str) -> str:
|
|
38
|
+
{% if spec.framework == 'langgraph' %}
|
|
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 %}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _score(judge, case: dict, output: str) -> float:
|
|
47
|
+
text = judge.invoke(
|
|
48
|
+
JUDGE_PROMPT.format(input=case["input"], output=output, criteria=case.get("criteria", ""))
|
|
49
|
+
).content
|
|
50
|
+
match = re.search(r"(\d(?:\.\d+)?)", text or "")
|
|
51
|
+
return max(0.0, min(1.0, float(match.group(1)))) if match else 0.0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main() -> int:
|
|
55
|
+
load_dotenv()
|
|
56
|
+
cases = json.loads(DATASET.read_text(encoding="utf-8"))
|
|
57
|
+
judge = get_chat_model()
|
|
58
|
+
scores = []
|
|
59
|
+
for case in cases:
|
|
60
|
+
output = _agent_reply(case["input"])
|
|
61
|
+
score = _score(judge, case, output)
|
|
62
|
+
scores.append(score)
|
|
63
|
+
print(f"[{score:.1f}] {case['input'][:60]}")
|
|
64
|
+
mean = sum(scores) / len(scores) if scores else 0.0
|
|
65
|
+
print(f"\nMean score: {mean:.2f} (threshold {THRESHOLD})")
|
|
66
|
+
return 0 if mean >= THRESHOLD else 1
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
sys.exit(main())
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{% if spec.framework == 'langgraph' %}
|
|
2
|
+
"""Agent definitions (LangGraph) — built dynamically from prompts.json."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from agentx.frameworks import build_react_agent
|
|
6
|
+
from agentx.tools import make_web_search_tool
|
|
7
|
+
{% if spec.use_skills %}from agentx.skills import get_skill_registry
|
|
8
|
+
{% endif %}
|
|
9
|
+
from .config import MODEL, PROVIDER
|
|
10
|
+
from .prompts import resolved_system_prompts
|
|
11
|
+
{% if spec.use_rag %}from .rag import get_knowledge_tool
|
|
12
|
+
{% endif %}
|
|
13
|
+
{% if spec.use_mcp %}from .tools import get_mcp_tools
|
|
14
|
+
{% endif %}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _build_tools():
|
|
18
|
+
tools = [make_web_search_tool()]
|
|
19
|
+
{% if spec.use_rag %}
|
|
20
|
+
tools.append(get_knowledge_tool())
|
|
21
|
+
{% endif %}
|
|
22
|
+
{% if spec.use_mcp %}
|
|
23
|
+
tools.extend(get_mcp_tools())
|
|
24
|
+
{% endif %}
|
|
25
|
+
return tools
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_agents():
|
|
29
|
+
"""Return {name: compiled LangGraph agent} — one per entry in prompts.json."""
|
|
30
|
+
tools = _build_tools()
|
|
31
|
+
{% if spec.use_skills %}
|
|
32
|
+
skills = get_skill_registry("data/skills").combined_instructions()
|
|
33
|
+
{% else %}
|
|
34
|
+
skills = ""
|
|
35
|
+
{% endif %}
|
|
36
|
+
agents = {}
|
|
37
|
+
for name, prompt in resolved_system_prompts().items():
|
|
38
|
+
if skills:
|
|
39
|
+
prompt = prompt + "\nApply these skills:\n" + skills
|
|
40
|
+
agents[name] = build_react_agent(
|
|
41
|
+
provider=PROVIDER, model=MODEL, tools=tools, system_prompt=prompt,
|
|
42
|
+
)
|
|
43
|
+
return agents
|
|
44
|
+
{% else %}
|
|
45
|
+
"""Agent + crew definitions (CrewAI) — built dynamically from prompts.json."""
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
from crewai import Task
|
|
49
|
+
|
|
50
|
+
from agentx.frameworks import build_crew, build_crewai_agent
|
|
51
|
+
from .config import MODEL, PROVIDER
|
|
52
|
+
from .prompts import agents_meta, resolved_system_prompts
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_agents():
|
|
56
|
+
"""Return {name: CrewAI Agent} — one per entry in prompts.json."""
|
|
57
|
+
prompts = resolved_system_prompts()
|
|
58
|
+
agents = {}
|
|
59
|
+
for name, meta in agents_meta().items():
|
|
60
|
+
agents[name] = build_crewai_agent(
|
|
61
|
+
role=meta.get("role", name),
|
|
62
|
+
goal=meta.get("goal", "Help the user accomplish their task accurately."),
|
|
63
|
+
backstory=prompts.get(name, ""),
|
|
64
|
+
provider=PROVIDER, model=MODEL,
|
|
65
|
+
)
|
|
66
|
+
return agents
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def build_project_crew(user_input: str):
|
|
70
|
+
"""Assemble a sequential crew where each agent works the user's request."""
|
|
71
|
+
agents = build_agents()
|
|
72
|
+
tasks = [
|
|
73
|
+
Task(description=user_input, expected_output="A helpful, complete response.", agent=agent)
|
|
74
|
+
for agent in agents.values()
|
|
75
|
+
]
|
|
76
|
+
return build_crew(list(agents.values()), tasks)
|
|
77
|
+
{% endif %}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Typed configuration for {{ spec.slug }} (12-factor, pydantic-settings).
|
|
2
|
+
|
|
3
|
+
Reads from environment / .env. Override at runtime, e.g. AGENTX_MODEL=gpt-4o.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Settings(BaseSettings):
|
|
11
|
+
model_config = SettingsConfigDict(env_prefix="AGENTX_", env_file=".env", extra="ignore")
|
|
12
|
+
|
|
13
|
+
provider: str = "{{ spec.provider }}"
|
|
14
|
+
model: str = "{{ model }}"
|
|
15
|
+
temperature: float = 0.3
|
|
16
|
+
{% if spec.observability %}
|
|
17
|
+
telemetry: bool = True
|
|
18
|
+
{% endif %}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
settings = Settings()
|
|
22
|
+
|
|
23
|
+
# Convenience aliases used across the project.
|
|
24
|
+
PROVIDER = settings.provider
|
|
25
|
+
MODEL = settings.model
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Guardrails for {{ spec.slug }} — input/output validation + PII redaction.
|
|
2
|
+
|
|
3
|
+
Customise BANNED_WORDS or compose your own guards. For jailbreak/moderation,
|
|
4
|
+
wire in Guardrails-AI or NeMo Guardrails here.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from agentx.guardrails import GuardResult, apply_guards, default_input_guards, default_output_guards
|
|
9
|
+
|
|
10
|
+
# Words/phrases to reject in user input (case-insensitive).
|
|
11
|
+
BANNED_WORDS: list[str] = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def guard_input(text: str) -> GuardResult:
|
|
15
|
+
"""Validate + lightly sanitise user input before it reaches the model."""
|
|
16
|
+
return apply_guards(text, default_input_guards(banned=BANNED_WORDS))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def guard_output(text: str) -> GuardResult:
|
|
20
|
+
"""Redact PII and cap length on model output before returning to the user."""
|
|
21
|
+
return apply_guards(text, default_output_guards(redact=True))
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Entry point for {{ spec.slug }}."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
{% if spec.use_short_memory %}from .memory import session_memory
|
|
6
|
+
{% endif %}
|
|
7
|
+
{% if spec.observability %}from .observability import init_observability
|
|
8
|
+
{% endif %}
|
|
9
|
+
{% if spec.guardrails %}from .guardrails import guard_input, guard_output
|
|
10
|
+
{% endif %}
|
|
11
|
+
{% if spec.framework == 'langgraph' %}
|
|
12
|
+
from agentx.frameworks import run_agent
|
|
13
|
+
from .agents import build_agents
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> None:
|
|
17
|
+
load_dotenv()
|
|
18
|
+
{% if spec.observability %}
|
|
19
|
+
init_observability()
|
|
20
|
+
{% endif %}
|
|
21
|
+
agents = build_agents()
|
|
22
|
+
agent_name, agent = next(iter(agents.items()))
|
|
23
|
+
print("🧬 {{ spec.slug }} ready. Type 'quit' to exit.\n")
|
|
24
|
+
while True:
|
|
25
|
+
try:
|
|
26
|
+
user = input("you > ").strip()
|
|
27
|
+
except (EOFError, KeyboardInterrupt):
|
|
28
|
+
break
|
|
29
|
+
if user.lower() in {"quit", "exit"}:
|
|
30
|
+
break
|
|
31
|
+
if not user:
|
|
32
|
+
continue
|
|
33
|
+
{% if spec.guardrails %}
|
|
34
|
+
user = guard_input(user).text
|
|
35
|
+
{% endif %}
|
|
36
|
+
{% if spec.use_short_memory %}
|
|
37
|
+
session_memory.add_user(user)
|
|
38
|
+
{% endif %}
|
|
39
|
+
reply = run_agent(agent, user)
|
|
40
|
+
{% if spec.guardrails %}
|
|
41
|
+
reply = guard_output(reply).text
|
|
42
|
+
{% endif %}
|
|
43
|
+
{% if spec.use_short_memory %}
|
|
44
|
+
session_memory.add_ai(reply)
|
|
45
|
+
{% endif %}
|
|
46
|
+
print(f"\n{agent_name} > {reply}\n")
|
|
47
|
+
{% else %}
|
|
48
|
+
from .agents import build_project_crew
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main() -> None:
|
|
52
|
+
load_dotenv()
|
|
53
|
+
{% if spec.observability %}
|
|
54
|
+
init_observability()
|
|
55
|
+
{% endif %}
|
|
56
|
+
print("🧬 {{ spec.slug }} (CrewAI). Type 'quit' to exit.\n")
|
|
57
|
+
while True:
|
|
58
|
+
try:
|
|
59
|
+
user = input("you > ").strip()
|
|
60
|
+
except (EOFError, KeyboardInterrupt):
|
|
61
|
+
break
|
|
62
|
+
if user.lower() in {"quit", "exit"}:
|
|
63
|
+
break
|
|
64
|
+
if not user:
|
|
65
|
+
continue
|
|
66
|
+
{% if spec.guardrails %}
|
|
67
|
+
user = guard_input(user).text
|
|
68
|
+
{% endif %}
|
|
69
|
+
crew = build_project_crew(user)
|
|
70
|
+
result = str(crew.kickoff())
|
|
71
|
+
{% if spec.guardrails %}
|
|
72
|
+
result = guard_output(result).text
|
|
73
|
+
{% endif %}
|
|
74
|
+
print(f"\ncrew > {result}\n")
|
|
75
|
+
{% endif %}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Memory for {{ spec.slug }} (mode: {{ spec.memory }})."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
{% if spec.use_short_memory %}
|
|
5
|
+
from agentx.memory import ConversationMemory
|
|
6
|
+
|
|
7
|
+
# Short-term, in-process rolling window of recent turns.
|
|
8
|
+
session_memory = ConversationMemory(max_turns=12)
|
|
9
|
+
{% endif %}
|
|
10
|
+
{% if spec.use_long_memory %}
|
|
11
|
+
from agentx.memory import LongTermMemory
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def long_term(session_id: str = "default") -> LongTermMemory:
|
|
15
|
+
"""Persistent per-session memory (JSONL under data/memory/)."""
|
|
16
|
+
return LongTermMemory(f"data/memory/{session_id}.jsonl")
|
|
17
|
+
{% endif %}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Observability wiring for {{ spec.slug }}.
|
|
2
|
+
|
|
3
|
+
OpenTelemetry GenAI tracing (+ optional Langfuse). Configure via env:
|
|
4
|
+
OTEL_EXPORTER_OTLP_ENDPOINT, LANGFUSE_PUBLIC_KEY/LANGFUSE_SECRET_KEY.
|
|
5
|
+
Disable entirely with AGENTX_TELEMETRY=false.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from agentx.observability import get_callbacks, setup_tracing
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def init_observability(service_name: str = "{{ spec.slug }}") -> bool:
|
|
13
|
+
"""Instrument tracing once at startup. Safe no-op if extras absent/disabled."""
|
|
14
|
+
return setup_tracing(service_name)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["init_observability", "get_callbacks"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Prompt loading for {{ spec.slug }}.
|
|
2
|
+
|
|
3
|
+
Prompts live in `prompts.json` at the project root — the single source of truth
|
|
4
|
+
for which agents exist and their system prompts. Edit that file directly, or use
|
|
5
|
+
the CLI:
|
|
6
|
+
|
|
7
|
+
agentx prompt list
|
|
8
|
+
agentx prompt set <agent> --text "You are ..."
|
|
9
|
+
agentx prompt add <agent> --role "..." --goal "..." --text "..."
|
|
10
|
+
|
|
11
|
+
A blank `system_prompt` is auto-derived from the agent's role + goal.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from agentx.prompts import render_agent_system
|
|
19
|
+
|
|
20
|
+
PROMPTS_FILE = Path(__file__).resolve().parents[2] / "prompts.json"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_prompts() -> dict:
|
|
24
|
+
if PROMPTS_FILE.exists():
|
|
25
|
+
return json.loads(PROMPTS_FILE.read_text(encoding="utf-8"))
|
|
26
|
+
return {"with_rag": {{ spec.use_rag }}, "agents": {}}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resolved_system_prompts() -> dict[str, str]:
|
|
30
|
+
"""Return {agent_name: system_prompt}, deriving blanks from role/goal."""
|
|
31
|
+
data = load_prompts()
|
|
32
|
+
with_rag = bool(data.get("with_rag", False))
|
|
33
|
+
out: dict[str, str] = {}
|
|
34
|
+
for name, meta in data.get("agents", {}).items():
|
|
35
|
+
explicit = (meta.get("system_prompt") or "").strip()
|
|
36
|
+
out[name] = explicit or render_agent_system(
|
|
37
|
+
role=meta.get("role", name),
|
|
38
|
+
goal=meta.get("goal", "Help the user accomplish their task accurately."),
|
|
39
|
+
with_rag=with_rag,
|
|
40
|
+
)
|
|
41
|
+
return out
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def agents_meta() -> dict[str, dict]:
|
|
45
|
+
return load_prompts().get("agents", {})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""RAG knowledge base for {{ spec.slug }}.
|
|
2
|
+
|
|
3
|
+
Drop `.txt` / `.md` files into the project's `knowledge/` folder; they are
|
|
4
|
+
chunked and indexed on first use. Uses Chroma when installed, else a built-in
|
|
5
|
+
keyword retriever.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from agentx.rag import build_index_from_texts, make_retriever_tool
|
|
12
|
+
|
|
13
|
+
_KNOWLEDGE_DIR = Path(__file__).resolve().parents[2] / "knowledge"
|
|
14
|
+
_index = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_texts() -> list[str]:
|
|
18
|
+
texts: list[str] = []
|
|
19
|
+
if _KNOWLEDGE_DIR.exists():
|
|
20
|
+
for fp in _KNOWLEDGE_DIR.glob("**/*"):
|
|
21
|
+
if fp.suffix.lower() in {".txt", ".md"}:
|
|
22
|
+
texts.append(fp.read_text(encoding="utf-8"))
|
|
23
|
+
return texts or [
|
|
24
|
+
"Placeholder knowledge base. Add .txt or .md files under the 'knowledge/' folder."
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_index():
|
|
29
|
+
global _index
|
|
30
|
+
if _index is None:
|
|
31
|
+
_index = build_index_from_texts(_load_texts(), persist_dir=".chroma")
|
|
32
|
+
return _index
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_knowledge_tool():
|
|
36
|
+
"""A LangChain tool that searches the knowledge base."""
|
|
37
|
+
return make_retriever_tool(get_index())
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""FastAPI server for {{ spec.slug }} — REST + SSE streaming.
|
|
2
|
+
|
|
3
|
+
Run: uv run uvicorn {{ pkg }}.server:app --reload --port 8000
|
|
4
|
+
Endpoints: GET /health, POST /chat, GET /chat/stream?message=...
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
from fastapi import FastAPI
|
|
12
|
+
from fastapi.responses import StreamingResponse
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
load_dotenv()
|
|
16
|
+
{% if spec.observability %}
|
|
17
|
+
from .observability import init_observability
|
|
18
|
+
|
|
19
|
+
init_observability()
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% if spec.guardrails %}
|
|
22
|
+
from .guardrails import guard_input, guard_output
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% if spec.framework == 'langgraph' %}
|
|
25
|
+
from agentx.frameworks import run_agent
|
|
26
|
+
from .agents import build_agents
|
|
27
|
+
|
|
28
|
+
_agent_cache = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _reply(message: str) -> str:
|
|
32
|
+
global _agent_cache
|
|
33
|
+
if _agent_cache is None:
|
|
34
|
+
_agent_cache = next(iter(build_agents().values()))
|
|
35
|
+
return run_agent(_agent_cache, message)
|
|
36
|
+
{% else %}
|
|
37
|
+
from .agents import build_project_crew
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _reply(message: str) -> str:
|
|
41
|
+
return str(build_project_crew(message).kickoff())
|
|
42
|
+
{% endif %}
|
|
43
|
+
|
|
44
|
+
app = FastAPI(title="{{ spec.slug }}", version="0.1.0")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ChatIn(BaseModel):
|
|
48
|
+
message: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.get("/health")
|
|
52
|
+
def health() -> dict:
|
|
53
|
+
return {"status": "ok", "service": "{{ spec.slug }}"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.post("/chat")
|
|
57
|
+
def chat(body: ChatIn) -> dict:
|
|
58
|
+
message = body.message
|
|
59
|
+
{% if spec.guardrails %}
|
|
60
|
+
message = guard_input(message).text
|
|
61
|
+
{% endif %}
|
|
62
|
+
reply = _reply(message)
|
|
63
|
+
{% if spec.guardrails %}
|
|
64
|
+
reply = guard_output(reply).text
|
|
65
|
+
{% endif %}
|
|
66
|
+
return {"reply": reply}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.get("/chat/stream")
|
|
70
|
+
def chat_stream(message: str) -> StreamingResponse:
|
|
71
|
+
"""Server-Sent Events stream of the reply (token-chunked)."""
|
|
72
|
+
{% if spec.guardrails %}
|
|
73
|
+
message = guard_input(message).text
|
|
74
|
+
{% endif %}
|
|
75
|
+
|
|
76
|
+
def _events():
|
|
77
|
+
reply = _reply(message)
|
|
78
|
+
{% if spec.guardrails %}
|
|
79
|
+
reply = guard_output(reply).text
|
|
80
|
+
{% endif %}
|
|
81
|
+
for chunk in reply.split(" "):
|
|
82
|
+
yield f"data: {json.dumps({'token': chunk + ' '})}\n\n"
|
|
83
|
+
yield "data: [DONE]\n\n"
|
|
84
|
+
|
|
85
|
+
return StreamingResponse(_events(), media_type="text/event-stream")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""MCP tools for {{ spec.slug }}.
|
|
2
|
+
|
|
3
|
+
Configure servers in `mcp_servers.json` at the project root. Tools load lazily
|
|
4
|
+
and degrade gracefully (returns [] if MCP isn't available).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from agentx.tools import load_mcp_tools
|
|
11
|
+
|
|
12
|
+
_CONFIG = Path(__file__).resolve().parents[2] / "mcp_servers.json"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_mcp_tools():
|
|
16
|
+
return load_mcp_tools(_CONFIG)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{{ spec.slug }}"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Agentic app generated by AgentX ({{ spec.framework }} · {{ provider_label }})."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10,<3.14"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"agentx-kit[{{ extras_str }}]>=0.1.0",
|
|
9
|
+
"python-dotenv>=1.0.0",
|
|
10
|
+
"ddgs>=9.0.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
{{ spec.slug }} = "{{ pkg }}.main:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/{{ pkg }}"]
|
|
22
|
+
|
|
23
|
+
{% if spec.agentx_local_path %}
|
|
24
|
+
[tool.uv.sources]
|
|
25
|
+
agentx-kit = { path = "{{ spec.agentx_local_path }}", editable = true }
|
|
26
|
+
{% endif %}
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "star-method",
|
|
3
|
+
"name": "STAR Method",
|
|
4
|
+
"description": "Structure answers as Situation, Task, Action, Result.",
|
|
5
|
+
"instructions": "When answering behavioural or experience questions, structure the response using the STAR method: Situation, Task, Action, Result. Be specific and quantify impact where possible."
|
|
6
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Interactive wizard — collects a ``ProjectSpec`` one option at a time.
|
|
2
|
+
|
|
3
|
+
Uses ``questionary`` for arrow-key selection. Every prompt has a sensible
|
|
4
|
+
default so power users can blast through with Enter.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import questionary
|
|
9
|
+
|
|
10
|
+
from ..providers import all_specs, get_spec
|
|
11
|
+
from .spec import AgentSpec, ProjectSpec
|
|
12
|
+
|
|
13
|
+
_FRAMEWORKS = [
|
|
14
|
+
("LangGraph (LangChain)", "langgraph"),
|
|
15
|
+
("CrewAI", "crewai"),
|
|
16
|
+
]
|
|
17
|
+
_MEMORY = [
|
|
18
|
+
("None", "none"),
|
|
19
|
+
("Short-term (windowed buffer)", "short"),
|
|
20
|
+
("Long-term (persistent JSONL)", "long"),
|
|
21
|
+
("Both", "both"),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _select(message: str, choices: list[tuple[str, str]], default_value: str) -> str:
|
|
26
|
+
options = [questionary.Choice(title=label, value=value) for label, value in choices]
|
|
27
|
+
default = next((o for o in options if o.value == default_value), options[0])
|
|
28
|
+
return questionary.select(message, choices=options, default=default).ask()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_wizard(name: str | None = None) -> ProjectSpec | None:
|
|
32
|
+
"""Run the interactive flow; returns a ProjectSpec, or None if cancelled."""
|
|
33
|
+
questionary.print("🧬 AgentX — new project\n", style="bold fg:cyan")
|
|
34
|
+
|
|
35
|
+
name = name or questionary.text("Project name:", default="my-agent").ask()
|
|
36
|
+
if not name:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
framework = _select("Agent framework:", _FRAMEWORKS, "langgraph")
|
|
40
|
+
if framework is None:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
# Provider + model
|
|
44
|
+
provider_choices = [
|
|
45
|
+
(f"{s.label} · needs: {', '.join(s.env_vars) or 'no key (local)'}", s.id)
|
|
46
|
+
for s in all_specs()
|
|
47
|
+
]
|
|
48
|
+
provider = _select("LLM provider:", provider_choices, "openai")
|
|
49
|
+
if provider is None:
|
|
50
|
+
return None
|
|
51
|
+
pspec = get_spec(provider)
|
|
52
|
+
model = questionary.text(
|
|
53
|
+
f"Model id ({pspec.label}):", default=pspec.default_model
|
|
54
|
+
).ask() or pspec.default_model
|
|
55
|
+
|
|
56
|
+
# Agents
|
|
57
|
+
n_str = questionary.text("How many agents?", default="1").ask() or "1"
|
|
58
|
+
try:
|
|
59
|
+
n_agents = max(1, min(10, int(n_str)))
|
|
60
|
+
except ValueError:
|
|
61
|
+
n_agents = 1
|
|
62
|
+
agents: list[AgentSpec] = []
|
|
63
|
+
for i in range(n_agents):
|
|
64
|
+
questionary.print(f"\nAgent {i + 1} of {n_agents}", style="bold")
|
|
65
|
+
a_name = questionary.text(" name:", default=f"agent_{i + 1}" if n_agents > 1 else "assistant").ask()
|
|
66
|
+
a_role = questionary.text(" role:", default="Helpful Assistant").ask()
|
|
67
|
+
a_goal = questionary.text(" goal:", default="Help the user accomplish their task accurately.").ask()
|
|
68
|
+
a_prompt = questionary.text(
|
|
69
|
+
" system prompt (optional — blank = auto from role/goal):", default="",
|
|
70
|
+
multiline=True,
|
|
71
|
+
).ask()
|
|
72
|
+
agents.append(AgentSpec(
|
|
73
|
+
name=a_name or f"agent_{i + 1}",
|
|
74
|
+
role=a_role or "Assistant",
|
|
75
|
+
goal=a_goal or "Help the user.",
|
|
76
|
+
system_prompt=(a_prompt or "").strip(),
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
# Capabilities — one by one
|
|
80
|
+
use_rag = questionary.confirm("Add a RAG module (knowledge base)?", default=False).ask()
|
|
81
|
+
memory = _select("Agent memory:", _MEMORY, "none")
|
|
82
|
+
use_mcp = questionary.confirm("Integrate MCP tools?", default=False).ask()
|
|
83
|
+
use_skills = questionary.confirm("Add a skills registry?", default=False).ask()
|
|
84
|
+
custom_prompts = questionary.confirm("Scaffold custom prompt templates (vs defaults)?", default=False).ask()
|
|
85
|
+
|
|
86
|
+
# Enterprise pack — bundle or pick individually
|
|
87
|
+
enterprise = questionary.confirm(
|
|
88
|
+
"Enable the enterprise pack (tracing, guardrails, FastAPI, Docker, CI, evals)?",
|
|
89
|
+
default=False,
|
|
90
|
+
).ask()
|
|
91
|
+
if enterprise:
|
|
92
|
+
observability = guardrails = serve = docker = ci = evals = True
|
|
93
|
+
else:
|
|
94
|
+
observability = questionary.confirm(" • OpenTelemetry/Langfuse observability?", default=False).ask()
|
|
95
|
+
guardrails = questionary.confirm(" • Input/output guardrails?", default=False).ask()
|
|
96
|
+
serve = questionary.confirm(" • FastAPI server (REST + SSE)?", default=False).ask()
|
|
97
|
+
docker = questionary.confirm(" • Dockerfile + docker-compose?", default=False).ask()
|
|
98
|
+
ci = questionary.confirm(" • GitHub Actions CI?", default=False).ask()
|
|
99
|
+
evals = questionary.confirm(" • LLM-as-judge eval harness?", default=False).ask()
|
|
100
|
+
|
|
101
|
+
create_venv = questionary.confirm("Create a .venv with `uv` now?", default=True).ask()
|
|
102
|
+
run_sync = False
|
|
103
|
+
if create_venv:
|
|
104
|
+
run_sync = questionary.confirm("Install dependencies now (`uv sync`)? (needs network)", default=False).ask()
|
|
105
|
+
|
|
106
|
+
return ProjectSpec(
|
|
107
|
+
name=name,
|
|
108
|
+
framework=framework,
|
|
109
|
+
provider=provider,
|
|
110
|
+
model=model,
|
|
111
|
+
agents=agents,
|
|
112
|
+
use_rag=bool(use_rag),
|
|
113
|
+
memory=memory or "none",
|
|
114
|
+
use_mcp=bool(use_mcp),
|
|
115
|
+
use_skills=bool(use_skills),
|
|
116
|
+
prompt_style="custom" if custom_prompts else "default",
|
|
117
|
+
observability=bool(observability),
|
|
118
|
+
guardrails=bool(guardrails),
|
|
119
|
+
serve=bool(serve),
|
|
120
|
+
docker=bool(docker),
|
|
121
|
+
ci=bool(ci),
|
|
122
|
+
evals=bool(evals),
|
|
123
|
+
create_venv=bool(create_venv),
|
|
124
|
+
run_sync=bool(run_sync),
|
|
125
|
+
)
|