spec-agents-core 0.5.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.
- agent_fabric/__init__.py +55 -0
- agent_fabric/agents/__init__.py +39 -0
- agent_fabric/agents/base.py +155 -0
- agent_fabric/agents/fabric.py +496 -0
- agent_fabric/agents/kinds.py +641 -0
- agent_fabric/agents/runtime.py +234 -0
- agent_fabric/agents/spec.py +244 -0
- agent_fabric/artifacts.py +206 -0
- agent_fabric/cli/__init__.py +113 -0
- agent_fabric/cli/__main__.py +5 -0
- agent_fabric/cli/commands.py +153 -0
- agent_fabric/cli/live.py +83 -0
- agent_fabric/cli/render.py +127 -0
- agent_fabric/compat.py +67 -0
- agent_fabric/component.py +318 -0
- agent_fabric/contracts.py +223 -0
- agent_fabric/errors.py +243 -0
- agent_fabric/evals.py +185 -0
- agent_fabric/executor.py +235 -0
- agent_fabric/integrations/__init__.py +1 -0
- agent_fabric/integrations/crewai.py +112 -0
- agent_fabric/integrations/dspy.py +169 -0
- agent_fabric/lint.py +379 -0
- agent_fabric/llm/__init__.py +25 -0
- agent_fabric/llm/backends.py +152 -0
- agent_fabric/llm/interpreter.py +187 -0
- agent_fabric/llm/planner.py +236 -0
- agent_fabric/llm/prompts.py +118 -0
- agent_fabric/llm/repair.py +79 -0
- agent_fabric/llm/self_correction.py +188 -0
- agent_fabric/markdown.py +163 -0
- agent_fabric/memory/__init__.py +11 -0
- agent_fabric/memory/base.py +115 -0
- agent_fabric/memory/langchain_adapter.py +87 -0
- agent_fabric/pipeline.py +469 -0
- agent_fabric/prompt.py +270 -0
- agent_fabric/py.typed +0 -0
- agent_fabric/registry.py +309 -0
- agent_fabric/report.py +52 -0
- agent_fabric/scaffold.py +167 -0
- agent_fabric/skills_export.py +194 -0
- agent_fabric/spec.py +330 -0
- agent_fabric/tabular.py +354 -0
- agent_fabric/testing.py +18 -0
- spec_agents_core-0.5.1.dist-info/METADATA +93 -0
- spec_agents_core-0.5.1.dist-info/RECORD +49 -0
- spec_agents_core-0.5.1.dist-info/WHEEL +4 -0
- spec_agents_core-0.5.1.dist-info/entry_points.txt +2 -0
- spec_agents_core-0.5.1.dist-info/licenses/LICENSE +21 -0
agent_fabric/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""agent_fabric - spec-driven pipelines and hierarchical agents over a LiteLLM proxy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
from importlib import metadata as _metadata
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = _metadata.version(
|
|
10
|
+
"spec-agents-core"
|
|
11
|
+
) # dynamic: derived from the latest agent-fabric-v* git tag at build time
|
|
12
|
+
except _metadata.PackageNotFoundError: # pragma: no cover - source tree without an installed distribution
|
|
13
|
+
__version__ = "0.0.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def domain_loader(ref: str) -> Callable:
|
|
17
|
+
"""Resolve a domain reference: 'module:register' callable, or an existing directory
|
|
18
|
+
loaded as a code-free spec pack (skills/<name>/SKILL.md, no Python required)."""
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
p = Path(ref)
|
|
22
|
+
if ":" not in ref and p.is_dir():
|
|
23
|
+
return lambda reg: reg.load_domain(p)
|
|
24
|
+
import importlib
|
|
25
|
+
|
|
26
|
+
mod, _, attr = ref.partition(":")
|
|
27
|
+
return getattr(importlib.import_module(mod), attr or "register")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_registry(domains: Iterable[Callable | str] = (), discover: bool = False):
|
|
31
|
+
"""Create a Registry and load domain packs (callables ``register(registry)`` or
|
|
32
|
+
references resolved by :func:`domain_loader`)."""
|
|
33
|
+
from .compat import check_pack_api
|
|
34
|
+
from .registry import Registry
|
|
35
|
+
|
|
36
|
+
reg = Registry()
|
|
37
|
+
for d in domains:
|
|
38
|
+
register = d if callable(d) else domain_loader(d)
|
|
39
|
+
check_pack_api(register, d if isinstance(d, str) else getattr(d, "__module__", repr(d)))
|
|
40
|
+
register(reg)
|
|
41
|
+
if discover:
|
|
42
|
+
reg.discover_domains()
|
|
43
|
+
return reg
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
from .compat import API_LEVEL, API_LEVEL_MIN, requires_api # noqa: E402
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"API_LEVEL",
|
|
50
|
+
"API_LEVEL_MIN",
|
|
51
|
+
"__version__",
|
|
52
|
+
"build_registry",
|
|
53
|
+
"domain_loader",
|
|
54
|
+
"requires_api",
|
|
55
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Hierarchical agents: declared as a tree in YAML, validated as a whole, built by registered builders."""
|
|
2
|
+
|
|
3
|
+
from .base import BaseAgent
|
|
4
|
+
from .fabric import AgentFabric, planner_builder
|
|
5
|
+
from .kinds import (
|
|
6
|
+
Delegation,
|
|
7
|
+
DelegationPlan,
|
|
8
|
+
FunctionAgent,
|
|
9
|
+
LLMWorkerAgent,
|
|
10
|
+
PipelineAgent,
|
|
11
|
+
PlannerAgent,
|
|
12
|
+
PydanticAISupervisor,
|
|
13
|
+
SupervisorAgent,
|
|
14
|
+
)
|
|
15
|
+
from .runtime import AgentResult, AgentRunReport, AgentTask, Budget, BudgetSettings, RunContext, TraceEvent
|
|
16
|
+
from .spec import AgentsConfig, AgentSpec
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AgentFabric",
|
|
20
|
+
"AgentResult",
|
|
21
|
+
"AgentRunReport",
|
|
22
|
+
"AgentSpec",
|
|
23
|
+
"AgentTask",
|
|
24
|
+
"AgentsConfig",
|
|
25
|
+
"BaseAgent",
|
|
26
|
+
"Budget",
|
|
27
|
+
"BudgetSettings",
|
|
28
|
+
"Delegation",
|
|
29
|
+
"DelegationPlan",
|
|
30
|
+
"FunctionAgent",
|
|
31
|
+
"LLMWorkerAgent",
|
|
32
|
+
"PipelineAgent",
|
|
33
|
+
"PlannerAgent",
|
|
34
|
+
"PydanticAISupervisor",
|
|
35
|
+
"RunContext",
|
|
36
|
+
"SupervisorAgent",
|
|
37
|
+
"TraceEvent",
|
|
38
|
+
"planner_builder",
|
|
39
|
+
]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Base agent: template method shared by every kind (budget, trace, fallback, memory)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from ..errors import BudgetExceeded, FabricError
|
|
10
|
+
from ..llm.backends import LLMSettings
|
|
11
|
+
from ..llm.prompts import AGENT_SYSTEM, compact
|
|
12
|
+
from ..memory.base import RunSummary, memory_context, namespaced
|
|
13
|
+
from .runtime import AgentResult, AgentTask, MeteredBackend, RunContext, Status
|
|
14
|
+
from .spec import AgentSpec
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
17
|
+
from .fabric import AgentFabric
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseAgent(ABC):
|
|
21
|
+
accepts_sub_agents: bool = False
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self, name: str, spec: AgentSpec, fabric: AgentFabric, children: dict[str, BaseAgent] | None = None
|
|
25
|
+
) -> None:
|
|
26
|
+
self.name, self.spec, self.fabric = name, spec, fabric
|
|
27
|
+
self.children: dict[str, BaseAgent] = children or {}
|
|
28
|
+
self.fallback_agent: BaseAgent | None = None
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------------ template method
|
|
31
|
+
def run(self, task: AgentTask, ctx: RunContext, parent_path: str = "", depth: int = 0) -> AgentResult:
|
|
32
|
+
"""Run the agent on a task: the template method every agent goes through.
|
|
33
|
+
|
|
34
|
+
Records the start and end events, charges the run budget (``max_depth`` and ``max_agent_runs``) and calls the kind-specific ``_run``. A
|
|
35
|
+
`BudgetExceeded` or other `FabricError` becomes a ``failed`` result carrying the located error; when a ``fallback`` backend is configured it is
|
|
36
|
+
tried first and the fallback is noted in ``notes``. Records LLM calls and duration, and, when memory is configured, the instruction, the answer and a
|
|
37
|
+
`RunSummary` under ``<session>/<path>``. Does not raise `FabricError`s: they end up in the result.
|
|
38
|
+
"""
|
|
39
|
+
path = f"{parent_path}/{self.name}" if parent_path else self.name
|
|
40
|
+
t0 = time.perf_counter()
|
|
41
|
+
ctx.event(path, "start", task.instruction)
|
|
42
|
+
try:
|
|
43
|
+
ctx.budget.charge_run(path, depth)
|
|
44
|
+
res = self._run(task, ctx, path, depth)
|
|
45
|
+
except BudgetExceeded as exc:
|
|
46
|
+
res = self.result(path, "failed", error=exc.report, summary=exc.message)
|
|
47
|
+
except FabricError as exc:
|
|
48
|
+
ctx.event(path, "error", f"{exc.category.value}: {exc.message}")
|
|
49
|
+
if self.fallback_agent is None:
|
|
50
|
+
res = self.result(path, "failed", error=exc.report, summary=exc.message)
|
|
51
|
+
else:
|
|
52
|
+
ctx.event(path, "fallback", self.fallback_agent.spec.backend)
|
|
53
|
+
try:
|
|
54
|
+
res = self.fallback_agent._run(task, ctx, path, depth)
|
|
55
|
+
res.notes.append(
|
|
56
|
+
f"fell back to backend '{self.fallback_agent.spec.backend}' after "
|
|
57
|
+
f"{exc.category.value} error: {exc.message}"
|
|
58
|
+
)
|
|
59
|
+
except FabricError as exc2:
|
|
60
|
+
res = self.result(
|
|
61
|
+
path,
|
|
62
|
+
"failed",
|
|
63
|
+
error=exc2.report,
|
|
64
|
+
summary=exc2.message,
|
|
65
|
+
notes=[f"primary failed: {exc.message}"],
|
|
66
|
+
)
|
|
67
|
+
res.llm_calls = ctx.llm_calls_by_path.get(path, 0)
|
|
68
|
+
res.duration_s = round(time.perf_counter() - t0, 4)
|
|
69
|
+
ctx.event(path, "end", res.status)
|
|
70
|
+
if ctx.memory is not None:
|
|
71
|
+
ctx.memory.add_message(namespaced(ctx.session_id, path), "user", task.instruction)
|
|
72
|
+
ctx.memory.add_message(namespaced(ctx.session_id, path), "assistant", res.summary or res.status)
|
|
73
|
+
ctx.memory.remember_run(
|
|
74
|
+
namespaced(ctx.session_id, path),
|
|
75
|
+
RunSummary(
|
|
76
|
+
objective=task.instruction, agent=path, status={path: res.status}, headlines=[res.summary[:200]]
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
return res
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def _run(self, task: AgentTask, ctx: RunContext, path: str, depth: int) -> AgentResult: ...
|
|
83
|
+
|
|
84
|
+
# ------------------------------------------------------------------ helpers
|
|
85
|
+
@property
|
|
86
|
+
def kind(self) -> str:
|
|
87
|
+
"""The agent kind (``supervisor``, ``planner``, ``pipeline``, ``llm`` or ``function``)."""
|
|
88
|
+
return self.spec.kind
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def settings(self) -> LLMSettings:
|
|
92
|
+
"""`LLMSettings` for this agent: the run's settings with the agent's own model, temperature and retries applied."""
|
|
93
|
+
return self.fabric.settings_for(self.spec)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def model(self) -> str:
|
|
97
|
+
"""LiteLLM alias this agent uses (its own ``model``, else the default for its kind)."""
|
|
98
|
+
return self.fabric.model_for(self.spec)
|
|
99
|
+
|
|
100
|
+
def llm(self, ctx: RunContext, path: str) -> MeteredBackend:
|
|
101
|
+
"""The LLM backend for this agent, metered so every call is charged to the run budget and traced at ``path``."""
|
|
102
|
+
return ctx.metered(self.fabric.llm_backend, path)
|
|
103
|
+
|
|
104
|
+
def result(self, path: str, status: Status, **kw: Any) -> AgentResult:
|
|
105
|
+
"""Build an `AgentResult` for this agent at ``path`` with the given status and extra fields."""
|
|
106
|
+
return AgentResult(agent=self.name, path=path, kind=self.kind, status=status, **kw)
|
|
107
|
+
|
|
108
|
+
def system_prompt(self) -> str:
|
|
109
|
+
"""Role header (if any) followed by the AGENT.md body - the body is the primary instruction source."""
|
|
110
|
+
s = self.spec
|
|
111
|
+
parts = []
|
|
112
|
+
if s.role or s.goal:
|
|
113
|
+
parts.append(
|
|
114
|
+
AGENT_SYSTEM.format(
|
|
115
|
+
role=s.role or self.name.replace("_", " "), goal=s.goal or s.card, backstory=s.backstory
|
|
116
|
+
).strip()
|
|
117
|
+
)
|
|
118
|
+
if s.instructions:
|
|
119
|
+
parts.append(s.instructions)
|
|
120
|
+
return "\n\n".join(parts) or f"You are the '{self.name}' agent."
|
|
121
|
+
|
|
122
|
+
def input_keys(self, task: AgentTask, ctx: RunContext) -> list[str]:
|
|
123
|
+
"""Blackboard keys this agent reads: the task's own inputs, else the spec's ``inputs``, else every key the run started with."""
|
|
124
|
+
return task.inputs or self.spec.inputs or list(ctx.input_keys)
|
|
125
|
+
|
|
126
|
+
def describe_inputs(self, values: dict[str, Any]) -> str:
|
|
127
|
+
"""Describe input values for a prompt: JSON, number and ``any`` values are shown compacted (up to 2500 characters), other types through their artifact profile."""
|
|
128
|
+
types = self.fabric.registry.types
|
|
129
|
+
lines = []
|
|
130
|
+
for key, v in values.items():
|
|
131
|
+
t = types.infer(v)
|
|
132
|
+
if t.name in ("json", "any", "number"):
|
|
133
|
+
lines.append(f"[{key}] {compact(v, max_chars=2500)}")
|
|
134
|
+
else:
|
|
135
|
+
lines.append(f"[{key}] {t.describe(t.profile(v), v)}")
|
|
136
|
+
return "\n".join(lines) or "(none)"
|
|
137
|
+
|
|
138
|
+
def memory_context(self, ctx: RunContext, path: str) -> str:
|
|
139
|
+
"""Text of this agent's recent runs in the session, for routers and planners."""
|
|
140
|
+
return memory_context(ctx.memory, namespaced(ctx.session_id, path))
|
|
141
|
+
|
|
142
|
+
def card(self) -> dict[str, Any]:
|
|
143
|
+
"""Short description of the agent for parent routers: name, kind, role, what it does and, for supervisors, its sub-agents."""
|
|
144
|
+
return {
|
|
145
|
+
"name": self.name,
|
|
146
|
+
"kind": self.kind,
|
|
147
|
+
"role": self.spec.role,
|
|
148
|
+
"does": self.spec.card,
|
|
149
|
+
**({"sub_agents": sorted(self.children)} if self.children else {}),
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
__all__ = [
|
|
154
|
+
"BaseAgent",
|
|
155
|
+
]
|