agentledger-runtime 1.0.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.
- agentledger/__init__.py +183 -0
- agentledger/__main__.py +5 -0
- agentledger/adapters.py +62 -0
- agentledger/adapters_frameworks.py +146 -0
- agentledger/adapters_langgraph.py +140 -0
- agentledger/adapters_mcp.py +145 -0
- agentledger/approval.py +32 -0
- agentledger/backup.py +127 -0
- agentledger/blobstore.py +31 -0
- agentledger/blobstore_s3.py +118 -0
- agentledger/cli.py +1047 -0
- agentledger/conformance.py +468 -0
- agentledger/context.py +146 -0
- agentledger/contract.py +176 -0
- agentledger/cost.py +133 -0
- agentledger/diff.py +205 -0
- agentledger/eval.py +128 -0
- agentledger/evidence.py +285 -0
- agentledger/examples.py +62 -0
- agentledger/failure.py +190 -0
- agentledger/failure_injection.py +119 -0
- agentledger/ids.py +31 -0
- agentledger/jsonutil.py +31 -0
- agentledger/lint.py +415 -0
- agentledger/media.py +268 -0
- agentledger/media_tools.py +209 -0
- agentledger/policy.py +174 -0
- agentledger/protocol.py +32 -0
- agentledger/replay.py +74 -0
- agentledger/repro.py +335 -0
- agentledger/retention.py +111 -0
- agentledger/review.py +109 -0
- agentledger/runtime.py +187 -0
- agentledger/sandbox.py +749 -0
- agentledger/scheduler.py +53 -0
- agentledger/shadow.py +62 -0
- agentledger/simple.py +131 -0
- agentledger/storage_postgres.py +439 -0
- agentledger/storage_schema.py +368 -0
- agentledger/store.py +532 -0
- agentledger/timetravel.py +248 -0
- agentledger/tools.py +516 -0
- agentledger/trace.py +223 -0
- agentledger/worker.py +355 -0
- agentledger_runtime-1.0.0.dist-info/METADATA +264 -0
- agentledger_runtime-1.0.0.dist-info/RECORD +49 -0
- agentledger_runtime-1.0.0.dist-info/WHEEL +4 -0
- agentledger_runtime-1.0.0.dist-info/entry_points.txt +2 -0
- agentledger_runtime-1.0.0.dist-info/licenses/LICENSE +55 -0
agentledger/__init__.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""AgentLedger agent runtime v1.0 stable core."""
|
|
2
|
+
|
|
3
|
+
__version__ = "1.0.0"
|
|
4
|
+
|
|
5
|
+
from .adapters import FrameworkAdapter, PythonFunctionAdapter, python_agent
|
|
6
|
+
from .adapters_frameworks import AutoGenAdapter, CrewAIAdapter, LangChainRunnableAdapter, LlamaIndexAdapter, MethodFrameworkAdapter, OpenAIAgentsSDKAdapter, SemanticKernelAdapter
|
|
7
|
+
from .approval import ApprovalDecision, ApprovalRequired
|
|
8
|
+
from .adapters_langgraph import LangGraphCheckpointerAdapter, LangGraphNodeAdapter
|
|
9
|
+
from .adapters_mcp import InMemoryMCPContextServer, InMemoryMCPToolServer, MCPContextAdapter, MCPResourceDescriptor, MCPToolAdapter
|
|
10
|
+
from .backup import BackupCheck, BackupReadinessChecker, BackupReadinessReport
|
|
11
|
+
from .blobstore import LocalBlobStore
|
|
12
|
+
from .blobstore_s3 import S3BlobStore, S3BlobStoreConfig, S3DependencyMissing
|
|
13
|
+
from .conformance import BlobStoreConformanceRunner, ConformanceCheck, ConformanceReport, FrameworkAdapterConformanceRunner, MediaRuntimeConformanceRunner, StateStoreConformanceRunner, WorkerConformanceRunner
|
|
14
|
+
from .contract import CONTRACT_VERSION, contract_json, runtime_contract
|
|
15
|
+
from .diff import DiffReport, DivergenceReport, DivergenceReporter, EvidenceDiffer
|
|
16
|
+
from .context import AgentContext
|
|
17
|
+
from .cost import BudgetController, BudgetExceeded, BudgetLimits, CostAttributionReport, CostAttributionReporter
|
|
18
|
+
from .eval import EvidenceCheck, EvidenceCheckReport, EvidenceRegressionRunner
|
|
19
|
+
from .evidence import EvidenceExporter
|
|
20
|
+
from .failure import FailureAttributionReport, FailureAttributionReporter, FailureClassification, NonRetryableAgentError, RetryableAgentError, RetryPolicy
|
|
21
|
+
from .failure_injection import FailureInjectionCheck, FailureInjectionReport, FailureInjectionSuite
|
|
22
|
+
from .lint import BoundaryLintFinding, BoundaryLintReport, BoundaryLintRule, RuntimeBoundaryLinter, load_boundary_rules
|
|
23
|
+
from .media import ArtifactLineage, EventStreamCheckpoint, MediaArtifact, MediaMetadata, StreamChunkRef
|
|
24
|
+
from .media_tools import media_tool_specs, register_media_tool_conventions
|
|
25
|
+
from .policy import PolicyEngine, RolePolicy
|
|
26
|
+
from .protocol import BlobStoreProtocol, ModelProviderProtocol, StateStoreProtocol, ToolExecutorProtocol
|
|
27
|
+
from .repro import GoldenCase, GoldenCorpus
|
|
28
|
+
from .replay import ReplayEngine
|
|
29
|
+
from .runtime import Runtime, SimulatedCrash
|
|
30
|
+
from .retention import RetentionPlan, RetentionPlanner
|
|
31
|
+
from .review import AdversarialReviewReport, AdversarialReviewRunner, ReviewCheck
|
|
32
|
+
from .sandbox import BubblewrapSandboxExecutor, DisabledSandboxExecutor, DockerSandboxExecutor, E2BSandboxExecutor, FirecrackerSandboxExecutor, KubernetesSandboxExecutor, LocalSandboxExecutor, RemoteSandboxExecutor, SandboxConfig, SandboxExecutor, SandboxPolicy, SandboxResult, SandboxRouter, SandboxToolRule, SandboxUnavailable, create_sandbox_executor
|
|
33
|
+
from .simple import RunResult, SimpleAgent, agent, arun, run
|
|
34
|
+
from .scheduler import RecoverySummary, RuntimeScheduler
|
|
35
|
+
from .storage_schema import Migration, MigrationStatus, SQLiteMigrationRunner, ddl_for, latest_schema_version, migrations_for
|
|
36
|
+
from .storage_postgres import PostgresDependencyMissing, PostgresStore, PostgresStoreConfig
|
|
37
|
+
from .store import SQLiteStore
|
|
38
|
+
from .tools import ToolRegistry, ToolSpec, ToolValidationError, tool, validate_tool_schema
|
|
39
|
+
from .trace import OTLPResource, OTLPTraceExporter, TraceExporter, TraceSpan
|
|
40
|
+
from .timetravel import TimeTravelDebugger, TimeTravelFrame, TimeTravelReport
|
|
41
|
+
from .worker import LocalWorker, WorkerDeploymentPlan, WorkerRunSummary, WorkerService, WorkerServiceSummary, build_worker_deployment_plan
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"ApprovalDecision",
|
|
45
|
+
"ApprovalRequired",
|
|
46
|
+
"AdversarialReviewReport",
|
|
47
|
+
"AdversarialReviewRunner",
|
|
48
|
+
"ArtifactLineage",
|
|
49
|
+
"BackupCheck",
|
|
50
|
+
"BackupReadinessChecker",
|
|
51
|
+
"BackupReadinessReport",
|
|
52
|
+
"RunResult",
|
|
53
|
+
"SimpleAgent",
|
|
54
|
+
"RetentionPlan",
|
|
55
|
+
"RetentionPlanner",
|
|
56
|
+
"ReviewCheck",
|
|
57
|
+
"BubblewrapSandboxExecutor",
|
|
58
|
+
"DisabledSandboxExecutor",
|
|
59
|
+
"DockerSandboxExecutor",
|
|
60
|
+
"E2BSandboxExecutor",
|
|
61
|
+
"FirecrackerSandboxExecutor",
|
|
62
|
+
"KubernetesSandboxExecutor",
|
|
63
|
+
"RemoteSandboxExecutor",
|
|
64
|
+
"SandboxConfig",
|
|
65
|
+
"SandboxRouter",
|
|
66
|
+
"SandboxToolRule",
|
|
67
|
+
"SandboxUnavailable",
|
|
68
|
+
"create_sandbox_executor",
|
|
69
|
+
"SandboxExecutor",
|
|
70
|
+
"SandboxPolicy",
|
|
71
|
+
"SandboxResult",
|
|
72
|
+
"LocalSandboxExecutor",
|
|
73
|
+
"agent",
|
|
74
|
+
"arun",
|
|
75
|
+
"run",
|
|
76
|
+
"AgentContext",
|
|
77
|
+
"BlobStoreProtocol",
|
|
78
|
+
"BlobStoreConformanceRunner",
|
|
79
|
+
"BudgetController",
|
|
80
|
+
"BudgetExceeded",
|
|
81
|
+
"BudgetLimits",
|
|
82
|
+
"CostAttributionReport",
|
|
83
|
+
"CostAttributionReporter",
|
|
84
|
+
"BoundaryLintFinding",
|
|
85
|
+
"BoundaryLintReport",
|
|
86
|
+
"BoundaryLintRule",
|
|
87
|
+
"ConformanceCheck",
|
|
88
|
+
"ConformanceReport",
|
|
89
|
+
"CONTRACT_VERSION",
|
|
90
|
+
"__version__",
|
|
91
|
+
"EvidenceCheck",
|
|
92
|
+
"EvidenceCheckReport",
|
|
93
|
+
"EvidenceRegressionRunner",
|
|
94
|
+
"EventStreamCheckpoint",
|
|
95
|
+
"OTLPResource",
|
|
96
|
+
"OTLPTraceExporter",
|
|
97
|
+
"TraceSpan",
|
|
98
|
+
"TraceExporter",
|
|
99
|
+
"TimeTravelDebugger",
|
|
100
|
+
"TimeTravelFrame",
|
|
101
|
+
"TimeTravelReport",
|
|
102
|
+
"PostgresStoreConfig",
|
|
103
|
+
"PostgresStore",
|
|
104
|
+
"PostgresDependencyMissing",
|
|
105
|
+
"EvidenceDiffer",
|
|
106
|
+
"DiffReport",
|
|
107
|
+
"DivergenceReport",
|
|
108
|
+
"DivergenceReporter",
|
|
109
|
+
"EvidenceExporter",
|
|
110
|
+
"FailureInjectionCheck",
|
|
111
|
+
"FailureInjectionReport",
|
|
112
|
+
"FailureInjectionSuite",
|
|
113
|
+
"FailureClassification",
|
|
114
|
+
"FailureAttributionReport",
|
|
115
|
+
"FailureAttributionReporter",
|
|
116
|
+
"FrameworkAdapter",
|
|
117
|
+
"FrameworkAdapterConformanceRunner",
|
|
118
|
+
"AutoGenAdapter",
|
|
119
|
+
"CrewAIAdapter",
|
|
120
|
+
"GoldenCase",
|
|
121
|
+
"GoldenCorpus",
|
|
122
|
+
"LangGraphCheckpointerAdapter",
|
|
123
|
+
"LangGraphNodeAdapter",
|
|
124
|
+
"LangChainRunnableAdapter",
|
|
125
|
+
"LlamaIndexAdapter",
|
|
126
|
+
"LocalBlobStore",
|
|
127
|
+
"LocalWorker",
|
|
128
|
+
"MediaArtifact",
|
|
129
|
+
"MediaMetadata",
|
|
130
|
+
"media_tool_specs",
|
|
131
|
+
"MCPToolAdapter",
|
|
132
|
+
"MCPContextAdapter",
|
|
133
|
+
"MCPResourceDescriptor",
|
|
134
|
+
"InMemoryMCPToolServer",
|
|
135
|
+
"InMemoryMCPContextServer",
|
|
136
|
+
"MethodFrameworkAdapter",
|
|
137
|
+
"MediaRuntimeConformanceRunner",
|
|
138
|
+
"Migration",
|
|
139
|
+
"MigrationStatus",
|
|
140
|
+
"ModelProviderProtocol",
|
|
141
|
+
"NonRetryableAgentError",
|
|
142
|
+
"PolicyEngine",
|
|
143
|
+
"OpenAIAgentsSDKAdapter",
|
|
144
|
+
"PythonFunctionAdapter",
|
|
145
|
+
"RecoverySummary",
|
|
146
|
+
"ReplayEngine",
|
|
147
|
+
"RetryPolicy",
|
|
148
|
+
"RetryableAgentError",
|
|
149
|
+
"RolePolicy",
|
|
150
|
+
"Runtime",
|
|
151
|
+
"RuntimeBoundaryLinter",
|
|
152
|
+
"load_boundary_rules",
|
|
153
|
+
"RuntimeScheduler",
|
|
154
|
+
"SemanticKernelAdapter",
|
|
155
|
+
"S3BlobStore",
|
|
156
|
+
"S3BlobStoreConfig",
|
|
157
|
+
"S3DependencyMissing",
|
|
158
|
+
"SQLiteMigrationRunner",
|
|
159
|
+
"SQLiteStore",
|
|
160
|
+
"SimulatedCrash",
|
|
161
|
+
"StateStoreConformanceRunner",
|
|
162
|
+
"StateStoreProtocol",
|
|
163
|
+
"StreamChunkRef",
|
|
164
|
+
"ToolExecutorProtocol",
|
|
165
|
+
"ToolRegistry",
|
|
166
|
+
"ToolSpec",
|
|
167
|
+
"ToolValidationError",
|
|
168
|
+
"WorkerRunSummary",
|
|
169
|
+
"WorkerConformanceRunner",
|
|
170
|
+
"WorkerDeploymentPlan",
|
|
171
|
+
"WorkerService",
|
|
172
|
+
"WorkerServiceSummary",
|
|
173
|
+
"build_worker_deployment_plan",
|
|
174
|
+
"contract_json",
|
|
175
|
+
"ddl_for",
|
|
176
|
+
"latest_schema_version",
|
|
177
|
+
"migrations_for",
|
|
178
|
+
"python_agent",
|
|
179
|
+
"runtime_contract",
|
|
180
|
+
"register_media_tool_conventions",
|
|
181
|
+
"tool",
|
|
182
|
+
"validate_tool_schema",
|
|
183
|
+
]
|
agentledger/__main__.py
ADDED
agentledger/adapters.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from .context import AgentContext
|
|
8
|
+
|
|
9
|
+
AgentCallable = Callable[[AgentContext, dict[str, Any]], Any]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FrameworkAdapter(ABC):
|
|
13
|
+
"""Base contract for framework adapters.
|
|
14
|
+
|
|
15
|
+
Adapters map framework-specific concepts into AgentLedger's stable runtime
|
|
16
|
+
boundary. Core imports no LangGraph/CrewAI/AutoGen/etc dependencies.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name = "framework"
|
|
20
|
+
|
|
21
|
+
def map_run_spec(self, framework_run: Any) -> dict[str, Any]:
|
|
22
|
+
return {"adapter": self.name, "framework_run": repr(framework_run)}
|
|
23
|
+
|
|
24
|
+
def map_step(self, framework_step: Any) -> dict[str, Any]:
|
|
25
|
+
return {"adapter": self.name, "framework_step": repr(framework_step)}
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def as_agent(self) -> AgentCallable:
|
|
29
|
+
"""Return a callable compatible with Runtime.run_once."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PythonFunctionAdapter(FrameworkAdapter):
|
|
33
|
+
"""Adapter for a plain Python function or coroutine.
|
|
34
|
+
|
|
35
|
+
This proves the framework-agnostic SDK path before adding heavier optional
|
|
36
|
+
integrations.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
name = "python-function"
|
|
40
|
+
|
|
41
|
+
def __init__(self, func: AgentCallable, *, role: str = "Agent"):
|
|
42
|
+
self.func = func
|
|
43
|
+
self.role = role
|
|
44
|
+
|
|
45
|
+
def map_run_spec(self, framework_run: Any = None) -> dict[str, Any]:
|
|
46
|
+
return {"adapter": self.name, "role": self.role, "function": getattr(self.func, "__name__", repr(self.func))}
|
|
47
|
+
|
|
48
|
+
def as_agent(self) -> AgentCallable:
|
|
49
|
+
async def wrapped(ctx: AgentContext, state: dict[str, Any]) -> Any:
|
|
50
|
+
result = self.func(ctx, state)
|
|
51
|
+
if inspect.isawaitable(result):
|
|
52
|
+
return await result
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
return wrapped
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def python_agent(*, role: str = "Agent") -> Callable[[AgentCallable], PythonFunctionAdapter]:
|
|
59
|
+
def decorator(func: AgentCallable) -> PythonFunctionAdapter:
|
|
60
|
+
return PythonFunctionAdapter(func, role=role)
|
|
61
|
+
|
|
62
|
+
return decorator
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from .adapters import AgentCallable, FrameworkAdapter
|
|
7
|
+
from .context import AgentContext
|
|
8
|
+
|
|
9
|
+
InputMapper = Callable[[AgentContext, dict[str, Any]], Any]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def default_input_mapper(_ctx: AgentContext, state: dict[str, Any]) -> dict[str, Any]:
|
|
13
|
+
return dict(state)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MethodFrameworkAdapter(FrameworkAdapter):
|
|
17
|
+
"""Dependency-free facade for framework objects with conventional methods."""
|
|
18
|
+
|
|
19
|
+
name = "method-framework"
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
target: Any,
|
|
24
|
+
*,
|
|
25
|
+
role: str = "FrameworkAgent",
|
|
26
|
+
method_candidates: tuple[str, ...],
|
|
27
|
+
input_mapper: InputMapper | None = None,
|
|
28
|
+
output_key: str | None = "output",
|
|
29
|
+
):
|
|
30
|
+
self.target = target
|
|
31
|
+
self.role = role
|
|
32
|
+
self.method_candidates = method_candidates
|
|
33
|
+
self.input_mapper = input_mapper or default_input_mapper
|
|
34
|
+
self.output_key = output_key
|
|
35
|
+
|
|
36
|
+
def map_run_spec(self, framework_run: Any = None) -> dict[str, Any]:
|
|
37
|
+
return {
|
|
38
|
+
"adapter": self.name,
|
|
39
|
+
"role": self.role,
|
|
40
|
+
"target": type(self.target).__name__,
|
|
41
|
+
"methods": list(self.method_candidates),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def as_agent(self) -> AgentCallable:
|
|
45
|
+
async def wrapped(ctx: AgentContext, state: dict[str, Any]) -> Any:
|
|
46
|
+
payload = self.input_mapper(ctx, state)
|
|
47
|
+
result = await self._invoke(payload)
|
|
48
|
+
if self.output_key is not None:
|
|
49
|
+
ctx.write_state_patch(self.output_key, result)
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
return wrapped
|
|
53
|
+
|
|
54
|
+
async def _invoke(self, payload: Any) -> Any:
|
|
55
|
+
for name in self.method_candidates:
|
|
56
|
+
method = getattr(self.target, name, None)
|
|
57
|
+
if method is None:
|
|
58
|
+
continue
|
|
59
|
+
result = method(payload)
|
|
60
|
+
if inspect.isawaitable(result):
|
|
61
|
+
return await result
|
|
62
|
+
return result
|
|
63
|
+
if callable(self.target):
|
|
64
|
+
result = self.target(payload)
|
|
65
|
+
if inspect.isawaitable(result):
|
|
66
|
+
return await result
|
|
67
|
+
return result
|
|
68
|
+
raise AttributeError(f"{type(self.target).__name__} does not expose any of {self.method_candidates!r}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class LangChainRunnableAdapter(MethodFrameworkAdapter):
|
|
72
|
+
"""Wrap a LangChain-style Runnable without importing LangChain."""
|
|
73
|
+
|
|
74
|
+
name = "langchain-runnable"
|
|
75
|
+
|
|
76
|
+
def __init__(self, runnable: Any, *, role: str = "LangChainAgent", input_mapper: InputMapper | None = None, output_key: str | None = "langchain_output"):
|
|
77
|
+
super().__init__(runnable, role=role, method_candidates=("ainvoke", "invoke"), input_mapper=input_mapper, output_key=output_key)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CrewAIAdapter(MethodFrameworkAdapter):
|
|
81
|
+
"""Wrap a CrewAI-style Crew/Task object without importing CrewAI."""
|
|
82
|
+
|
|
83
|
+
name = "crewai"
|
|
84
|
+
|
|
85
|
+
def __init__(self, crew_or_task: Any, *, role: str = "CrewAIAgent", input_mapper: InputMapper | None = None, output_key: str | None = "crewai_output"):
|
|
86
|
+
super().__init__(crew_or_task, role=role, method_candidates=("akickoff", "kickoff", "arun", "run"), input_mapper=input_mapper, output_key=output_key)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AutoGenAdapter(MethodFrameworkAdapter):
|
|
90
|
+
"""Wrap an AutoGen-style agent object without importing AutoGen."""
|
|
91
|
+
|
|
92
|
+
name = "autogen"
|
|
93
|
+
|
|
94
|
+
def __init__(self, agent: Any, *, role: str = "AutoGenAgent", input_mapper: InputMapper | None = None, output_key: str | None = "autogen_output"):
|
|
95
|
+
super().__init__(
|
|
96
|
+
agent,
|
|
97
|
+
role=role,
|
|
98
|
+
method_candidates=("a_generate_reply", "generate_reply", "a_run", "run", "ainvoke", "invoke"),
|
|
99
|
+
input_mapper=input_mapper,
|
|
100
|
+
output_key=output_key,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class OpenAIAgentsSDKAdapter(MethodFrameworkAdapter):
|
|
105
|
+
"""Wrap an OpenAI Agents SDK-style runner without importing the SDK."""
|
|
106
|
+
|
|
107
|
+
name = "openai-agents-sdk"
|
|
108
|
+
|
|
109
|
+
def __init__(self, agent_or_runner: Any, *, role: str = "OpenAIAgent", input_mapper: InputMapper | None = None, output_key: str | None = "openai_agent_output"):
|
|
110
|
+
super().__init__(
|
|
111
|
+
agent_or_runner,
|
|
112
|
+
role=role,
|
|
113
|
+
method_candidates=("arun", "run", "ainvoke", "invoke"),
|
|
114
|
+
input_mapper=input_mapper,
|
|
115
|
+
output_key=output_key,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class LlamaIndexAdapter(MethodFrameworkAdapter):
|
|
120
|
+
"""Wrap a LlamaIndex-style query/chat/retriever object without importing LlamaIndex."""
|
|
121
|
+
|
|
122
|
+
name = "llamaindex"
|
|
123
|
+
|
|
124
|
+
def __init__(self, query_engine_or_agent: Any, *, role: str = "LlamaIndexAgent", input_mapper: InputMapper | None = None, output_key: str | None = "llamaindex_output"):
|
|
125
|
+
super().__init__(
|
|
126
|
+
query_engine_or_agent,
|
|
127
|
+
role=role,
|
|
128
|
+
method_candidates=("aquery", "query", "achat", "chat", "aretrieve", "retrieve", "ainvoke", "invoke"),
|
|
129
|
+
input_mapper=input_mapper,
|
|
130
|
+
output_key=output_key,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SemanticKernelAdapter(MethodFrameworkAdapter):
|
|
135
|
+
"""Wrap a Semantic Kernel-style kernel/function object without importing it."""
|
|
136
|
+
|
|
137
|
+
name = "semantic-kernel"
|
|
138
|
+
|
|
139
|
+
def __init__(self, kernel_or_function: Any, *, role: str = "SemanticKernelAgent", input_mapper: InputMapper | None = None, output_key: str | None = "semantic_kernel_output"):
|
|
140
|
+
super().__init__(
|
|
141
|
+
kernel_or_function,
|
|
142
|
+
role=role,
|
|
143
|
+
method_candidates=("ainvoke", "invoke", "invoke_prompt", "run_async", "run"),
|
|
144
|
+
input_mapper=input_mapper,
|
|
145
|
+
output_key=output_key,
|
|
146
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .adapters import FrameworkAdapter, PythonFunctionAdapter
|
|
6
|
+
from .ids import new_id
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LangGraphCheckpointerAdapter:
|
|
10
|
+
"""Dependency-free LangGraph-style checkpointer adapter.
|
|
11
|
+
|
|
12
|
+
Runtime core does not import LangGraph. This adapter exposes the common
|
|
13
|
+
checkpointer shape (`put`, `get`, `get_tuple`, `list`, `put_writes`) using
|
|
14
|
+
plain dictionaries so optional packages can wrap it with LangGraph's exact
|
|
15
|
+
classes without changing AgentLedger state semantics.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
name = "langgraph-checkpointer"
|
|
19
|
+
|
|
20
|
+
def __init__(self, runtime: Any):
|
|
21
|
+
self.runtime = runtime
|
|
22
|
+
|
|
23
|
+
def config_for_run(self, run_id: str, *, thread_id: str | None = None, checkpoint_ns: str = "") -> dict[str, Any]:
|
|
24
|
+
return {"configurable": {"agentledger_run_id": run_id, "thread_id": thread_id or run_id, "checkpoint_ns": checkpoint_ns}}
|
|
25
|
+
|
|
26
|
+
def checkpoint_from_run(self, run_id: str) -> dict[str, Any]:
|
|
27
|
+
state, state_version, session_id = self.runtime.store.load_state(run_id)
|
|
28
|
+
return {"run_id": run_id, "session_id": session_id, "state_version": state_version, "state": state}
|
|
29
|
+
|
|
30
|
+
def persist_checkpoint(self, run_id: str, checkpoint: dict[str, Any], *, reason: str = "langgraph checkpoint") -> int:
|
|
31
|
+
return self.runtime.store.apply_system_state_patch(run_id=run_id, patch={"langgraph_checkpoint": checkpoint}, reason=reason)
|
|
32
|
+
|
|
33
|
+
def put(
|
|
34
|
+
self,
|
|
35
|
+
config: dict[str, Any],
|
|
36
|
+
checkpoint: dict[str, Any],
|
|
37
|
+
metadata: dict[str, Any] | None = None,
|
|
38
|
+
new_versions: dict[str, Any] | None = None,
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
run_id = self._run_id_from_config(config)
|
|
41
|
+
checkpoint_id = str(checkpoint.get("id") or checkpoint.get("checkpoint_id") or new_id("lgckpt"))
|
|
42
|
+
next_config = self._with_checkpoint_id(config, checkpoint_id)
|
|
43
|
+
record = {
|
|
44
|
+
"checkpoint": {**checkpoint, "id": checkpoint_id},
|
|
45
|
+
"metadata": metadata or {},
|
|
46
|
+
"new_versions": new_versions or {},
|
|
47
|
+
"config": next_config,
|
|
48
|
+
}
|
|
49
|
+
self.runtime.store.apply_system_state_patch(
|
|
50
|
+
run_id=run_id,
|
|
51
|
+
patch={"langgraph_checkpoint": record, "langgraph_pending_writes": []},
|
|
52
|
+
reason="langgraph checkpoint put",
|
|
53
|
+
)
|
|
54
|
+
return next_config
|
|
55
|
+
|
|
56
|
+
async def aput(
|
|
57
|
+
self,
|
|
58
|
+
config: dict[str, Any],
|
|
59
|
+
checkpoint: dict[str, Any],
|
|
60
|
+
metadata: dict[str, Any] | None = None,
|
|
61
|
+
new_versions: dict[str, Any] | None = None,
|
|
62
|
+
) -> dict[str, Any]:
|
|
63
|
+
return self.put(config, checkpoint, metadata, new_versions)
|
|
64
|
+
|
|
65
|
+
def get_tuple(self, config: dict[str, Any]) -> dict[str, Any] | None:
|
|
66
|
+
run_id = self._run_id_from_config(config)
|
|
67
|
+
state = self.runtime.store.final_state(run_id)
|
|
68
|
+
record = state.get("langgraph_checkpoint")
|
|
69
|
+
if record is None:
|
|
70
|
+
return None
|
|
71
|
+
if "checkpoint" not in record:
|
|
72
|
+
record = {"checkpoint": record, "metadata": {}, "config": config}
|
|
73
|
+
return {
|
|
74
|
+
"config": record.get("config", config),
|
|
75
|
+
"checkpoint": record.get("checkpoint"),
|
|
76
|
+
"metadata": record.get("metadata", {}),
|
|
77
|
+
"parent_config": record.get("parent_config"),
|
|
78
|
+
"pending_writes": state.get("langgraph_pending_writes", []),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async def aget_tuple(self, config: dict[str, Any]) -> dict[str, Any] | None:
|
|
82
|
+
return self.get_tuple(config)
|
|
83
|
+
|
|
84
|
+
def get(self, config: dict[str, Any]) -> dict[str, Any] | None:
|
|
85
|
+
item = self.get_tuple(config)
|
|
86
|
+
return item["checkpoint"] if item is not None else None
|
|
87
|
+
|
|
88
|
+
async def aget(self, config: dict[str, Any]) -> dict[str, Any] | None:
|
|
89
|
+
return self.get(config)
|
|
90
|
+
|
|
91
|
+
def list(self, config: dict[str, Any] | None = None, **_kwargs: Any) -> list[dict[str, Any]]:
|
|
92
|
+
if config is None:
|
|
93
|
+
return []
|
|
94
|
+
item = self.get_tuple(config)
|
|
95
|
+
return [item] if item is not None else []
|
|
96
|
+
|
|
97
|
+
async def alist(self, config: dict[str, Any] | None = None, **kwargs: Any) -> list[dict[str, Any]]:
|
|
98
|
+
return self.list(config, **kwargs)
|
|
99
|
+
|
|
100
|
+
def put_writes(self, config: dict[str, Any], writes: list[Any], task_id: str, task_path: str = "") -> None:
|
|
101
|
+
run_id = self._run_id_from_config(config)
|
|
102
|
+
state = self.runtime.store.final_state(run_id)
|
|
103
|
+
pending = list(state.get("langgraph_pending_writes", []))
|
|
104
|
+
pending.append({"task_id": task_id, "task_path": task_path, "writes": writes, "config": config})
|
|
105
|
+
self.runtime.store.apply_system_state_patch(
|
|
106
|
+
run_id=run_id,
|
|
107
|
+
patch={"langgraph_pending_writes": pending},
|
|
108
|
+
reason="langgraph pending writes",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
async def aput_writes(self, config: dict[str, Any], writes: list[Any], task_id: str, task_path: str = "") -> None:
|
|
112
|
+
self.put_writes(config, writes, task_id, task_path)
|
|
113
|
+
|
|
114
|
+
def _run_id_from_config(self, config: dict[str, Any]) -> str:
|
|
115
|
+
configurable = config.get("configurable", {}) if isinstance(config, dict) else {}
|
|
116
|
+
run_id = configurable.get("agentledger_run_id") or configurable.get("run_id")
|
|
117
|
+
if not run_id:
|
|
118
|
+
raise ValueError("LangGraph config must include configurable.agentledger_run_id or configurable.run_id")
|
|
119
|
+
return str(run_id)
|
|
120
|
+
|
|
121
|
+
def _with_checkpoint_id(self, config: dict[str, Any], checkpoint_id: str) -> dict[str, Any]:
|
|
122
|
+
configurable = dict(config.get("configurable", {}))
|
|
123
|
+
configurable["checkpoint_id"] = checkpoint_id
|
|
124
|
+
return {**config, "configurable": configurable}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class LangGraphNodeAdapter(FrameworkAdapter):
|
|
128
|
+
"""Wrap a callable node as a Runtime.run_once-compatible agent."""
|
|
129
|
+
|
|
130
|
+
name = "langgraph-node"
|
|
131
|
+
|
|
132
|
+
def __init__(self, node: Any, *, role: str = "LangGraphAgent"):
|
|
133
|
+
self.node = node
|
|
134
|
+
self.role = role
|
|
135
|
+
|
|
136
|
+
def map_run_spec(self, framework_run: Any = None) -> dict[str, Any]:
|
|
137
|
+
return {"adapter": self.name, "role": self.role, "node": getattr(self.node, "__name__", repr(self.node))}
|
|
138
|
+
|
|
139
|
+
def as_agent(self):
|
|
140
|
+
return PythonFunctionAdapter(self.node, role=self.role).as_agent()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from .tools import ToolRegistry, ToolSpec
|
|
8
|
+
|
|
9
|
+
MCPCall = Callable[[str, dict[str, Any]], Any]
|
|
10
|
+
MCPResourceRead = Callable[[str], Any]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class MCPResourceDescriptor:
|
|
15
|
+
uri: str
|
|
16
|
+
name: str
|
|
17
|
+
mime_type: str = "application/json"
|
|
18
|
+
|
|
19
|
+
def to_dict(self) -> dict[str, Any]:
|
|
20
|
+
return {"uri": self.uri, "name": self.name, "mimeType": self.mime_type}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class InMemoryMCPToolServer:
|
|
24
|
+
"""Dependency-free MCP-style tool server fixture for examples/tests."""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self._tools: dict[str, tuple[dict[str, Any], MCPCall]] = {}
|
|
28
|
+
|
|
29
|
+
def add_tool(self, descriptor: dict[str, Any], handler: MCPCall) -> None:
|
|
30
|
+
self._tools[descriptor["name"]] = (descriptor, handler)
|
|
31
|
+
|
|
32
|
+
def list_tools(self) -> list[dict[str, Any]]:
|
|
33
|
+
return [self._tools[name][0] for name in sorted(self._tools)]
|
|
34
|
+
|
|
35
|
+
def call_tool(self, name: str, args: dict[str, Any]) -> Any:
|
|
36
|
+
try:
|
|
37
|
+
_descriptor, handler = self._tools[name]
|
|
38
|
+
except KeyError as exc:
|
|
39
|
+
raise KeyError(f"MCP tool not found: {name}") from exc
|
|
40
|
+
return handler(name, args)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InMemoryMCPContextServer:
|
|
44
|
+
"""Dependency-free MCP-style context/resource server fixture."""
|
|
45
|
+
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
self._resources: dict[str, tuple[MCPResourceDescriptor, MCPResourceRead]] = {}
|
|
48
|
+
|
|
49
|
+
def add_resource(self, *, uri: str, name: str, reader: MCPResourceRead, mime_type: str = "application/json") -> None:
|
|
50
|
+
self._resources[uri] = (MCPResourceDescriptor(uri=uri, name=name, mime_type=mime_type), reader)
|
|
51
|
+
|
|
52
|
+
def list_resources(self) -> list[dict[str, Any]]:
|
|
53
|
+
return [self._resources[uri][0].to_dict() for uri in sorted(self._resources)]
|
|
54
|
+
|
|
55
|
+
def read_resource(self, uri: str) -> Any:
|
|
56
|
+
try:
|
|
57
|
+
descriptor, reader = self._resources[uri]
|
|
58
|
+
except KeyError as exc:
|
|
59
|
+
raise KeyError(f"MCP resource not found: {uri}") from exc
|
|
60
|
+
return {"resource": descriptor.to_dict(), "content": reader(uri)}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class MCPToolAdapter:
|
|
64
|
+
"""Map MCP-style tool descriptors into AgentLedger ToolSpec objects.
|
|
65
|
+
|
|
66
|
+
The adapter is dependency-free: callers provide a `client_call` function that
|
|
67
|
+
knows how to invoke an MCP client. Runtime core still owns policy, ledger,
|
|
68
|
+
audit, budget, replay, and shadow semantics through ToolGateway.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
name = "mcp-tool"
|
|
72
|
+
|
|
73
|
+
def __init__(self, client_call: MCPCall):
|
|
74
|
+
self.client_call = client_call
|
|
75
|
+
|
|
76
|
+
def tool_spec_from_descriptor(self, descriptor: dict[str, Any]) -> ToolSpec:
|
|
77
|
+
tool_name = descriptor["name"]
|
|
78
|
+
annotations = descriptor.get("annotations", {}) or {}
|
|
79
|
+
input_schema = descriptor.get("inputSchema") or descriptor.get("input_schema") or {}
|
|
80
|
+
side_effect = annotations.get("side_effect", "none")
|
|
81
|
+
risk_level = annotations.get("risk_level", "low")
|
|
82
|
+
idempotency_required = bool(annotations.get("idempotency_required", side_effect != "none"))
|
|
83
|
+
|
|
84
|
+
async def call(args: dict[str, Any]) -> Any:
|
|
85
|
+
result = self.client_call(tool_name, args)
|
|
86
|
+
if inspect.isawaitable(result):
|
|
87
|
+
return await result
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
return ToolSpec(
|
|
91
|
+
name=tool_name,
|
|
92
|
+
func=call,
|
|
93
|
+
version=str(descriptor.get("version", "v1")),
|
|
94
|
+
input_schema=input_schema,
|
|
95
|
+
output_schema=descriptor.get("outputSchema") or descriptor.get("output_schema") or {},
|
|
96
|
+
side_effect=side_effect,
|
|
97
|
+
risk_level=risk_level,
|
|
98
|
+
idempotency_required=idempotency_required,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def register(self, registry: ToolRegistry, descriptor: dict[str, Any]) -> ToolSpec:
|
|
102
|
+
spec = self.tool_spec_from_descriptor(descriptor)
|
|
103
|
+
registry.register(spec)
|
|
104
|
+
return spec
|
|
105
|
+
|
|
106
|
+
def register_all(self, registry: ToolRegistry, descriptors: list[dict[str, Any]]) -> list[ToolSpec]:
|
|
107
|
+
return [self.register(registry, descriptor) for descriptor in descriptors]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class MCPContextAdapter:
|
|
111
|
+
"""Expose MCP-style context/resource reads through ToolGateway."""
|
|
112
|
+
|
|
113
|
+
name = "mcp-context"
|
|
114
|
+
|
|
115
|
+
def __init__(self, resource_read: Callable[[str], Any]):
|
|
116
|
+
self.resource_read = resource_read
|
|
117
|
+
|
|
118
|
+
def read_tool_spec(self, *, name: str = "mcp.context.read", risk_level: str = "low") -> ToolSpec:
|
|
119
|
+
async def call(args: dict[str, Any]) -> Any:
|
|
120
|
+
uri = args["uri"]
|
|
121
|
+
result = self.resource_read(uri)
|
|
122
|
+
if inspect.isawaitable(result):
|
|
123
|
+
return await result
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
return ToolSpec(
|
|
127
|
+
name=name,
|
|
128
|
+
func=call,
|
|
129
|
+
version="v1",
|
|
130
|
+
description="Read an MCP-style context resource by URI.",
|
|
131
|
+
input_schema={
|
|
132
|
+
"type": "object",
|
|
133
|
+
"required": ["uri"],
|
|
134
|
+
"properties": {"uri": {"type": "string", "minLength": 1}},
|
|
135
|
+
"additionalProperties": False,
|
|
136
|
+
},
|
|
137
|
+
output_schema={"type": "object"},
|
|
138
|
+
side_effect="none",
|
|
139
|
+
risk_level=risk_level,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def register_read_tool(self, registry: ToolRegistry, *, name: str = "mcp.context.read", risk_level: str = "low") -> ToolSpec:
|
|
143
|
+
spec = self.read_tool_spec(name=name, risk_level=risk_level)
|
|
144
|
+
registry.register(spec)
|
|
145
|
+
return spec
|