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.
Files changed (49) hide show
  1. agent_fabric/__init__.py +55 -0
  2. agent_fabric/agents/__init__.py +39 -0
  3. agent_fabric/agents/base.py +155 -0
  4. agent_fabric/agents/fabric.py +496 -0
  5. agent_fabric/agents/kinds.py +641 -0
  6. agent_fabric/agents/runtime.py +234 -0
  7. agent_fabric/agents/spec.py +244 -0
  8. agent_fabric/artifacts.py +206 -0
  9. agent_fabric/cli/__init__.py +113 -0
  10. agent_fabric/cli/__main__.py +5 -0
  11. agent_fabric/cli/commands.py +153 -0
  12. agent_fabric/cli/live.py +83 -0
  13. agent_fabric/cli/render.py +127 -0
  14. agent_fabric/compat.py +67 -0
  15. agent_fabric/component.py +318 -0
  16. agent_fabric/contracts.py +223 -0
  17. agent_fabric/errors.py +243 -0
  18. agent_fabric/evals.py +185 -0
  19. agent_fabric/executor.py +235 -0
  20. agent_fabric/integrations/__init__.py +1 -0
  21. agent_fabric/integrations/crewai.py +112 -0
  22. agent_fabric/integrations/dspy.py +169 -0
  23. agent_fabric/lint.py +379 -0
  24. agent_fabric/llm/__init__.py +25 -0
  25. agent_fabric/llm/backends.py +152 -0
  26. agent_fabric/llm/interpreter.py +187 -0
  27. agent_fabric/llm/planner.py +236 -0
  28. agent_fabric/llm/prompts.py +118 -0
  29. agent_fabric/llm/repair.py +79 -0
  30. agent_fabric/llm/self_correction.py +188 -0
  31. agent_fabric/markdown.py +163 -0
  32. agent_fabric/memory/__init__.py +11 -0
  33. agent_fabric/memory/base.py +115 -0
  34. agent_fabric/memory/langchain_adapter.py +87 -0
  35. agent_fabric/pipeline.py +469 -0
  36. agent_fabric/prompt.py +270 -0
  37. agent_fabric/py.typed +0 -0
  38. agent_fabric/registry.py +309 -0
  39. agent_fabric/report.py +52 -0
  40. agent_fabric/scaffold.py +167 -0
  41. agent_fabric/skills_export.py +194 -0
  42. agent_fabric/spec.py +330 -0
  43. agent_fabric/tabular.py +354 -0
  44. agent_fabric/testing.py +18 -0
  45. spec_agents_core-0.5.1.dist-info/METADATA +93 -0
  46. spec_agents_core-0.5.1.dist-info/RECORD +49 -0
  47. spec_agents_core-0.5.1.dist-info/WHEEL +4 -0
  48. spec_agents_core-0.5.1.dist-info/entry_points.txt +2 -0
  49. spec_agents_core-0.5.1.dist-info/licenses/LICENSE +21 -0
@@ -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
+ ]