agent_os_kernel 3.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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
atr/composition.py
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Tool composition for ATR.
|
|
5
|
+
|
|
6
|
+
Provides mechanisms for chaining and composing tools declaratively.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import inspect
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
Callable,
|
|
19
|
+
Dict,
|
|
20
|
+
Generic,
|
|
21
|
+
List,
|
|
22
|
+
Optional,
|
|
23
|
+
TypeVar,
|
|
24
|
+
Union,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
T = TypeVar("T")
|
|
28
|
+
U = TypeVar("U")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CompositionError(Exception):
|
|
32
|
+
"""Error during tool composition."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ExecutionMode(str, Enum):
|
|
38
|
+
"""How to execute composed tools."""
|
|
39
|
+
|
|
40
|
+
SEQUENTIAL = "sequential" # One after another
|
|
41
|
+
PARALLEL = "parallel" # All at once
|
|
42
|
+
CONDITIONAL = "conditional" # Based on condition
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ToolResult(Generic[T]):
|
|
47
|
+
"""Result from a composed tool execution.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
value: The result value.
|
|
51
|
+
success: Whether execution succeeded.
|
|
52
|
+
error: Error if execution failed.
|
|
53
|
+
tool_name: Name of the tool that produced this result.
|
|
54
|
+
metadata: Additional execution metadata.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
value: Optional[T] = None
|
|
58
|
+
success: bool = True
|
|
59
|
+
error: Optional[Exception] = None
|
|
60
|
+
tool_name: str = ""
|
|
61
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def ok(cls, value: T, tool_name: str = "", **metadata) -> "ToolResult[T]":
|
|
65
|
+
"""Create a successful result."""
|
|
66
|
+
return cls(value=value, success=True, tool_name=tool_name, metadata=metadata)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def fail(cls, error: Exception, tool_name: str = "", **metadata) -> "ToolResult[T]":
|
|
70
|
+
"""Create a failed result."""
|
|
71
|
+
return cls(error=error, success=False, tool_name=tool_name, metadata=metadata)
|
|
72
|
+
|
|
73
|
+
def map(self, func: Callable[[T], U]) -> "ToolResult[U]":
|
|
74
|
+
"""Transform the result value if successful."""
|
|
75
|
+
if not self.success:
|
|
76
|
+
return ToolResult(
|
|
77
|
+
error=self.error, success=False, tool_name=self.tool_name, metadata=self.metadata
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
new_value = func(self.value)
|
|
81
|
+
return ToolResult.ok(new_value, self.tool_name, **self.metadata)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return ToolResult.fail(e, self.tool_name, **self.metadata)
|
|
84
|
+
|
|
85
|
+
def flat_map(self, func: Callable[[T], "ToolResult[U]"]) -> "ToolResult[U]":
|
|
86
|
+
"""Chain with another operation that returns a ToolResult."""
|
|
87
|
+
if not self.success:
|
|
88
|
+
return ToolResult(
|
|
89
|
+
error=self.error, success=False, tool_name=self.tool_name, metadata=self.metadata
|
|
90
|
+
)
|
|
91
|
+
try:
|
|
92
|
+
return func(self.value)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
return ToolResult.fail(e, self.tool_name, **self.metadata)
|
|
95
|
+
|
|
96
|
+
def unwrap(self) -> T:
|
|
97
|
+
"""Get the value or raise if failed."""
|
|
98
|
+
if not self.success:
|
|
99
|
+
raise self.error or CompositionError("Result is not successful")
|
|
100
|
+
return self.value
|
|
101
|
+
|
|
102
|
+
def unwrap_or(self, default: T) -> T:
|
|
103
|
+
"""Get the value or return default if failed."""
|
|
104
|
+
if not self.success:
|
|
105
|
+
return default
|
|
106
|
+
return self.value
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ToolStep(ABC, Generic[T]):
|
|
110
|
+
"""Abstract base for a step in a tool composition."""
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def execute(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
114
|
+
"""Execute this step synchronously."""
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
async def execute_async(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
119
|
+
"""Execute this step asynchronously."""
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def name(self) -> str:
|
|
125
|
+
"""Name of this step."""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class FunctionStep(ToolStep[T]):
|
|
130
|
+
"""A step that executes a function.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> step = FunctionStep(my_function, name="process_data")
|
|
134
|
+
>>> result = step.execute(input_data, {})
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
func: Callable[..., T],
|
|
140
|
+
name: Optional[str] = None,
|
|
141
|
+
input_mapping: Optional[Callable[[Any], Dict[str, Any]]] = None,
|
|
142
|
+
):
|
|
143
|
+
"""Initialize function step.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
func: The function to execute.
|
|
147
|
+
name: Name for this step (defaults to function name).
|
|
148
|
+
input_mapping: Optional function to transform input to kwargs.
|
|
149
|
+
"""
|
|
150
|
+
self._func = func
|
|
151
|
+
self._name = name or func.__name__
|
|
152
|
+
self._input_mapping = input_mapping
|
|
153
|
+
self._is_async = asyncio.iscoroutinefunction(func)
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def name(self) -> str:
|
|
157
|
+
return self._name
|
|
158
|
+
|
|
159
|
+
def execute(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
160
|
+
"""Execute the function synchronously."""
|
|
161
|
+
try:
|
|
162
|
+
kwargs = self._prepare_kwargs(input_data, context)
|
|
163
|
+
|
|
164
|
+
if self._is_async:
|
|
165
|
+
# Run async function in event loop
|
|
166
|
+
loop = asyncio.new_event_loop()
|
|
167
|
+
try:
|
|
168
|
+
result = loop.run_until_complete(self._func(**kwargs))
|
|
169
|
+
finally:
|
|
170
|
+
loop.close()
|
|
171
|
+
else:
|
|
172
|
+
result = self._func(**kwargs)
|
|
173
|
+
|
|
174
|
+
return ToolResult.ok(result, self._name)
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return ToolResult.fail(e, self._name)
|
|
178
|
+
|
|
179
|
+
async def execute_async(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
180
|
+
"""Execute the function asynchronously."""
|
|
181
|
+
try:
|
|
182
|
+
kwargs = self._prepare_kwargs(input_data, context)
|
|
183
|
+
|
|
184
|
+
if self._is_async:
|
|
185
|
+
result = await self._func(**kwargs)
|
|
186
|
+
else:
|
|
187
|
+
# Run sync function in executor
|
|
188
|
+
loop = asyncio.get_event_loop()
|
|
189
|
+
result = await loop.run_in_executor(None, lambda: self._func(**kwargs))
|
|
190
|
+
|
|
191
|
+
return ToolResult.ok(result, self._name)
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
return ToolResult.fail(e, self._name)
|
|
195
|
+
|
|
196
|
+
def _prepare_kwargs(self, input_data: Any, context: Dict[str, Any]) -> Dict[str, Any]: # noqa: ARG002
|
|
197
|
+
"""Prepare kwargs for function call."""
|
|
198
|
+
if self._input_mapping:
|
|
199
|
+
return self._input_mapping(input_data)
|
|
200
|
+
elif isinstance(input_data, dict):
|
|
201
|
+
return input_data
|
|
202
|
+
else:
|
|
203
|
+
# Try to match to first parameter
|
|
204
|
+
sig = inspect.signature(self._func)
|
|
205
|
+
params = list(sig.parameters.keys())
|
|
206
|
+
if params:
|
|
207
|
+
return {params[0]: input_data}
|
|
208
|
+
return {}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class Pipeline(ToolStep[T]):
|
|
212
|
+
"""A sequential composition of tool steps.
|
|
213
|
+
|
|
214
|
+
Each step's output becomes the next step's input.
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
>>> pipeline = Pipeline([
|
|
218
|
+
... FunctionStep(parse_input),
|
|
219
|
+
... FunctionStep(process_data),
|
|
220
|
+
... FunctionStep(format_output),
|
|
221
|
+
... ], name="data_pipeline")
|
|
222
|
+
>>> result = pipeline.execute(raw_input, {})
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __init__(
|
|
226
|
+
self,
|
|
227
|
+
steps: List[ToolStep],
|
|
228
|
+
name: str = "pipeline",
|
|
229
|
+
stop_on_error: bool = True,
|
|
230
|
+
):
|
|
231
|
+
"""Initialize pipeline.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
steps: List of steps to execute in order.
|
|
235
|
+
name: Name for this pipeline.
|
|
236
|
+
stop_on_error: Whether to stop if a step fails.
|
|
237
|
+
"""
|
|
238
|
+
self._steps = steps
|
|
239
|
+
self._name = name
|
|
240
|
+
self._stop_on_error = stop_on_error
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def name(self) -> str:
|
|
244
|
+
return self._name
|
|
245
|
+
|
|
246
|
+
def execute(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
247
|
+
"""Execute all steps sequentially."""
|
|
248
|
+
current_data = input_data
|
|
249
|
+
results: List[ToolResult] = []
|
|
250
|
+
|
|
251
|
+
for step in self._steps:
|
|
252
|
+
result = step.execute(current_data, context)
|
|
253
|
+
results.append(result)
|
|
254
|
+
|
|
255
|
+
if not result.success:
|
|
256
|
+
if self._stop_on_error:
|
|
257
|
+
return ToolResult.fail(
|
|
258
|
+
result.error or CompositionError(f"Step '{step.name}' failed"),
|
|
259
|
+
self._name,
|
|
260
|
+
step_results=results,
|
|
261
|
+
)
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
current_data = result.value
|
|
265
|
+
|
|
266
|
+
return ToolResult.ok(current_data, self._name, step_results=results)
|
|
267
|
+
|
|
268
|
+
async def execute_async(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
269
|
+
"""Execute all steps sequentially (async)."""
|
|
270
|
+
current_data = input_data
|
|
271
|
+
results: List[ToolResult] = []
|
|
272
|
+
|
|
273
|
+
for step in self._steps:
|
|
274
|
+
result = await step.execute_async(current_data, context)
|
|
275
|
+
results.append(result)
|
|
276
|
+
|
|
277
|
+
if not result.success:
|
|
278
|
+
if self._stop_on_error:
|
|
279
|
+
return ToolResult.fail(
|
|
280
|
+
result.error or CompositionError(f"Step '{step.name}' failed"),
|
|
281
|
+
self._name,
|
|
282
|
+
step_results=results,
|
|
283
|
+
)
|
|
284
|
+
continue
|
|
285
|
+
|
|
286
|
+
current_data = result.value
|
|
287
|
+
|
|
288
|
+
return ToolResult.ok(current_data, self._name, step_results=results)
|
|
289
|
+
|
|
290
|
+
def then(self, step: ToolStep) -> "Pipeline":
|
|
291
|
+
"""Add a step to the pipeline."""
|
|
292
|
+
return Pipeline(
|
|
293
|
+
steps=self._steps + [step], name=self._name, stop_on_error=self._stop_on_error
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class ParallelExecution(ToolStep[List[T]]):
|
|
298
|
+
"""Execute multiple steps in parallel.
|
|
299
|
+
|
|
300
|
+
Example:
|
|
301
|
+
>>> parallel = ParallelExecution([
|
|
302
|
+
... FunctionStep(fetch_from_api_a),
|
|
303
|
+
... FunctionStep(fetch_from_api_b),
|
|
304
|
+
... ], name="parallel_fetch")
|
|
305
|
+
>>> result = parallel.execute(query, {})
|
|
306
|
+
>>> results = result.value # List of results from both
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
def __init__(
|
|
310
|
+
self,
|
|
311
|
+
steps: List[ToolStep],
|
|
312
|
+
name: str = "parallel",
|
|
313
|
+
collect_all: bool = True,
|
|
314
|
+
):
|
|
315
|
+
"""Initialize parallel execution.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
steps: Steps to execute in parallel.
|
|
319
|
+
name: Name for this composition.
|
|
320
|
+
collect_all: If True, wait for all. If False, return first success.
|
|
321
|
+
"""
|
|
322
|
+
self._steps = steps
|
|
323
|
+
self._name = name
|
|
324
|
+
self._collect_all = collect_all
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def name(self) -> str:
|
|
328
|
+
return self._name
|
|
329
|
+
|
|
330
|
+
def execute(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[List[T]]:
|
|
331
|
+
"""Execute all steps in parallel using threads."""
|
|
332
|
+
import concurrent.futures
|
|
333
|
+
|
|
334
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
335
|
+
futures = {
|
|
336
|
+
executor.submit(step.execute, input_data, context): step for step in self._steps
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
results: List[ToolResult] = []
|
|
340
|
+
|
|
341
|
+
for future in concurrent.futures.as_completed(futures):
|
|
342
|
+
result = future.result()
|
|
343
|
+
results.append(result)
|
|
344
|
+
|
|
345
|
+
if not self._collect_all and result.success:
|
|
346
|
+
# Return first success
|
|
347
|
+
return ToolResult.ok([result.value], self._name, step_results=results)
|
|
348
|
+
|
|
349
|
+
values = [r.value for r in results if r.success]
|
|
350
|
+
errors = [r for r in results if not r.success]
|
|
351
|
+
|
|
352
|
+
if errors and not values:
|
|
353
|
+
return ToolResult.fail(
|
|
354
|
+
errors[0].error or CompositionError("All parallel steps failed"),
|
|
355
|
+
self._name,
|
|
356
|
+
step_results=results,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
return ToolResult.ok(values, self._name, step_results=results)
|
|
360
|
+
|
|
361
|
+
async def execute_async(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[List[T]]:
|
|
362
|
+
"""Execute all steps in parallel using asyncio."""
|
|
363
|
+
tasks = [step.execute_async(input_data, context) for step in self._steps]
|
|
364
|
+
|
|
365
|
+
if self._collect_all:
|
|
366
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
367
|
+
processed_results: List[ToolResult] = []
|
|
368
|
+
|
|
369
|
+
for i, result in enumerate(results):
|
|
370
|
+
if isinstance(result, Exception):
|
|
371
|
+
processed_results.append(ToolResult.fail(result, self._steps[i].name))
|
|
372
|
+
else:
|
|
373
|
+
processed_results.append(result)
|
|
374
|
+
|
|
375
|
+
values = [r.value for r in processed_results if r.success]
|
|
376
|
+
errors = [r for r in processed_results if not r.success]
|
|
377
|
+
|
|
378
|
+
if errors and not values:
|
|
379
|
+
return ToolResult.fail(
|
|
380
|
+
errors[0].error or CompositionError("All parallel steps failed"),
|
|
381
|
+
self._name,
|
|
382
|
+
step_results=processed_results,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
return ToolResult.ok(values, self._name, step_results=processed_results)
|
|
386
|
+
else:
|
|
387
|
+
# Return first success
|
|
388
|
+
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
|
389
|
+
|
|
390
|
+
for task in pending:
|
|
391
|
+
task.cancel()
|
|
392
|
+
|
|
393
|
+
for task in done:
|
|
394
|
+
result = task.result()
|
|
395
|
+
if result.success:
|
|
396
|
+
return ToolResult.ok([result.value], self._name)
|
|
397
|
+
|
|
398
|
+
# All completed tasks failed
|
|
399
|
+
results = [task.result() for task in done]
|
|
400
|
+
return ToolResult.fail(
|
|
401
|
+
results[0].error if results else CompositionError("No results"),
|
|
402
|
+
self._name,
|
|
403
|
+
step_results=results,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class ConditionalStep(ToolStep[T]):
|
|
408
|
+
"""Execute different steps based on a condition.
|
|
409
|
+
|
|
410
|
+
Example:
|
|
411
|
+
>>> conditional = ConditionalStep(
|
|
412
|
+
... condition=lambda x, ctx: x.get('type') == 'pdf',
|
|
413
|
+
... if_true=FunctionStep(parse_pdf),
|
|
414
|
+
... if_false=FunctionStep(parse_text),
|
|
415
|
+
... )
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
def __init__(
|
|
419
|
+
self,
|
|
420
|
+
condition: Callable[[Any, Dict[str, Any]], bool],
|
|
421
|
+
if_true: ToolStep[T],
|
|
422
|
+
if_false: Optional[ToolStep[T]] = None,
|
|
423
|
+
name: str = "conditional",
|
|
424
|
+
):
|
|
425
|
+
"""Initialize conditional step.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
condition: Function that returns True or False.
|
|
429
|
+
if_true: Step to execute if condition is True.
|
|
430
|
+
if_false: Step to execute if condition is False (optional).
|
|
431
|
+
name: Name for this step.
|
|
432
|
+
"""
|
|
433
|
+
self._condition = condition
|
|
434
|
+
self._if_true = if_true
|
|
435
|
+
self._if_false = if_false
|
|
436
|
+
self._name = name
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def name(self) -> str:
|
|
440
|
+
return self._name
|
|
441
|
+
|
|
442
|
+
def execute(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
443
|
+
"""Execute based on condition."""
|
|
444
|
+
try:
|
|
445
|
+
condition_result = self._condition(input_data, context)
|
|
446
|
+
except Exception as e:
|
|
447
|
+
return ToolResult.fail(e, self._name)
|
|
448
|
+
|
|
449
|
+
if condition_result:
|
|
450
|
+
return self._if_true.execute(input_data, context)
|
|
451
|
+
elif self._if_false:
|
|
452
|
+
return self._if_false.execute(input_data, context)
|
|
453
|
+
else:
|
|
454
|
+
return ToolResult.ok(input_data, self._name)
|
|
455
|
+
|
|
456
|
+
async def execute_async(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
457
|
+
"""Execute based on condition (async)."""
|
|
458
|
+
try:
|
|
459
|
+
if asyncio.iscoroutinefunction(self._condition):
|
|
460
|
+
condition_result = await self._condition(input_data, context)
|
|
461
|
+
else:
|
|
462
|
+
condition_result = self._condition(input_data, context)
|
|
463
|
+
except Exception as e:
|
|
464
|
+
return ToolResult.fail(e, self._name)
|
|
465
|
+
|
|
466
|
+
if condition_result:
|
|
467
|
+
return await self._if_true.execute_async(input_data, context)
|
|
468
|
+
elif self._if_false:
|
|
469
|
+
return await self._if_false.execute_async(input_data, context)
|
|
470
|
+
else:
|
|
471
|
+
return ToolResult.ok(input_data, self._name)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class FallbackStep(ToolStep[T]):
|
|
475
|
+
"""Try multiple steps until one succeeds.
|
|
476
|
+
|
|
477
|
+
Example:
|
|
478
|
+
>>> fallback = FallbackStep([
|
|
479
|
+
... FunctionStep(primary_api),
|
|
480
|
+
... FunctionStep(backup_api),
|
|
481
|
+
... FunctionStep(cache_lookup),
|
|
482
|
+
... ])
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
def __init__(
|
|
486
|
+
self,
|
|
487
|
+
steps: List[ToolStep[T]],
|
|
488
|
+
name: str = "fallback",
|
|
489
|
+
):
|
|
490
|
+
"""Initialize fallback step.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
steps: Steps to try in order until one succeeds.
|
|
494
|
+
name: Name for this step.
|
|
495
|
+
"""
|
|
496
|
+
self._steps = steps
|
|
497
|
+
self._name = name
|
|
498
|
+
|
|
499
|
+
@property
|
|
500
|
+
def name(self) -> str:
|
|
501
|
+
return self._name
|
|
502
|
+
|
|
503
|
+
def execute(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
504
|
+
"""Try steps until one succeeds."""
|
|
505
|
+
errors: List[Exception] = []
|
|
506
|
+
|
|
507
|
+
for step in self._steps:
|
|
508
|
+
result = step.execute(input_data, context)
|
|
509
|
+
if result.success:
|
|
510
|
+
return result
|
|
511
|
+
errors.append(result.error)
|
|
512
|
+
|
|
513
|
+
return ToolResult.fail(
|
|
514
|
+
CompositionError(f"All fallback steps failed: {errors}"), self._name, errors=errors
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
async def execute_async(self, input_data: Any, context: Dict[str, Any]) -> ToolResult[T]:
|
|
518
|
+
"""Try steps until one succeeds (async)."""
|
|
519
|
+
errors: List[Exception] = []
|
|
520
|
+
|
|
521
|
+
for step in self._steps:
|
|
522
|
+
result = await step.execute_async(input_data, context)
|
|
523
|
+
if result.success:
|
|
524
|
+
return result
|
|
525
|
+
errors.append(result.error)
|
|
526
|
+
|
|
527
|
+
return ToolResult.fail(
|
|
528
|
+
CompositionError(f"All fallback steps failed: {errors}"), self._name, errors=errors
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
# Builder pattern for creating compositions
|
|
533
|
+
class ToolChain(Generic[T]):
|
|
534
|
+
"""Fluent builder for creating tool compositions.
|
|
535
|
+
|
|
536
|
+
Example:
|
|
537
|
+
>>> chain = (ToolChain(name="process_document")
|
|
538
|
+
... .then(parse_input)
|
|
539
|
+
... .then(validate_data)
|
|
540
|
+
... .parallel([extract_text, extract_images])
|
|
541
|
+
... .then(merge_results)
|
|
542
|
+
... .build())
|
|
543
|
+
>>> result = chain.execute(document, {})
|
|
544
|
+
"""
|
|
545
|
+
|
|
546
|
+
def __init__(self, name: str = "chain"):
|
|
547
|
+
"""Initialize tool chain builder.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
name: Name for the resulting composition.
|
|
551
|
+
"""
|
|
552
|
+
self._name = name
|
|
553
|
+
self._steps: List[ToolStep] = []
|
|
554
|
+
|
|
555
|
+
def then(self, step: Union[ToolStep, Callable]) -> "ToolChain[T]":
|
|
556
|
+
"""Add a sequential step.
|
|
557
|
+
|
|
558
|
+
Args:
|
|
559
|
+
step: ToolStep or callable to add.
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
Self for chaining.
|
|
563
|
+
"""
|
|
564
|
+
if callable(step) and not isinstance(step, ToolStep):
|
|
565
|
+
step = FunctionStep(step)
|
|
566
|
+
self._steps.append(step)
|
|
567
|
+
return self
|
|
568
|
+
|
|
569
|
+
def parallel(
|
|
570
|
+
self, steps: List[Union[ToolStep, Callable]], collect_all: bool = True
|
|
571
|
+
) -> "ToolChain[T]":
|
|
572
|
+
"""Add parallel execution.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
steps: Steps to run in parallel.
|
|
576
|
+
collect_all: Whether to wait for all or return first.
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Self for chaining.
|
|
580
|
+
"""
|
|
581
|
+
converted = [
|
|
582
|
+
FunctionStep(s) if callable(s) and not isinstance(s, ToolStep) else s for s in steps
|
|
583
|
+
]
|
|
584
|
+
self._steps.append(ParallelExecution(converted, collect_all=collect_all))
|
|
585
|
+
return self
|
|
586
|
+
|
|
587
|
+
def branch(
|
|
588
|
+
self,
|
|
589
|
+
condition: Callable[[Any, Dict[str, Any]], bool],
|
|
590
|
+
if_true: Union[ToolStep, Callable],
|
|
591
|
+
if_false: Optional[Union[ToolStep, Callable]] = None,
|
|
592
|
+
) -> "ToolChain[T]":
|
|
593
|
+
"""Add conditional branching.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
condition: Condition function.
|
|
597
|
+
if_true: Step if condition is True.
|
|
598
|
+
if_false: Step if condition is False.
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
Self for chaining.
|
|
602
|
+
"""
|
|
603
|
+
if callable(if_true) and not isinstance(if_true, ToolStep):
|
|
604
|
+
if_true = FunctionStep(if_true)
|
|
605
|
+
if if_false and callable(if_false) and not isinstance(if_false, ToolStep):
|
|
606
|
+
if_false = FunctionStep(if_false)
|
|
607
|
+
|
|
608
|
+
self._steps.append(ConditionalStep(condition, if_true, if_false))
|
|
609
|
+
return self
|
|
610
|
+
|
|
611
|
+
def fallback(self, steps: List[Union[ToolStep, Callable]]) -> "ToolChain[T]":
|
|
612
|
+
"""Add fallback execution.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
steps: Steps to try until one succeeds.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
Self for chaining.
|
|
619
|
+
"""
|
|
620
|
+
converted = [
|
|
621
|
+
FunctionStep(s) if callable(s) and not isinstance(s, ToolStep) else s for s in steps
|
|
622
|
+
]
|
|
623
|
+
self._steps.append(FallbackStep(converted))
|
|
624
|
+
return self
|
|
625
|
+
|
|
626
|
+
def build(self) -> Pipeline:
|
|
627
|
+
"""Build the final pipeline.
|
|
628
|
+
|
|
629
|
+
Returns:
|
|
630
|
+
Pipeline ready for execution.
|
|
631
|
+
"""
|
|
632
|
+
return Pipeline(self._steps, name=self._name)
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def compose(*steps: Union[ToolStep, Callable], name: str = "composed") -> Pipeline:
|
|
636
|
+
"""Convenience function to compose tools.
|
|
637
|
+
|
|
638
|
+
Example:
|
|
639
|
+
>>> pipeline = compose(parse, process, format, name="my_pipeline")
|
|
640
|
+
>>> result = pipeline.execute(data, {})
|
|
641
|
+
"""
|
|
642
|
+
converted = [
|
|
643
|
+
FunctionStep(s) if callable(s) and not isinstance(s, ToolStep) else s for s in steps
|
|
644
|
+
]
|
|
645
|
+
return Pipeline(converted, name=name)
|