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.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. 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)