cemaf 0.1.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.
- cemaf/__init__.py +34 -0
- cemaf/agents/__init__.py +63 -0
- cemaf/agents/base.py +191 -0
- cemaf/agents/factories.py +141 -0
- cemaf/agents/protocols.py +220 -0
- cemaf/blueprint/__init__.py +31 -0
- cemaf/blueprint/builder.py +260 -0
- cemaf/blueprint/factories.py +109 -0
- cemaf/blueprint/mock.py +38 -0
- cemaf/blueprint/protocols.py +113 -0
- cemaf/blueprint/rules.py +264 -0
- cemaf/blueprint/schema.py +188 -0
- cemaf/cache/__init__.py +52 -0
- cemaf/cache/decorators.py +153 -0
- cemaf/cache/factories.py +116 -0
- cemaf/cache/mock.py +140 -0
- cemaf/cache/protocols.py +154 -0
- cemaf/cache/stores.py +101 -0
- cemaf/citation/__init__.py +34 -0
- cemaf/citation/factories.py +121 -0
- cemaf/citation/mock.py +44 -0
- cemaf/citation/models.py +244 -0
- cemaf/citation/protocols.py +116 -0
- cemaf/citation/rules.py +211 -0
- cemaf/citation/tracker.py +206 -0
- cemaf/config/__init__.py +31 -0
- cemaf/config/factories.py +66 -0
- cemaf/config/loader.py +217 -0
- cemaf/config/mock.py +86 -0
- cemaf/config/protocols.py +423 -0
- cemaf/context/__init__.py +81 -0
- cemaf/context/advanced_compiler.py +278 -0
- cemaf/context/algorithm.py +350 -0
- cemaf/context/budget.py +94 -0
- cemaf/context/compiler.py +262 -0
- cemaf/context/context.py +286 -0
- cemaf/context/factories.py +271 -0
- cemaf/context/merge.py +553 -0
- cemaf/context/patch.py +353 -0
- cemaf/context/paths.py +266 -0
- cemaf/context/protocols.py +137 -0
- cemaf/context/source.py +390 -0
- cemaf/core/__init__.py +63 -0
- cemaf/core/constants.py +67 -0
- cemaf/core/enums.py +72 -0
- cemaf/core/execution.py +393 -0
- cemaf/core/registry.py +429 -0
- cemaf/core/result.py +123 -0
- cemaf/core/storage.py +203 -0
- cemaf/core/types.py +24 -0
- cemaf/core/utils.py +106 -0
- cemaf/evals/__init__.py +48 -0
- cemaf/evals/composite.py +297 -0
- cemaf/evals/evaluators.py +351 -0
- cemaf/evals/factories.py +119 -0
- cemaf/evals/llm_judge.py +215 -0
- cemaf/evals/protocols.py +233 -0
- cemaf/evals/semantic.py +166 -0
- cemaf/events/__init__.py +45 -0
- cemaf/events/bus.py +96 -0
- cemaf/events/factories.py +65 -0
- cemaf/events/mock.py +177 -0
- cemaf/events/notifiers.py +207 -0
- cemaf/events/protocols.py +271 -0
- cemaf/generation/__init__.py +49 -0
- cemaf/generation/factories.py +219 -0
- cemaf/generation/mock.py +350 -0
- cemaf/generation/protocols.py +545 -0
- cemaf/ingestion/__init__.py +47 -0
- cemaf/ingestion/adapters.py +413 -0
- cemaf/ingestion/factories.py +195 -0
- cemaf/ingestion/protocols.py +155 -0
- cemaf/llm/__init__.py +65 -0
- cemaf/llm/factories.py +132 -0
- cemaf/llm/mock.py +147 -0
- cemaf/llm/protocols.py +360 -0
- cemaf/llm/response_utils.py +376 -0
- cemaf/llm/tiktoken_estimator.py +136 -0
- cemaf/mcp/__init__.py +54 -0
- cemaf/mcp/adapter.py +766 -0
- cemaf/mcp/bridges/__init__.py +7 -0
- cemaf/mcp/bridges/prompt_bridge.py +87 -0
- cemaf/mcp/bridges/resource_bridge.py +69 -0
- cemaf/mcp/bridges/tool_bridge.py +59 -0
- cemaf/mcp/factories.py +71 -0
- cemaf/mcp/mock.py +82 -0
- cemaf/mcp/protocols.py +404 -0
- cemaf/mcp/transport/__init__.py +8 -0
- cemaf/mcp/transport/base.py +60 -0
- cemaf/mcp/transport/sse.py +60 -0
- cemaf/mcp/transport/stdio.py +52 -0
- cemaf/mcp/transport/websocket.py +44 -0
- cemaf/mcp/types.py +138 -0
- cemaf/memory/__init__.py +69 -0
- cemaf/memory/base.py +272 -0
- cemaf/memory/factories.py +130 -0
- cemaf/memory/protocols.py +237 -0
- cemaf/moderation/__init__.py +62 -0
- cemaf/moderation/factories.py +65 -0
- cemaf/moderation/gates.py +407 -0
- cemaf/moderation/mock.py +519 -0
- cemaf/moderation/pipeline.py +397 -0
- cemaf/moderation/protocols.py +181 -0
- cemaf/moderation/rules.py +443 -0
- cemaf/observability/__init__.py +38 -0
- cemaf/observability/factories.py +226 -0
- cemaf/observability/protocols.py +93 -0
- cemaf/observability/run_logger.py +406 -0
- cemaf/observability/simple.py +116 -0
- cemaf/orchestration/__init__.py +24 -0
- cemaf/orchestration/checkpointer.py +289 -0
- cemaf/orchestration/dag.py +552 -0
- cemaf/orchestration/deep_agent.py +412 -0
- cemaf/orchestration/executor.py +723 -0
- cemaf/orchestration/factories.py +112 -0
- cemaf/orchestration/protocols.py +141 -0
- cemaf/persistence/__init__.py +38 -0
- cemaf/persistence/entities.py +149 -0
- cemaf/persistence/factories.py +190 -0
- cemaf/persistence/protocols.py +151 -0
- cemaf/replay/__init__.py +15 -0
- cemaf/replay/factories.py +51 -0
- cemaf/replay/protocols.py +44 -0
- cemaf/replay/replayer.py +317 -0
- cemaf/resilience/__init__.py +97 -0
- cemaf/resilience/circuit_breaker.py +227 -0
- cemaf/resilience/decorators.py +163 -0
- cemaf/resilience/factories.py +188 -0
- cemaf/resilience/protocols.py +269 -0
- cemaf/resilience/rate_limiter.py +173 -0
- cemaf/resilience/retry.py +197 -0
- cemaf/retrieval/__init__.py +51 -0
- cemaf/retrieval/factories.py +205 -0
- cemaf/retrieval/hybrid.py +171 -0
- cemaf/retrieval/memory_store.py +169 -0
- cemaf/retrieval/protocols.py +231 -0
- cemaf/scheduler/__init__.py +41 -0
- cemaf/scheduler/executor.py +198 -0
- cemaf/scheduler/factories.py +65 -0
- cemaf/scheduler/mock.py +145 -0
- cemaf/scheduler/protocols.py +208 -0
- cemaf/scheduler/triggers.py +283 -0
- cemaf/skills/__init__.py +65 -0
- cemaf/skills/base.py +149 -0
- cemaf/skills/factories.py +114 -0
- cemaf/skills/protocols.py +248 -0
- cemaf/skills/registry.py +114 -0
- cemaf/streaming/__init__.py +24 -0
- cemaf/streaming/factories.py +65 -0
- cemaf/streaming/protocols.py +239 -0
- cemaf/streaming/sse.py +96 -0
- cemaf/tools/__init__.py +71 -0
- cemaf/tools/base.py +229 -0
- cemaf/tools/factories.py +128 -0
- cemaf/tools/protocols.py +275 -0
- cemaf/tools/registry.py +156 -0
- cemaf/validation/__init__.py +48 -0
- cemaf/validation/factories.py +85 -0
- cemaf/validation/mock.py +109 -0
- cemaf/validation/pipeline.py +128 -0
- cemaf/validation/protocols.py +160 -0
- cemaf/validation/rules.py +324 -0
- cemaf-0.1.0.dist-info/METADATA +371 -0
- cemaf-0.1.0.dist-info/RECORD +166 -0
- cemaf-0.1.0.dist-info/WHEEL +4 -0
- cemaf-0.1.0.dist-info/licenses/LICENSE +21 -0
cemaf/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CEMAF - Context Engineering Multi-Agent Framework
|
|
3
|
+
|
|
4
|
+
A pluggable, modular framework for building AI agent systems with:
|
|
5
|
+
- Tools: Atomic, stateless functions
|
|
6
|
+
- Skills: Composable capabilities using tools
|
|
7
|
+
- Agents: Autonomous entities with goals and memory
|
|
8
|
+
- DeepAgent: Hierarchical orchestration with context isolation
|
|
9
|
+
- Dynamic DAGs: Runtime workflow composition
|
|
10
|
+
|
|
11
|
+
Author: Hikuri Bado Chinca (@drchinca)
|
|
12
|
+
Email: chincadr@gmail.com
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
|
|
17
|
+
from cemaf.core.enums import AgentStatus, MemoryScope, NodeType, RunStatus
|
|
18
|
+
from cemaf.core.types import JSON, AgentID, NodeID, RunID, SkillID, ToolID
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"__version__",
|
|
22
|
+
# Types
|
|
23
|
+
"JSON",
|
|
24
|
+
"AgentID",
|
|
25
|
+
"NodeID",
|
|
26
|
+
"RunID",
|
|
27
|
+
"SkillID",
|
|
28
|
+
"ToolID",
|
|
29
|
+
# Enums
|
|
30
|
+
"AgentStatus",
|
|
31
|
+
"MemoryScope",
|
|
32
|
+
"NodeType",
|
|
33
|
+
"RunStatus",
|
|
34
|
+
]
|
cemaf/agents/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agents module - Autonomous entities with goals and memory.
|
|
3
|
+
|
|
4
|
+
Agents are the HIGHEST level of the execution hierarchy:
|
|
5
|
+
- AUTONOMOUS: Make decisions about which skills to use
|
|
6
|
+
- GOAL-ORIENTED: Work toward completing objectives
|
|
7
|
+
- MEMORY-ENABLED: Maintain state across interactions
|
|
8
|
+
- CONTEXT-AWARE: Have isolated context scope
|
|
9
|
+
|
|
10
|
+
Agents USE Skills (which USE Tools).
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Settings for this module are defined in AgentsSettings.
|
|
15
|
+
|
|
16
|
+
Environment Variables:
|
|
17
|
+
CEMAF_AGENTS_MAX_ITERATIONS: Max agent iterations (default: 10)
|
|
18
|
+
CEMAF_AGENTS_MAX_SKILL_CALLS: Max skill calls per agent (default: 50)
|
|
19
|
+
CEMAF_AGENTS_TIMEOUT_SECONDS: Agent timeout in seconds (default: 300.0)
|
|
20
|
+
CEMAF_AGENTS_DEEP_AGENT_MAX_DEPTH: Max depth for DeepAgent (default: 5)
|
|
21
|
+
CEMAF_AGENTS_DEEP_AGENT_MAX_CHILDREN: Max children per node (default: 10)
|
|
22
|
+
CEMAF_AGENTS_DEEP_AGENT_MAX_TOTAL: Max total agents (default: 100)
|
|
23
|
+
CEMAF_AGENTS_DEEP_AGENT_TIMEOUT_SECONDS: DeepAgent timeout (default: 600.0)
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Protocol-based (Recommended):
|
|
28
|
+
>>> from cemaf.agents import Agent, AgentContext, AgentResult, AgentState
|
|
29
|
+
>>> from cemaf.core.types import AgentID
|
|
30
|
+
>>>
|
|
31
|
+
>>> class MyAgent:
|
|
32
|
+
... @property
|
|
33
|
+
... def id(self) -> AgentID:
|
|
34
|
+
... return AgentID("my_agent")
|
|
35
|
+
...
|
|
36
|
+
... @property
|
|
37
|
+
... def description(self) -> str:
|
|
38
|
+
... return "My custom agent"
|
|
39
|
+
...
|
|
40
|
+
... @property
|
|
41
|
+
... def skills(self) -> tuple:
|
|
42
|
+
... return ()
|
|
43
|
+
...
|
|
44
|
+
... async def run(self, goal, context: AgentContext) -> AgentResult:
|
|
45
|
+
... return AgentResult.ok("result", AgentState())
|
|
46
|
+
|
|
47
|
+
## Extension
|
|
48
|
+
|
|
49
|
+
Agent implementations are discovered via protocols. No registration needed.
|
|
50
|
+
Simply implement the Agent protocol and your agent is compatible with all
|
|
51
|
+
CEMAF orchestration systems.
|
|
52
|
+
|
|
53
|
+
See cemaf.agents.protocols.Agent for the protocol definition.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
from cemaf.agents.protocols import Agent, AgentContext, AgentResult, AgentState
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"Agent",
|
|
60
|
+
"AgentState",
|
|
61
|
+
"AgentResult",
|
|
62
|
+
"AgentContext",
|
|
63
|
+
]
|
cemaf/agents/base.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent base classes.
|
|
3
|
+
|
|
4
|
+
An Agent is:
|
|
5
|
+
- Autonomous entity with a goal
|
|
6
|
+
- Uses skills to accomplish tasks
|
|
7
|
+
- Maintains state/memory
|
|
8
|
+
- Can make decisions
|
|
9
|
+
|
|
10
|
+
## Best Practices
|
|
11
|
+
|
|
12
|
+
### Using VectorStore and Memory Stores
|
|
13
|
+
|
|
14
|
+
When skills or agents retrieve from vector stores or memory stores, they should:
|
|
15
|
+
|
|
16
|
+
1. **Emit context patches** for retrieval results so downstream nodes can reuse them:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
# In a skill or agent:
|
|
20
|
+
retrieved_docs = await vector_store.search(query, top_k=5)
|
|
21
|
+
|
|
22
|
+
# Patch retrieval results into context
|
|
23
|
+
context = context.set("retrieved_docs", retrieved_docs)
|
|
24
|
+
# Or use ContextPatch explicitly for better provenance tracking
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. **Store retrieval metadata** (query, timestamp, scores) for debugging:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
metadata = {
|
|
31
|
+
"query": query,
|
|
32
|
+
"num_results": len(results),
|
|
33
|
+
"top_score": results[0].score if results else None,
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. **Use caching** for expensive retrieval operations (see below)
|
|
38
|
+
|
|
39
|
+
### Resilience Patterns
|
|
40
|
+
|
|
41
|
+
Use resilience decorators around expensive or flaky operations:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from cemaf.resilience import retry, circuit_breaker
|
|
45
|
+
|
|
46
|
+
class RetrievalSkill(Skill):
|
|
47
|
+
@retry(max_attempts=3, backoff_factor=1.5)
|
|
48
|
+
@circuit_breaker(failure_threshold=5, recovery_timeout=60)
|
|
49
|
+
async def search_vector_store(self, query: str) -> list[Document]:
|
|
50
|
+
# Expensive retrieval operation
|
|
51
|
+
return await self.vector_store.search(query)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Caching Patterns
|
|
55
|
+
|
|
56
|
+
Use caching decorators for expensive calls (LLM, retrieval):
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from cemaf.cache import cache_result
|
|
60
|
+
|
|
61
|
+
class AnalysisAgent(Agent):
|
|
62
|
+
@cache_result(ttl=3600) # Cache for 1 hour
|
|
63
|
+
async def analyze_with_llm(self, text: str) -> AnalysisResult:
|
|
64
|
+
# Expensive LLM call
|
|
65
|
+
return await self.llm.complete(...)
|
|
66
|
+
|
|
67
|
+
@cache_result(ttl=600, key_prefix="retrieval")
|
|
68
|
+
async def retrieve_context(self, query: str) -> list[Document]:
|
|
69
|
+
# Expensive retrieval
|
|
70
|
+
return await self.vector_store.search(query)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See examples/retrieval_dag_example.py for a complete working example.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
from abc import ABC, abstractmethod
|
|
77
|
+
from dataclasses import dataclass, field
|
|
78
|
+
from typing import Any, TypeVar
|
|
79
|
+
|
|
80
|
+
from pydantic import BaseModel, Field
|
|
81
|
+
|
|
82
|
+
from cemaf.core.enums import AgentStatus
|
|
83
|
+
from cemaf.core.types import JSON, AgentID
|
|
84
|
+
from cemaf.skills.base import Skill, SkillResult
|
|
85
|
+
|
|
86
|
+
GoalT = TypeVar("GoalT", bound=BaseModel)
|
|
87
|
+
ResultT = TypeVar("ResultT")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AgentState(BaseModel):
|
|
91
|
+
"""Mutable state during agent execution."""
|
|
92
|
+
|
|
93
|
+
model_config = {"frozen": True}
|
|
94
|
+
|
|
95
|
+
status: AgentStatus = AgentStatus.IDLE
|
|
96
|
+
iteration: int = 0
|
|
97
|
+
skill_calls: int = 0
|
|
98
|
+
messages: tuple[JSON, ...] = ()
|
|
99
|
+
working_memory: JSON = Field(default_factory=dict)
|
|
100
|
+
|
|
101
|
+
def next(self, **updates: Any) -> AgentState:
|
|
102
|
+
"""Create new state with updates."""
|
|
103
|
+
data = self.model_dump()
|
|
104
|
+
data.update(updates)
|
|
105
|
+
return AgentState(**data)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class AgentResult[ResultT]:
|
|
110
|
+
"""Result of agent execution with state trace."""
|
|
111
|
+
|
|
112
|
+
success: bool
|
|
113
|
+
output: ResultT | None = None
|
|
114
|
+
error: str | None = None
|
|
115
|
+
final_state: AgentState | None = None
|
|
116
|
+
skill_results: tuple[SkillResult, ...] = ()
|
|
117
|
+
metadata: JSON = field(default_factory=dict)
|
|
118
|
+
|
|
119
|
+
def __post_init__(self) -> None:
|
|
120
|
+
"""Ensure trace collections remain immutable and replay-safe."""
|
|
121
|
+
object.__setattr__(self, "skill_results", tuple(self.skill_results))
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def ok(cls, output: ResultT, state: AgentState) -> AgentResult[ResultT]:
|
|
125
|
+
return cls(success=True, output=output, final_state=state)
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def fail(cls, error: str, state: AgentState | None = None) -> AgentResult[ResultT]:
|
|
129
|
+
return cls(success=False, error=error, final_state=state)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AgentContext(BaseModel):
|
|
133
|
+
"""Isolated context for agent execution."""
|
|
134
|
+
|
|
135
|
+
model_config = {"frozen": True}
|
|
136
|
+
|
|
137
|
+
run_id: str
|
|
138
|
+
agent_id: str
|
|
139
|
+
parent_agent_id: str | None = None
|
|
140
|
+
depth: int = 0
|
|
141
|
+
global_memory: JSON = Field(default_factory=dict)
|
|
142
|
+
artifacts: JSON = Field(default_factory=dict)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Agent[GoalT: BaseModel, ResultT](ABC):
|
|
146
|
+
"""
|
|
147
|
+
Abstract base class for agents.
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
class AnalystAgent(Agent[AnalysisGoal, AnalysisResult]):
|
|
151
|
+
def __init__(self, sql_skill: Skill):
|
|
152
|
+
self._sql = sql_skill
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def id(self) -> AgentID:
|
|
156
|
+
return AgentID("analyst")
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def skills(self) -> tuple[Skill, ...]:
|
|
160
|
+
return (self._sql,)
|
|
161
|
+
|
|
162
|
+
async def run(self, goal: AnalysisGoal, ctx: AgentContext) -> AgentResult:
|
|
163
|
+
state = AgentState()
|
|
164
|
+
result = await self._sql.execute(...)
|
|
165
|
+
if not result.success:
|
|
166
|
+
return AgentResult.fail(result.error, state)
|
|
167
|
+
return AgentResult.ok(AnalysisResult(data=result.data), state)
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
@abstractmethod
|
|
172
|
+
def id(self) -> AgentID:
|
|
173
|
+
"""Unique identifier."""
|
|
174
|
+
...
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
@abstractmethod
|
|
178
|
+
def description(self) -> str:
|
|
179
|
+
"""What this agent does."""
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
@abstractmethod
|
|
184
|
+
def skills(self) -> tuple[Skill[Any, Any], ...]:
|
|
185
|
+
"""Skills available to this agent."""
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
@abstractmethod
|
|
189
|
+
async def run(self, goal: GoalT, context: AgentContext) -> AgentResult[ResultT]:
|
|
190
|
+
"""Execute the agent to accomplish a goal."""
|
|
191
|
+
...
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Factory functions for agent components.
|
|
3
|
+
|
|
4
|
+
Provides convenient ways to create agent contexts and configurations
|
|
5
|
+
with sensible defaults while maintaining dependency injection principles.
|
|
6
|
+
|
|
7
|
+
Note:
|
|
8
|
+
Agents are protocol-based abstractions that users implement.
|
|
9
|
+
This module provides factory functions for agent contexts and configurations,
|
|
10
|
+
not for agent instances themselves.
|
|
11
|
+
|
|
12
|
+
Extension Point:
|
|
13
|
+
This module is designed for extension. Add your custom agent
|
|
14
|
+
implementations and register them here if needed.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
from cemaf.agents.protocols import AgentContext
|
|
20
|
+
from cemaf.config.protocols import Settings
|
|
21
|
+
from cemaf.context.context import Context
|
|
22
|
+
from cemaf.core.types import AgentID
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_agent_context(
|
|
26
|
+
agent_id: AgentID,
|
|
27
|
+
context: Context | None = None,
|
|
28
|
+
max_iterations: int = 10,
|
|
29
|
+
max_skill_calls: int = 50,
|
|
30
|
+
timeout_seconds: float = 300.0,
|
|
31
|
+
) -> AgentContext:
|
|
32
|
+
"""
|
|
33
|
+
Factory for AgentContext with sensible defaults.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
agent_id: Unique agent identifier
|
|
37
|
+
context: Base context (creates new if None)
|
|
38
|
+
max_iterations: Maximum agent iterations
|
|
39
|
+
max_skill_calls: Maximum skill calls per agent
|
|
40
|
+
timeout_seconds: Agent execution timeout
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Configured AgentContext instance
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
# Basic agent context
|
|
47
|
+
from cemaf.core.types import AgentID
|
|
48
|
+
agent_ctx = create_agent_context(AgentID("my_agent"))
|
|
49
|
+
|
|
50
|
+
# With custom limits
|
|
51
|
+
agent_ctx = create_agent_context(
|
|
52
|
+
AgentID("my_agent"),
|
|
53
|
+
max_iterations=20,
|
|
54
|
+
timeout_seconds=600.0
|
|
55
|
+
)
|
|
56
|
+
"""
|
|
57
|
+
base_context = context or Context.empty()
|
|
58
|
+
|
|
59
|
+
return AgentContext(
|
|
60
|
+
agent_id=agent_id,
|
|
61
|
+
context=base_context,
|
|
62
|
+
max_iterations=max_iterations,
|
|
63
|
+
max_skill_calls=max_skill_calls,
|
|
64
|
+
timeout_seconds=timeout_seconds,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def create_agent_context_from_config(
|
|
69
|
+
agent_id: AgentID,
|
|
70
|
+
context: Context | None = None,
|
|
71
|
+
settings: Settings | None = None,
|
|
72
|
+
) -> AgentContext:
|
|
73
|
+
"""
|
|
74
|
+
Create AgentContext from environment configuration.
|
|
75
|
+
|
|
76
|
+
Reads from environment variables:
|
|
77
|
+
- CEMAF_AGENTS_MAX_ITERATIONS: Max iterations (default: 10)
|
|
78
|
+
- CEMAF_AGENTS_MAX_SKILL_CALLS: Max skill calls (default: 50)
|
|
79
|
+
- CEMAF_AGENTS_TIMEOUT_SECONDS: Timeout in seconds (default: 300.0)
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
agent_id: Unique agent identifier
|
|
83
|
+
context: Base context (creates new if None)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Configured AgentContext instance
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
# From environment
|
|
90
|
+
from cemaf.core.types import AgentID
|
|
91
|
+
agent_ctx = create_agent_context_from_config(AgentID("my_agent"))
|
|
92
|
+
"""
|
|
93
|
+
max_iterations = int(os.getenv("CEMAF_AGENTS_MAX_ITERATIONS", "10"))
|
|
94
|
+
max_skill_calls = int(os.getenv("CEMAF_AGENTS_MAX_SKILL_CALLS", "50"))
|
|
95
|
+
timeout_seconds = float(os.getenv("CEMAF_AGENTS_TIMEOUT_SECONDS", "300.0"))
|
|
96
|
+
|
|
97
|
+
return create_agent_context(
|
|
98
|
+
agent_id=agent_id,
|
|
99
|
+
context=context,
|
|
100
|
+
max_iterations=max_iterations,
|
|
101
|
+
max_skill_calls=max_skill_calls,
|
|
102
|
+
timeout_seconds=timeout_seconds,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ============================================================================
|
|
107
|
+
# EXTEND HERE: Bring Your Own Agent Implementations
|
|
108
|
+
# ============================================================================
|
|
109
|
+
# This is the extension point for custom agent implementations.
|
|
110
|
+
#
|
|
111
|
+
# To add your own agent type:
|
|
112
|
+
# 1. Implement the Agent protocol (see cemaf.agents.protocols)
|
|
113
|
+
# 2. Add a factory function below
|
|
114
|
+
# 3. Optionally add a config-based factory
|
|
115
|
+
#
|
|
116
|
+
# Example (ReAct Agent):
|
|
117
|
+
# def create_react_agent(
|
|
118
|
+
# agent_id: AgentID,
|
|
119
|
+
# llm: LLMClient,
|
|
120
|
+
# skills: tuple[Skill, ...],
|
|
121
|
+
# ) -> Agent:
|
|
122
|
+
# from your_package import ReActAgent
|
|
123
|
+
# return ReActAgent(agent_id=agent_id, llm=llm, skills=skills)
|
|
124
|
+
#
|
|
125
|
+
# def create_react_agent_from_config(
|
|
126
|
+
# agent_id: AgentID,
|
|
127
|
+
# skills: tuple[Skill, ...],
|
|
128
|
+
# , settings: Settings | None = None) -> Agent:
|
|
129
|
+
# from cemaf.llm.factories import create_llm_client_from_config
|
|
130
|
+
# llm = create_llm_client_from_config()
|
|
131
|
+
# return create_react_agent(agent_id, llm, skills)
|
|
132
|
+
#
|
|
133
|
+
# Example (Planning Agent):
|
|
134
|
+
# def create_planning_agent(
|
|
135
|
+
# agent_id: AgentID,
|
|
136
|
+
# llm: LLMClient,
|
|
137
|
+
# planner: Planner,
|
|
138
|
+
# ) -> Agent:
|
|
139
|
+
# from your_package import PlanningAgent
|
|
140
|
+
# return PlanningAgent(agent_id=agent_id, llm=llm, planner=planner)
|
|
141
|
+
# ============================================================================
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent protocols - Abstract interfaces for autonomous agents.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- Goal-oriented execution
|
|
6
|
+
- Skill composition
|
|
7
|
+
- State management
|
|
8
|
+
- Iterative reasoning
|
|
9
|
+
|
|
10
|
+
## Protocol-First Design
|
|
11
|
+
|
|
12
|
+
This module provides structural typing via @runtime_checkable protocols.
|
|
13
|
+
Any class that implements the required methods is automatically compatible.
|
|
14
|
+
|
|
15
|
+
Extension Point:
|
|
16
|
+
Custom agent implementations should implement these protocols rather than
|
|
17
|
+
inheriting from ABC classes. This allows maximum flexibility and follows
|
|
18
|
+
CEMAF's dependency injection principles.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> from cemaf.agents.protocols import Agent
|
|
22
|
+
>>> from cemaf.core.types import AgentID
|
|
23
|
+
>>>
|
|
24
|
+
>>> class MyCustomAgent:
|
|
25
|
+
... @property
|
|
26
|
+
... def id(self) -> AgentID:
|
|
27
|
+
... return AgentID("my_agent")
|
|
28
|
+
...
|
|
29
|
+
... @property
|
|
30
|
+
... def description(self) -> str:
|
|
31
|
+
... return "My custom agent"
|
|
32
|
+
...
|
|
33
|
+
... @property
|
|
34
|
+
... def skills(self) -> tuple[Any, ...]:
|
|
35
|
+
... return ()
|
|
36
|
+
...
|
|
37
|
+
... async def run(self, goal: Any, context: AgentContext) -> AgentResult:
|
|
38
|
+
... return AgentResult.ok("result", AgentState())
|
|
39
|
+
>>>
|
|
40
|
+
>>> # No inheritance needed - structural compatibility!
|
|
41
|
+
>>> assert isinstance(MyCustomAgent(), Agent)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from typing import Any, Protocol, runtime_checkable
|
|
45
|
+
|
|
46
|
+
# Re-export data classes from base (these are not changed)
|
|
47
|
+
from cemaf.agents.base import AgentContext, AgentResult, AgentState
|
|
48
|
+
from cemaf.core.types import AgentID
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"Agent",
|
|
52
|
+
"AgentContext",
|
|
53
|
+
"AgentResult",
|
|
54
|
+
"AgentState",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@runtime_checkable
|
|
59
|
+
class Agent[GoalT, ResultT](Protocol):
|
|
60
|
+
"""
|
|
61
|
+
Protocol for agent implementations.
|
|
62
|
+
|
|
63
|
+
An Agent is an autonomous entity that:
|
|
64
|
+
- Has a unique identifier
|
|
65
|
+
- Has a clear purpose/description
|
|
66
|
+
- Composes skills to accomplish goals
|
|
67
|
+
- Maintains state during execution
|
|
68
|
+
- Returns structured results
|
|
69
|
+
|
|
70
|
+
This is a protocol, not an ABC. Any class with these methods is compatible.
|
|
71
|
+
|
|
72
|
+
Type Parameters:
|
|
73
|
+
GoalT: Type of goal this agent accepts (typically a Pydantic model)
|
|
74
|
+
ResultT: Type of result this agent produces
|
|
75
|
+
|
|
76
|
+
Extension Point:
|
|
77
|
+
Implement this protocol for custom agents:
|
|
78
|
+
- ReAct agents (reasoning + acting)
|
|
79
|
+
- Planning agents (goal decomposition)
|
|
80
|
+
- Conversational agents (dialogue management)
|
|
81
|
+
- Domain-specific agents (SQL, code analysis, etc.)
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
>>> class AnalystAgent:
|
|
85
|
+
... def __init__(self, sql_skill):
|
|
86
|
+
... self._sql_skill = sql_skill
|
|
87
|
+
...
|
|
88
|
+
... @property
|
|
89
|
+
... def id(self) -> AgentID:
|
|
90
|
+
... return AgentID("analyst")
|
|
91
|
+
...
|
|
92
|
+
... @property
|
|
93
|
+
... def description(self) -> str:
|
|
94
|
+
... return "Analyzes data using SQL queries"
|
|
95
|
+
...
|
|
96
|
+
... @property
|
|
97
|
+
... def skills(self) -> tuple[Skill, ...]:
|
|
98
|
+
... return (self._sql_skill,)
|
|
99
|
+
...
|
|
100
|
+
... async def run(self, goal: AnalysisGoal, ctx: AgentContext) -> AgentResult[AnalysisResult]:
|
|
101
|
+
... state = AgentState()
|
|
102
|
+
... result = await self._sql_skill.execute(goal.query, ctx)
|
|
103
|
+
... if not result.success:
|
|
104
|
+
... return AgentResult.fail(result.error, state)
|
|
105
|
+
... return AgentResult.ok(AnalysisResult(data=result.output), state)
|
|
106
|
+
>>>
|
|
107
|
+
>>> # Automatically compatible - no inheritance!
|
|
108
|
+
>>> agent = AnalystAgent(sql_skill)
|
|
109
|
+
>>> assert isinstance(agent, Agent)
|
|
110
|
+
|
|
111
|
+
Best Practices:
|
|
112
|
+
1. **Dependency Injection**: Accept all dependencies in __init__
|
|
113
|
+
2. **Immutable State**: Return new AgentState, never mutate
|
|
114
|
+
3. **Result Pattern**: Always return AgentResult[T], never raise
|
|
115
|
+
4. **Skill Composition**: Use skills for reusable capabilities
|
|
116
|
+
5. **Context Isolation**: Each agent has isolated AgentContext
|
|
117
|
+
|
|
118
|
+
See Also:
|
|
119
|
+
- cemaf.agents.base.Agent (deprecated ABC, use this protocol instead)
|
|
120
|
+
- cemaf.skills.protocols.Skill (skill protocol)
|
|
121
|
+
- cemaf.orchestration.deep_agent.DeepAgent (hierarchical agent orchestration)
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def id(self) -> AgentID:
|
|
126
|
+
"""
|
|
127
|
+
Unique identifier for this agent.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
AgentID instance (typically AgentID("name"))
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> @property
|
|
134
|
+
>>> def id(self) -> AgentID:
|
|
135
|
+
... return AgentID("my_agent")
|
|
136
|
+
"""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def description(self) -> str:
|
|
141
|
+
"""
|
|
142
|
+
Human-readable description of what this agent does.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Clear, concise description of agent's purpose
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
>>> @property
|
|
149
|
+
>>> def description(self) -> str:
|
|
150
|
+
... return "Analyzes data and generates reports"
|
|
151
|
+
"""
|
|
152
|
+
...
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def skills(self) -> tuple[Any, ...]:
|
|
156
|
+
"""
|
|
157
|
+
Skills available to this agent.
|
|
158
|
+
|
|
159
|
+
Skills are composable capabilities that agents use to accomplish goals.
|
|
160
|
+
This tuple defines which skills this agent has access to.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Tuple of Skill instances (empty tuple if no skills)
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> @property
|
|
167
|
+
>>> def skills(self) -> tuple[Skill, ...]:
|
|
168
|
+
... return (self._sql_skill, self._analysis_skill)
|
|
169
|
+
"""
|
|
170
|
+
...
|
|
171
|
+
|
|
172
|
+
async def run(self, goal: GoalT, context: AgentContext) -> AgentResult[ResultT]:
|
|
173
|
+
"""
|
|
174
|
+
Execute the agent to accomplish a goal.
|
|
175
|
+
|
|
176
|
+
This is the main entry point for agent execution. The agent should:
|
|
177
|
+
1. Validate the goal
|
|
178
|
+
2. Plan approach (if needed)
|
|
179
|
+
3. Execute skills to accomplish goal
|
|
180
|
+
4. Track state throughout execution
|
|
181
|
+
5. Return structured result
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
goal: Goal to accomplish (typically a Pydantic model)
|
|
185
|
+
context: Execution context with run metadata, parent info, memory
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
AgentResult containing:
|
|
189
|
+
- success: Whether goal was accomplished
|
|
190
|
+
- output: The result data (if successful)
|
|
191
|
+
- error: Error message (if failed)
|
|
192
|
+
- final_state: Final agent state
|
|
193
|
+
- skill_results: Trace of skill executions
|
|
194
|
+
- metadata: Additional execution metadata
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> async def run(self, goal: AnalysisGoal, context: AgentContext) -> AgentResult[AnalysisResult]:
|
|
198
|
+
... state = AgentState()
|
|
199
|
+
...
|
|
200
|
+
... # Execute skills
|
|
201
|
+
... result = await self._sql_skill.execute(goal.query, context)
|
|
202
|
+
... state = state.next(skill_calls=1)
|
|
203
|
+
...
|
|
204
|
+
... if not result.success:
|
|
205
|
+
... return AgentResult.fail(result.error, state)
|
|
206
|
+
...
|
|
207
|
+
... # Process results
|
|
208
|
+
... analysis = self._analyze_data(result.output)
|
|
209
|
+
... state = state.next(status=AgentStatus.COMPLETED)
|
|
210
|
+
...
|
|
211
|
+
... return AgentResult.ok(AnalysisResult(data=analysis), state)
|
|
212
|
+
|
|
213
|
+
Best Practices:
|
|
214
|
+
- Always return AgentResult, never raise exceptions
|
|
215
|
+
- Track iterations and skill calls in state
|
|
216
|
+
- Include metadata for debugging/observability
|
|
217
|
+
- Use context patches to share data with downstream nodes
|
|
218
|
+
- Check cancellation tokens if long-running
|
|
219
|
+
"""
|
|
220
|
+
...
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blueprint module for defining scene blueprints.
|
|
3
|
+
|
|
4
|
+
Blueprints describe the structure and requirements for content generation scenes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from cemaf.blueprint.builder import BlueprintBuilder
|
|
8
|
+
from cemaf.blueprint.mock import MockBlueprintRegistry, create_mock_blueprint
|
|
9
|
+
from cemaf.blueprint.rules import BlueprintContentRule, BlueprintSchemaRule
|
|
10
|
+
from cemaf.blueprint.schema import (
|
|
11
|
+
Blueprint,
|
|
12
|
+
Participant,
|
|
13
|
+
SceneGoal,
|
|
14
|
+
StyleGuide,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Schema models
|
|
19
|
+
"Blueprint",
|
|
20
|
+
"Participant",
|
|
21
|
+
"SceneGoal",
|
|
22
|
+
"StyleGuide",
|
|
23
|
+
# Builder
|
|
24
|
+
"BlueprintBuilder",
|
|
25
|
+
# Validation rules
|
|
26
|
+
"BlueprintContentRule",
|
|
27
|
+
"BlueprintSchemaRule",
|
|
28
|
+
# Mocks for testing
|
|
29
|
+
"MockBlueprintRegistry",
|
|
30
|
+
"create_mock_blueprint",
|
|
31
|
+
]
|