proxilion 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proxilion/__init__.py +136 -0
- proxilion/audit/__init__.py +133 -0
- proxilion/audit/base_exporters.py +527 -0
- proxilion/audit/compliance/__init__.py +130 -0
- proxilion/audit/compliance/base.py +457 -0
- proxilion/audit/compliance/eu_ai_act.py +603 -0
- proxilion/audit/compliance/iso27001.py +544 -0
- proxilion/audit/compliance/soc2.py +491 -0
- proxilion/audit/events.py +493 -0
- proxilion/audit/explainability.py +1173 -0
- proxilion/audit/exporters/__init__.py +58 -0
- proxilion/audit/exporters/aws_s3.py +636 -0
- proxilion/audit/exporters/azure_storage.py +608 -0
- proxilion/audit/exporters/cloud_base.py +468 -0
- proxilion/audit/exporters/gcp_storage.py +570 -0
- proxilion/audit/exporters/multi_exporter.py +498 -0
- proxilion/audit/hash_chain.py +652 -0
- proxilion/audit/logger.py +543 -0
- proxilion/caching/__init__.py +49 -0
- proxilion/caching/tool_cache.py +633 -0
- proxilion/context/__init__.py +73 -0
- proxilion/context/context_window.py +556 -0
- proxilion/context/message_history.py +505 -0
- proxilion/context/session.py +735 -0
- proxilion/contrib/__init__.py +51 -0
- proxilion/contrib/anthropic.py +609 -0
- proxilion/contrib/google.py +1012 -0
- proxilion/contrib/langchain.py +641 -0
- proxilion/contrib/mcp.py +893 -0
- proxilion/contrib/openai.py +646 -0
- proxilion/core.py +3058 -0
- proxilion/decorators.py +966 -0
- proxilion/engines/__init__.py +287 -0
- proxilion/engines/base.py +266 -0
- proxilion/engines/casbin_engine.py +412 -0
- proxilion/engines/opa_engine.py +493 -0
- proxilion/engines/simple.py +437 -0
- proxilion/exceptions.py +887 -0
- proxilion/guards/__init__.py +54 -0
- proxilion/guards/input_guard.py +522 -0
- proxilion/guards/output_guard.py +634 -0
- proxilion/observability/__init__.py +198 -0
- proxilion/observability/cost_tracker.py +866 -0
- proxilion/observability/hooks.py +683 -0
- proxilion/observability/metrics.py +798 -0
- proxilion/observability/session_cost_tracker.py +1063 -0
- proxilion/policies/__init__.py +67 -0
- proxilion/policies/base.py +304 -0
- proxilion/policies/builtin.py +486 -0
- proxilion/policies/registry.py +376 -0
- proxilion/providers/__init__.py +201 -0
- proxilion/providers/adapter.py +468 -0
- proxilion/providers/anthropic_adapter.py +330 -0
- proxilion/providers/gemini_adapter.py +391 -0
- proxilion/providers/openai_adapter.py +294 -0
- proxilion/py.typed +0 -0
- proxilion/resilience/__init__.py +81 -0
- proxilion/resilience/degradation.py +615 -0
- proxilion/resilience/fallback.py +555 -0
- proxilion/resilience/retry.py +554 -0
- proxilion/scheduling/__init__.py +57 -0
- proxilion/scheduling/priority_queue.py +419 -0
- proxilion/scheduling/scheduler.py +459 -0
- proxilion/security/__init__.py +244 -0
- proxilion/security/agent_trust.py +968 -0
- proxilion/security/behavioral_drift.py +794 -0
- proxilion/security/cascade_protection.py +869 -0
- proxilion/security/circuit_breaker.py +428 -0
- proxilion/security/cost_limiter.py +690 -0
- proxilion/security/idor_protection.py +460 -0
- proxilion/security/intent_capsule.py +849 -0
- proxilion/security/intent_validator.py +495 -0
- proxilion/security/memory_integrity.py +767 -0
- proxilion/security/rate_limiter.py +509 -0
- proxilion/security/scope_enforcer.py +680 -0
- proxilion/security/sequence_validator.py +636 -0
- proxilion/security/trust_boundaries.py +784 -0
- proxilion/streaming/__init__.py +70 -0
- proxilion/streaming/detector.py +761 -0
- proxilion/streaming/transformer.py +674 -0
- proxilion/timeouts/__init__.py +55 -0
- proxilion/timeouts/decorators.py +477 -0
- proxilion/timeouts/manager.py +545 -0
- proxilion/tools/__init__.py +69 -0
- proxilion/tools/decorators.py +493 -0
- proxilion/tools/registry.py +732 -0
- proxilion/types.py +339 -0
- proxilion/validation/__init__.py +93 -0
- proxilion/validation/pydantic_schema.py +351 -0
- proxilion/validation/schema.py +651 -0
- proxilion-0.0.1.dist-info/METADATA +872 -0
- proxilion-0.0.1.dist-info/RECORD +94 -0
- proxilion-0.0.1.dist-info/WHEEL +4 -0
- proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Proxilion contrib module - Framework integrations.
|
|
3
|
+
|
|
4
|
+
This package provides integrations with popular AI frameworks
|
|
5
|
+
and protocols:
|
|
6
|
+
|
|
7
|
+
- MCP (Model Context Protocol) - Anthropic's agent-tool protocol
|
|
8
|
+
- LangChain - Popular LLM framework
|
|
9
|
+
- OpenAI - Function calling integration
|
|
10
|
+
- Anthropic - Tool use integration
|
|
11
|
+
- Google - Vertex AI / Gemini integration
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
>>> # MCP integration
|
|
15
|
+
>>> from proxilion.contrib.mcp import (
|
|
16
|
+
... MCPToolWrapper,
|
|
17
|
+
... ProxilionMCPServer,
|
|
18
|
+
... MCPSession,
|
|
19
|
+
... )
|
|
20
|
+
>>>
|
|
21
|
+
>>> # LangChain integration
|
|
22
|
+
>>> from proxilion.contrib.langchain import (
|
|
23
|
+
... ProxilionTool,
|
|
24
|
+
... ProxilionCallbackHandler,
|
|
25
|
+
... wrap_langchain_tools,
|
|
26
|
+
... )
|
|
27
|
+
>>>
|
|
28
|
+
>>> # OpenAI integration
|
|
29
|
+
>>> from proxilion.contrib.openai import (
|
|
30
|
+
... ProxilionFunctionHandler,
|
|
31
|
+
... create_secure_function,
|
|
32
|
+
... )
|
|
33
|
+
>>>
|
|
34
|
+
>>> # Anthropic integration
|
|
35
|
+
>>> from proxilion.contrib.anthropic import (
|
|
36
|
+
... ProxilionToolHandler,
|
|
37
|
+
... process_tool_use,
|
|
38
|
+
... )
|
|
39
|
+
>>>
|
|
40
|
+
>>> # Google Vertex AI / Gemini integration
|
|
41
|
+
>>> from proxilion.contrib.google import (
|
|
42
|
+
... ProxilionVertexHandler,
|
|
43
|
+
... GeminiFunctionCall,
|
|
44
|
+
... GeminiToolResult,
|
|
45
|
+
... extract_function_calls,
|
|
46
|
+
... format_tool_response,
|
|
47
|
+
... )
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Note: Individual modules handle optional dependencies gracefully
|
|
51
|
+
# and will work without their respective SDKs installed
|
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Anthropic integration for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module provides authorization wrappers for Anthropic's tool_use feature,
|
|
5
|
+
enabling secure tool execution with user-context authorization.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- ProxilionToolHandler: Manages tool registration and execution
|
|
9
|
+
- process_tool_use: Process tool_use blocks from Claude responses
|
|
10
|
+
- Safe error handling for production use
|
|
11
|
+
|
|
12
|
+
Note:
|
|
13
|
+
Anthropic SDK is an optional dependency. This module works by wrapping
|
|
14
|
+
tool definitions and implementations rather than modifying the
|
|
15
|
+
Anthropic client directly.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> from anthropic import Anthropic
|
|
19
|
+
>>> from proxilion import Proxilion
|
|
20
|
+
>>> from proxilion.contrib.anthropic import ProxilionToolHandler
|
|
21
|
+
>>>
|
|
22
|
+
>>> auth = Proxilion()
|
|
23
|
+
>>> handler = ProxilionToolHandler(auth)
|
|
24
|
+
>>>
|
|
25
|
+
>>> # Register a tool
|
|
26
|
+
>>> handler.register_tool(
|
|
27
|
+
... name="get_weather",
|
|
28
|
+
... schema=weather_schema,
|
|
29
|
+
... implementation=get_weather_impl,
|
|
30
|
+
... resource="weather_api",
|
|
31
|
+
... )
|
|
32
|
+
>>>
|
|
33
|
+
>>> # Process tool_use from Claude response
|
|
34
|
+
>>> for block in response.content:
|
|
35
|
+
... if block.type == "tool_use":
|
|
36
|
+
... result = handler.execute(
|
|
37
|
+
... tool_use_block=block,
|
|
38
|
+
... user=current_user,
|
|
39
|
+
... )
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import asyncio
|
|
45
|
+
import inspect
|
|
46
|
+
import json
|
|
47
|
+
import logging
|
|
48
|
+
from collections.abc import Callable
|
|
49
|
+
from dataclasses import dataclass, field
|
|
50
|
+
from datetime import datetime, timezone
|
|
51
|
+
from typing import Any, TypeVar
|
|
52
|
+
|
|
53
|
+
from proxilion.exceptions import ProxilionError
|
|
54
|
+
from proxilion.types import AgentContext, UserContext
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
T = TypeVar("T")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AnthropicIntegrationError(ProxilionError):
|
|
62
|
+
"""Error in Anthropic integration."""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ToolNotFoundError(AnthropicIntegrationError):
|
|
67
|
+
"""Raised when a tool is not registered."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, tool_name: str) -> None:
|
|
70
|
+
self.tool_name = tool_name
|
|
71
|
+
super().__init__(f"Tool not registered: {tool_name}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ToolExecutionError(AnthropicIntegrationError):
|
|
75
|
+
"""Raised when tool execution fails."""
|
|
76
|
+
|
|
77
|
+
def __init__(self, tool_name: str, safe_message: str) -> None:
|
|
78
|
+
self.tool_name = tool_name
|
|
79
|
+
self.safe_message = safe_message
|
|
80
|
+
super().__init__(f"Tool execution failed: {safe_message}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class RegisteredTool:
|
|
85
|
+
"""A registered tool with its schema and implementation."""
|
|
86
|
+
name: str
|
|
87
|
+
schema: dict[str, Any]
|
|
88
|
+
implementation: Callable[..., Any]
|
|
89
|
+
resource: str
|
|
90
|
+
action: str
|
|
91
|
+
async_impl: bool
|
|
92
|
+
description: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class ToolUseResult:
|
|
97
|
+
"""Result of a tool use execution."""
|
|
98
|
+
tool_use_id: str
|
|
99
|
+
tool_name: str
|
|
100
|
+
success: bool
|
|
101
|
+
result: Any | None = None
|
|
102
|
+
error: str | None = None
|
|
103
|
+
authorized: bool = True
|
|
104
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
105
|
+
|
|
106
|
+
def to_tool_result_block(self) -> dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
Convert to Anthropic tool_result format.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dictionary suitable for tool_result content block.
|
|
112
|
+
"""
|
|
113
|
+
if self.success:
|
|
114
|
+
content = self.result
|
|
115
|
+
if not isinstance(content, str):
|
|
116
|
+
content = json.dumps(content)
|
|
117
|
+
return {
|
|
118
|
+
"type": "tool_result",
|
|
119
|
+
"tool_use_id": self.tool_use_id,
|
|
120
|
+
"content": content,
|
|
121
|
+
}
|
|
122
|
+
else:
|
|
123
|
+
return {
|
|
124
|
+
"type": "tool_result",
|
|
125
|
+
"tool_use_id": self.tool_use_id,
|
|
126
|
+
"content": self.error or "Tool execution failed",
|
|
127
|
+
"is_error": True,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ProxilionToolHandler:
|
|
132
|
+
"""
|
|
133
|
+
Handler for Anthropic tool_use with Proxilion authorization.
|
|
134
|
+
|
|
135
|
+
Manages tool registration, authorization, and execution for
|
|
136
|
+
Anthropic's Claude tool_use feature.
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
>>> from proxilion import Proxilion, Policy, UserContext
|
|
140
|
+
>>> from proxilion.contrib.anthropic import ProxilionToolHandler
|
|
141
|
+
>>>
|
|
142
|
+
>>> auth = Proxilion()
|
|
143
|
+
>>>
|
|
144
|
+
>>> @auth.policy("calculator")
|
|
145
|
+
... class CalculatorPolicy(Policy):
|
|
146
|
+
... def can_execute(self, context):
|
|
147
|
+
... return True
|
|
148
|
+
>>>
|
|
149
|
+
>>> handler = ProxilionToolHandler(auth)
|
|
150
|
+
>>>
|
|
151
|
+
>>> def calculate(expression: str) -> str:
|
|
152
|
+
... return str(eval(expression))
|
|
153
|
+
>>>
|
|
154
|
+
>>> handler.register_tool(
|
|
155
|
+
... name="calculator",
|
|
156
|
+
... schema={
|
|
157
|
+
... "name": "calculator",
|
|
158
|
+
... "description": "Evaluate a math expression",
|
|
159
|
+
... "input_schema": {
|
|
160
|
+
... "type": "object",
|
|
161
|
+
... "properties": {
|
|
162
|
+
... "expression": {"type": "string"}
|
|
163
|
+
... },
|
|
164
|
+
... "required": ["expression"]
|
|
165
|
+
... }
|
|
166
|
+
... },
|
|
167
|
+
... implementation=calculate,
|
|
168
|
+
... resource="calculator",
|
|
169
|
+
... )
|
|
170
|
+
>>>
|
|
171
|
+
>>> # Execute tool
|
|
172
|
+
>>> result = handler.execute(
|
|
173
|
+
... tool_name="calculator",
|
|
174
|
+
... tool_use_id="toolu_123",
|
|
175
|
+
... input_data={"expression": "2 + 2"},
|
|
176
|
+
... user=user,
|
|
177
|
+
... )
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(
|
|
181
|
+
self,
|
|
182
|
+
proxilion: Any,
|
|
183
|
+
default_action: str = "execute",
|
|
184
|
+
safe_errors: bool = True,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Initialize the tool handler.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
proxilion: Proxilion instance for authorization.
|
|
191
|
+
default_action: Default action for authorization checks.
|
|
192
|
+
safe_errors: If True, return safe error messages.
|
|
193
|
+
"""
|
|
194
|
+
self.proxilion = proxilion
|
|
195
|
+
self.default_action = default_action
|
|
196
|
+
self.safe_errors = safe_errors
|
|
197
|
+
|
|
198
|
+
self._tools: dict[str, RegisteredTool] = {}
|
|
199
|
+
self._execution_history: list[ToolUseResult] = []
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def tools(self) -> list[RegisteredTool]:
|
|
203
|
+
"""Get list of registered tools."""
|
|
204
|
+
return list(self._tools.values())
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def tool_schemas(self) -> list[dict[str, Any]]:
|
|
208
|
+
"""Get list of tool schemas for Anthropic API."""
|
|
209
|
+
return [t.schema for t in self._tools.values()]
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def execution_history(self) -> list[ToolUseResult]:
|
|
213
|
+
"""Get history of tool executions."""
|
|
214
|
+
return list(self._execution_history)
|
|
215
|
+
|
|
216
|
+
def register_tool(
|
|
217
|
+
self,
|
|
218
|
+
name: str,
|
|
219
|
+
schema: dict[str, Any],
|
|
220
|
+
implementation: Callable[..., Any],
|
|
221
|
+
resource: str | None = None,
|
|
222
|
+
action: str | None = None,
|
|
223
|
+
description: str | None = None,
|
|
224
|
+
) -> None:
|
|
225
|
+
"""
|
|
226
|
+
Register a tool for execution.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
name: Tool name (must match Anthropic tool_use name).
|
|
230
|
+
schema: Anthropic tool schema with input_schema.
|
|
231
|
+
implementation: Python function to execute.
|
|
232
|
+
resource: Resource name for authorization (default: tool name).
|
|
233
|
+
action: Action for authorization (default: handler default).
|
|
234
|
+
description: Optional description override.
|
|
235
|
+
"""
|
|
236
|
+
is_async = inspect.iscoroutinefunction(implementation)
|
|
237
|
+
|
|
238
|
+
self._tools[name] = RegisteredTool(
|
|
239
|
+
name=name,
|
|
240
|
+
schema=schema,
|
|
241
|
+
implementation=implementation,
|
|
242
|
+
resource=resource or name,
|
|
243
|
+
action=action or self.default_action,
|
|
244
|
+
async_impl=is_async,
|
|
245
|
+
description=description or schema.get("description", ""),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
logger.debug(f"Registered tool: {name} (resource: {resource or name})")
|
|
249
|
+
|
|
250
|
+
def unregister_tool(self, name: str) -> bool:
|
|
251
|
+
"""
|
|
252
|
+
Unregister a tool.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
name: Tool name to unregister.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
True if tool was registered and removed.
|
|
259
|
+
"""
|
|
260
|
+
if name in self._tools:
|
|
261
|
+
del self._tools[name]
|
|
262
|
+
return True
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
def get_tool(self, name: str) -> RegisteredTool | None:
|
|
266
|
+
"""Get a registered tool by name."""
|
|
267
|
+
return self._tools.get(name)
|
|
268
|
+
|
|
269
|
+
def execute(
|
|
270
|
+
self,
|
|
271
|
+
tool_name: str | None = None,
|
|
272
|
+
tool_use_id: str | None = None,
|
|
273
|
+
input_data: dict[str, Any] | None = None,
|
|
274
|
+
user: UserContext | None = None,
|
|
275
|
+
agent: AgentContext | None = None,
|
|
276
|
+
tool_use_block: Any | None = None,
|
|
277
|
+
) -> ToolUseResult:
|
|
278
|
+
"""
|
|
279
|
+
Execute a tool with authorization.
|
|
280
|
+
|
|
281
|
+
Can accept either explicit parameters or an Anthropic
|
|
282
|
+
tool_use block object.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
tool_name: Name of the tool to call.
|
|
286
|
+
tool_use_id: Unique ID for this tool use.
|
|
287
|
+
input_data: Tool input data.
|
|
288
|
+
user: User context for authorization.
|
|
289
|
+
agent: Optional agent context.
|
|
290
|
+
tool_use_block: Anthropic tool_use block (alternative).
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
ToolUseResult with execution result or error.
|
|
294
|
+
"""
|
|
295
|
+
# Extract from tool_use_block if provided
|
|
296
|
+
if tool_use_block is not None:
|
|
297
|
+
tool_name = getattr(tool_use_block, "name", None)
|
|
298
|
+
tool_use_id = getattr(tool_use_block, "id", tool_use_id or "unknown")
|
|
299
|
+
input_data = getattr(tool_use_block, "input", {})
|
|
300
|
+
|
|
301
|
+
tool_use_id = tool_use_id or "unknown"
|
|
302
|
+
|
|
303
|
+
if tool_name is None:
|
|
304
|
+
return ToolUseResult(
|
|
305
|
+
tool_use_id=tool_use_id,
|
|
306
|
+
tool_name="unknown",
|
|
307
|
+
success=False,
|
|
308
|
+
error="No tool name provided",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
input_data = input_data or {}
|
|
312
|
+
|
|
313
|
+
# Get registered tool
|
|
314
|
+
tool = self._tools.get(tool_name)
|
|
315
|
+
if tool is None:
|
|
316
|
+
result = ToolUseResult(
|
|
317
|
+
tool_use_id=tool_use_id,
|
|
318
|
+
tool_name=tool_name,
|
|
319
|
+
success=False,
|
|
320
|
+
error=f"Tool not found: {tool_name}",
|
|
321
|
+
)
|
|
322
|
+
self._execution_history.append(result)
|
|
323
|
+
return result
|
|
324
|
+
|
|
325
|
+
# Check authorization
|
|
326
|
+
if user is not None:
|
|
327
|
+
context = {
|
|
328
|
+
"tool_name": tool_name,
|
|
329
|
+
"input": input_data,
|
|
330
|
+
**input_data,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
auth_result = self.proxilion.check(user, tool.action, tool.resource, context)
|
|
334
|
+
|
|
335
|
+
if not auth_result.allowed:
|
|
336
|
+
result = ToolUseResult(
|
|
337
|
+
tool_use_id=tool_use_id,
|
|
338
|
+
tool_name=tool_name,
|
|
339
|
+
success=False,
|
|
340
|
+
error="Not authorized" if self.safe_errors else auth_result.reason,
|
|
341
|
+
authorized=False,
|
|
342
|
+
)
|
|
343
|
+
self._execution_history.append(result)
|
|
344
|
+
return result
|
|
345
|
+
|
|
346
|
+
# Execute tool
|
|
347
|
+
try:
|
|
348
|
+
if tool.async_impl:
|
|
349
|
+
loop = asyncio.new_event_loop()
|
|
350
|
+
try:
|
|
351
|
+
output = loop.run_until_complete(tool.implementation(**input_data))
|
|
352
|
+
finally:
|
|
353
|
+
loop.close()
|
|
354
|
+
else:
|
|
355
|
+
output = tool.implementation(**input_data)
|
|
356
|
+
|
|
357
|
+
# Convert output to string if needed for Anthropic
|
|
358
|
+
if not isinstance(output, str):
|
|
359
|
+
output = json.dumps(output)
|
|
360
|
+
|
|
361
|
+
result = ToolUseResult(
|
|
362
|
+
tool_use_id=tool_use_id,
|
|
363
|
+
tool_name=tool_name,
|
|
364
|
+
success=True,
|
|
365
|
+
result=output,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
logger.error(f"Tool execution error: {tool_name} - {e}")
|
|
370
|
+
|
|
371
|
+
error_msg = "Tool execution failed"
|
|
372
|
+
if not self.safe_errors:
|
|
373
|
+
error_msg = str(e)
|
|
374
|
+
|
|
375
|
+
result = ToolUseResult(
|
|
376
|
+
tool_use_id=tool_use_id,
|
|
377
|
+
tool_name=tool_name,
|
|
378
|
+
success=False,
|
|
379
|
+
error=error_msg,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
self._execution_history.append(result)
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
async def execute_async(
|
|
386
|
+
self,
|
|
387
|
+
tool_name: str | None = None,
|
|
388
|
+
tool_use_id: str | None = None,
|
|
389
|
+
input_data: dict[str, Any] | None = None,
|
|
390
|
+
user: UserContext | None = None,
|
|
391
|
+
agent: AgentContext | None = None,
|
|
392
|
+
tool_use_block: Any | None = None,
|
|
393
|
+
) -> ToolUseResult:
|
|
394
|
+
"""
|
|
395
|
+
Execute a tool asynchronously with authorization.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
tool_name: Name of the tool to call.
|
|
399
|
+
tool_use_id: Unique ID for this tool use.
|
|
400
|
+
input_data: Tool input data.
|
|
401
|
+
user: User context for authorization.
|
|
402
|
+
agent: Optional agent context.
|
|
403
|
+
tool_use_block: Anthropic tool_use block.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
ToolUseResult with execution result or error.
|
|
407
|
+
"""
|
|
408
|
+
if tool_use_block is not None:
|
|
409
|
+
tool_name = getattr(tool_use_block, "name", None)
|
|
410
|
+
tool_use_id = getattr(tool_use_block, "id", tool_use_id or "unknown")
|
|
411
|
+
input_data = getattr(tool_use_block, "input", {})
|
|
412
|
+
|
|
413
|
+
tool_use_id = tool_use_id or "unknown"
|
|
414
|
+
|
|
415
|
+
if tool_name is None:
|
|
416
|
+
return ToolUseResult(
|
|
417
|
+
tool_use_id=tool_use_id,
|
|
418
|
+
tool_name="unknown",
|
|
419
|
+
success=False,
|
|
420
|
+
error="No tool name provided",
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
input_data = input_data or {}
|
|
424
|
+
|
|
425
|
+
tool = self._tools.get(tool_name)
|
|
426
|
+
if tool is None:
|
|
427
|
+
result = ToolUseResult(
|
|
428
|
+
tool_use_id=tool_use_id,
|
|
429
|
+
tool_name=tool_name,
|
|
430
|
+
success=False,
|
|
431
|
+
error=f"Tool not found: {tool_name}",
|
|
432
|
+
)
|
|
433
|
+
self._execution_history.append(result)
|
|
434
|
+
return result
|
|
435
|
+
|
|
436
|
+
# Check authorization
|
|
437
|
+
if user is not None:
|
|
438
|
+
context = {
|
|
439
|
+
"tool_name": tool_name,
|
|
440
|
+
"input": input_data,
|
|
441
|
+
**input_data,
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
auth_result = self.proxilion.check(user, tool.action, tool.resource, context)
|
|
445
|
+
|
|
446
|
+
if not auth_result.allowed:
|
|
447
|
+
result = ToolUseResult(
|
|
448
|
+
tool_use_id=tool_use_id,
|
|
449
|
+
tool_name=tool_name,
|
|
450
|
+
success=False,
|
|
451
|
+
error="Not authorized" if self.safe_errors else auth_result.reason,
|
|
452
|
+
authorized=False,
|
|
453
|
+
)
|
|
454
|
+
self._execution_history.append(result)
|
|
455
|
+
return result
|
|
456
|
+
|
|
457
|
+
# Execute tool
|
|
458
|
+
try:
|
|
459
|
+
if tool.async_impl:
|
|
460
|
+
output = await tool.implementation(**input_data)
|
|
461
|
+
else:
|
|
462
|
+
loop = asyncio.get_event_loop()
|
|
463
|
+
output = await loop.run_in_executor(
|
|
464
|
+
None,
|
|
465
|
+
lambda: tool.implementation(**input_data),
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if not isinstance(output, str):
|
|
469
|
+
output = json.dumps(output)
|
|
470
|
+
|
|
471
|
+
result = ToolUseResult(
|
|
472
|
+
tool_use_id=tool_use_id,
|
|
473
|
+
tool_name=tool_name,
|
|
474
|
+
success=True,
|
|
475
|
+
result=output,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
except Exception as e:
|
|
479
|
+
logger.error(f"Tool execution error: {tool_name} - {e}")
|
|
480
|
+
|
|
481
|
+
error_msg = "Tool execution failed"
|
|
482
|
+
if not self.safe_errors:
|
|
483
|
+
error_msg = str(e)
|
|
484
|
+
|
|
485
|
+
result = ToolUseResult(
|
|
486
|
+
tool_use_id=tool_use_id,
|
|
487
|
+
tool_name=tool_name,
|
|
488
|
+
success=False,
|
|
489
|
+
error=error_msg,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
self._execution_history.append(result)
|
|
493
|
+
return result
|
|
494
|
+
|
|
495
|
+
def to_anthropic_tools(self) -> list[dict[str, Any]]:
|
|
496
|
+
"""
|
|
497
|
+
Get tool schemas in Anthropic tools format.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
List of tool definitions for Anthropic API.
|
|
501
|
+
"""
|
|
502
|
+
return self.tool_schemas
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def process_tool_use(
|
|
506
|
+
response: Any,
|
|
507
|
+
handler: ProxilionToolHandler,
|
|
508
|
+
user: UserContext | None = None,
|
|
509
|
+
) -> list[ToolUseResult]:
|
|
510
|
+
"""
|
|
511
|
+
Process an Anthropic response and execute any tool_use blocks.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
response: Anthropic API response object.
|
|
515
|
+
handler: ProxilionToolHandler for execution.
|
|
516
|
+
user: User context for authorization.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
List of ToolUseResult for each tool_use block.
|
|
520
|
+
|
|
521
|
+
Example:
|
|
522
|
+
>>> response = client.messages.create(...)
|
|
523
|
+
>>> results = process_tool_use(response, handler, user)
|
|
524
|
+
>>> tool_results = [r.to_tool_result_block() for r in results]
|
|
525
|
+
"""
|
|
526
|
+
results: list[ToolUseResult] = []
|
|
527
|
+
|
|
528
|
+
# Get content blocks from response
|
|
529
|
+
content = getattr(response, "content", [])
|
|
530
|
+
if not content:
|
|
531
|
+
return results
|
|
532
|
+
|
|
533
|
+
for block in content:
|
|
534
|
+
# Check if this is a tool_use block
|
|
535
|
+
block_type = getattr(block, "type", None)
|
|
536
|
+
if block_type != "tool_use":
|
|
537
|
+
continue
|
|
538
|
+
|
|
539
|
+
result = handler.execute(
|
|
540
|
+
tool_use_block=block,
|
|
541
|
+
user=user,
|
|
542
|
+
)
|
|
543
|
+
results.append(result)
|
|
544
|
+
|
|
545
|
+
return results
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
async def process_tool_use_async(
|
|
549
|
+
response: Any,
|
|
550
|
+
handler: ProxilionToolHandler,
|
|
551
|
+
user: UserContext | None = None,
|
|
552
|
+
) -> list[ToolUseResult]:
|
|
553
|
+
"""
|
|
554
|
+
Process an Anthropic response and execute tool_use blocks asynchronously.
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
response: Anthropic API response object.
|
|
558
|
+
handler: ProxilionToolHandler for execution.
|
|
559
|
+
user: User context for authorization.
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
List of ToolUseResult for each tool_use block.
|
|
563
|
+
"""
|
|
564
|
+
results: list[ToolUseResult] = []
|
|
565
|
+
|
|
566
|
+
content = getattr(response, "content", [])
|
|
567
|
+
if not content:
|
|
568
|
+
return results
|
|
569
|
+
|
|
570
|
+
for block in content:
|
|
571
|
+
block_type = getattr(block, "type", None)
|
|
572
|
+
if block_type != "tool_use":
|
|
573
|
+
continue
|
|
574
|
+
|
|
575
|
+
result = await handler.execute_async(
|
|
576
|
+
tool_use_block=block,
|
|
577
|
+
user=user,
|
|
578
|
+
)
|
|
579
|
+
results.append(result)
|
|
580
|
+
|
|
581
|
+
return results
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def create_tool_result_content(results: list[ToolUseResult]) -> list[dict[str, Any]]:
|
|
585
|
+
"""
|
|
586
|
+
Create tool_result content blocks from execution results.
|
|
587
|
+
|
|
588
|
+
Convenience function for building the tool_result message
|
|
589
|
+
to send back to Claude.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
results: List of ToolUseResult objects.
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
List of tool_result content blocks.
|
|
596
|
+
|
|
597
|
+
Example:
|
|
598
|
+
>>> results = process_tool_use(response, handler, user)
|
|
599
|
+
>>> tool_results = create_tool_result_content(results)
|
|
600
|
+
>>> next_response = client.messages.create(
|
|
601
|
+
... messages=[
|
|
602
|
+
... {"role": "user", "content": original_query},
|
|
603
|
+
... {"role": "assistant", "content": response.content},
|
|
604
|
+
... {"role": "user", "content": tool_results},
|
|
605
|
+
... ],
|
|
606
|
+
... ...
|
|
607
|
+
... )
|
|
608
|
+
"""
|
|
609
|
+
return [result.to_tool_result_block() for result in results]
|