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,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Anthropic adapter for Proxilion.
|
|
3
|
+
|
|
4
|
+
Provides translation between Anthropic's tool use format
|
|
5
|
+
and Proxilion's unified format.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from proxilion.providers.adapter import (
|
|
14
|
+
BaseAdapter,
|
|
15
|
+
Provider,
|
|
16
|
+
UnifiedResponse,
|
|
17
|
+
UnifiedToolCall,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AnthropicAdapter(BaseAdapter):
|
|
24
|
+
"""
|
|
25
|
+
Adapter for Anthropic Claude API.
|
|
26
|
+
|
|
27
|
+
Handles tool use blocks from Anthropic's Messages API,
|
|
28
|
+
including parallel tool calls.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> from anthropic import Anthropic
|
|
32
|
+
>>> from proxilion.providers import AnthropicAdapter
|
|
33
|
+
>>>
|
|
34
|
+
>>> adapter = AnthropicAdapter()
|
|
35
|
+
>>> client = Anthropic()
|
|
36
|
+
>>>
|
|
37
|
+
>>> response = client.messages.create(
|
|
38
|
+
... model="claude-sonnet-4-20250514",
|
|
39
|
+
... messages=[{"role": "user", "content": "Get weather"}],
|
|
40
|
+
... tools=[...],
|
|
41
|
+
... )
|
|
42
|
+
>>>
|
|
43
|
+
>>> # Extract tool calls
|
|
44
|
+
>>> tool_calls = adapter.extract_tool_calls(response)
|
|
45
|
+
>>> for call in tool_calls:
|
|
46
|
+
... print(f"Tool: {call.name}, Args: {call.arguments}")
|
|
47
|
+
>>>
|
|
48
|
+
>>> # Format result for continuation
|
|
49
|
+
>>> result_msg = adapter.format_tool_result(tool_calls[0], {"temp": 72})
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def provider(self) -> Provider:
|
|
54
|
+
"""Get the provider type."""
|
|
55
|
+
return Provider.ANTHROPIC
|
|
56
|
+
|
|
57
|
+
def extract_tool_calls(self, response: Any) -> list[UnifiedToolCall]:
|
|
58
|
+
"""
|
|
59
|
+
Extract tool calls from Anthropic response.
|
|
60
|
+
|
|
61
|
+
Anthropic returns tool_use blocks within the content array.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
response: Anthropic Message response.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of unified tool calls.
|
|
68
|
+
"""
|
|
69
|
+
# Handle dictionary form
|
|
70
|
+
if isinstance(response, dict):
|
|
71
|
+
return self._extract_from_dict(response)
|
|
72
|
+
|
|
73
|
+
# Handle object form
|
|
74
|
+
content = getattr(response, "content", None)
|
|
75
|
+
if not content:
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
tool_calls = []
|
|
79
|
+
for block in content:
|
|
80
|
+
# Check if it's a tool_use block
|
|
81
|
+
block_type = getattr(block, "type", None)
|
|
82
|
+
is_tool_use = block_type == "tool_use"
|
|
83
|
+
is_dict_tool_use = isinstance(block, dict) and block.get("type") == "tool_use"
|
|
84
|
+
if is_tool_use or is_dict_tool_use:
|
|
85
|
+
tool_calls.append(UnifiedToolCall.from_anthropic(block))
|
|
86
|
+
|
|
87
|
+
return tool_calls
|
|
88
|
+
|
|
89
|
+
def _extract_from_dict(self, response: dict) -> list[UnifiedToolCall]:
|
|
90
|
+
"""Extract tool calls from dictionary response."""
|
|
91
|
+
content = response.get("content", [])
|
|
92
|
+
tool_calls = []
|
|
93
|
+
|
|
94
|
+
for block in content:
|
|
95
|
+
if isinstance(block, dict) and block.get("type") == "tool_use":
|
|
96
|
+
tool_calls.append(UnifiedToolCall.from_anthropic(block))
|
|
97
|
+
|
|
98
|
+
return tool_calls
|
|
99
|
+
|
|
100
|
+
def extract_response(self, response: Any) -> UnifiedResponse:
|
|
101
|
+
"""
|
|
102
|
+
Extract full response from Anthropic response.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
response: Anthropic Message response.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
UnifiedResponse instance.
|
|
109
|
+
"""
|
|
110
|
+
tool_calls = self.extract_tool_calls(response)
|
|
111
|
+
|
|
112
|
+
# Handle dictionary form
|
|
113
|
+
if isinstance(response, dict):
|
|
114
|
+
content = response.get("content", [])
|
|
115
|
+
text_content = self._extract_text_content(content)
|
|
116
|
+
stop_reason = response.get("stop_reason")
|
|
117
|
+
|
|
118
|
+
usage = response.get("usage", {})
|
|
119
|
+
usage_dict = {
|
|
120
|
+
"input_tokens": usage.get("input_tokens", 0),
|
|
121
|
+
"output_tokens": usage.get("output_tokens", 0),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return UnifiedResponse(
|
|
125
|
+
content=text_content,
|
|
126
|
+
tool_calls=tool_calls,
|
|
127
|
+
finish_reason=stop_reason,
|
|
128
|
+
provider=Provider.ANTHROPIC,
|
|
129
|
+
usage=usage_dict,
|
|
130
|
+
raw=response,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Handle object form
|
|
134
|
+
content_blocks = getattr(response, "content", [])
|
|
135
|
+
text_content = self._extract_text_content_from_objects(content_blocks)
|
|
136
|
+
stop_reason = getattr(response, "stop_reason", None)
|
|
137
|
+
|
|
138
|
+
usage_dict = {}
|
|
139
|
+
usage = getattr(response, "usage", None)
|
|
140
|
+
if usage:
|
|
141
|
+
usage_dict = {
|
|
142
|
+
"input_tokens": getattr(usage, "input_tokens", 0),
|
|
143
|
+
"output_tokens": getattr(usage, "output_tokens", 0),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return UnifiedResponse(
|
|
147
|
+
content=text_content,
|
|
148
|
+
tool_calls=tool_calls,
|
|
149
|
+
finish_reason=stop_reason,
|
|
150
|
+
provider=Provider.ANTHROPIC,
|
|
151
|
+
usage=usage_dict,
|
|
152
|
+
raw=response,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def _extract_text_content(self, content: list) -> str | None:
|
|
156
|
+
"""Extract text content from content blocks (dict form)."""
|
|
157
|
+
text_parts = []
|
|
158
|
+
for block in content:
|
|
159
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
160
|
+
text_parts.append(block.get("text", ""))
|
|
161
|
+
|
|
162
|
+
return "".join(text_parts) if text_parts else None
|
|
163
|
+
|
|
164
|
+
def _extract_text_content_from_objects(self, content: list) -> str | None:
|
|
165
|
+
"""Extract text content from content blocks (object form)."""
|
|
166
|
+
text_parts = []
|
|
167
|
+
for block in content:
|
|
168
|
+
block_type = getattr(block, "type", None)
|
|
169
|
+
if block_type == "text":
|
|
170
|
+
text_parts.append(getattr(block, "text", ""))
|
|
171
|
+
|
|
172
|
+
return "".join(text_parts) if text_parts else None
|
|
173
|
+
|
|
174
|
+
def format_tool_result(
|
|
175
|
+
self,
|
|
176
|
+
tool_call: UnifiedToolCall,
|
|
177
|
+
result: Any,
|
|
178
|
+
is_error: bool = False,
|
|
179
|
+
) -> dict[str, Any]:
|
|
180
|
+
"""
|
|
181
|
+
Format tool result for Anthropic API.
|
|
182
|
+
|
|
183
|
+
Creates a tool_result block for the user message.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
tool_call: The original tool call.
|
|
187
|
+
result: The result to send back.
|
|
188
|
+
is_error: Whether the result represents an error.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dictionary with tool_result block.
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
>>> result_block = adapter.format_tool_result(
|
|
195
|
+
... tool_call,
|
|
196
|
+
... {"temperature": 72}
|
|
197
|
+
... )
|
|
198
|
+
>>> messages.append({
|
|
199
|
+
... "role": "user",
|
|
200
|
+
... "content": [result_block],
|
|
201
|
+
... })
|
|
202
|
+
"""
|
|
203
|
+
content = self._serialize_result(result)
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
"type": "tool_result",
|
|
207
|
+
"tool_use_id": tool_call.id,
|
|
208
|
+
"content": content,
|
|
209
|
+
"is_error": is_error,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
def format_tools(
|
|
213
|
+
self,
|
|
214
|
+
tools: list[Any],
|
|
215
|
+
) -> list[dict[str, Any]]:
|
|
216
|
+
"""
|
|
217
|
+
Format tool definitions for Anthropic API.
|
|
218
|
+
|
|
219
|
+
Converts ToolDefinition objects to Anthropic's tool format.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
tools: List of ToolDefinition objects.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List of tool definitions in Anthropic format.
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
>>> anthropic_tools = adapter.format_tools(registry.list_enabled())
|
|
229
|
+
>>> response = client.messages.create(
|
|
230
|
+
... model="claude-sonnet-4-20250514",
|
|
231
|
+
... messages=[...],
|
|
232
|
+
... tools=anthropic_tools,
|
|
233
|
+
... )
|
|
234
|
+
"""
|
|
235
|
+
formatted = []
|
|
236
|
+
for tool in tools:
|
|
237
|
+
# Check if it's a ToolDefinition
|
|
238
|
+
if hasattr(tool, "to_anthropic_format"):
|
|
239
|
+
formatted.append(tool.to_anthropic_format())
|
|
240
|
+
elif hasattr(tool, "name") and hasattr(tool, "description"):
|
|
241
|
+
# Manual conversion
|
|
242
|
+
formatted.append({
|
|
243
|
+
"name": tool.name,
|
|
244
|
+
"description": tool.description,
|
|
245
|
+
"input_schema": getattr(tool, "parameters", {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"properties": {},
|
|
248
|
+
}),
|
|
249
|
+
})
|
|
250
|
+
elif isinstance(tool, dict):
|
|
251
|
+
# Already in correct format or needs conversion
|
|
252
|
+
if "input_schema" in tool:
|
|
253
|
+
formatted.append(tool)
|
|
254
|
+
elif "parameters" in tool:
|
|
255
|
+
formatted.append({
|
|
256
|
+
"name": tool.get("name"),
|
|
257
|
+
"description": tool.get("description", ""),
|
|
258
|
+
"input_schema": tool.get("parameters"),
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
return formatted
|
|
262
|
+
|
|
263
|
+
def format_user_message_with_results(
|
|
264
|
+
self,
|
|
265
|
+
results: list[tuple[UnifiedToolCall, Any, bool]],
|
|
266
|
+
) -> dict[str, Any]:
|
|
267
|
+
"""
|
|
268
|
+
Format a user message containing multiple tool results.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
results: List of (tool_call, result, is_error) tuples.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Dictionary suitable for Anthropic messages list.
|
|
275
|
+
|
|
276
|
+
Example:
|
|
277
|
+
>>> results = [
|
|
278
|
+
... (call1, {"temp": 72}, False),
|
|
279
|
+
... (call2, "Error", True),
|
|
280
|
+
... ]
|
|
281
|
+
>>> user_msg = adapter.format_user_message_with_results(results)
|
|
282
|
+
>>> messages.append(user_msg)
|
|
283
|
+
"""
|
|
284
|
+
content = [
|
|
285
|
+
self.format_tool_result(tc, result, is_error)
|
|
286
|
+
for tc, result, is_error in results
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
"role": "user",
|
|
291
|
+
"content": content,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
def format_assistant_message(
|
|
295
|
+
self,
|
|
296
|
+
text_content: str | None,
|
|
297
|
+
tool_calls: list[UnifiedToolCall],
|
|
298
|
+
) -> dict[str, Any]:
|
|
299
|
+
"""
|
|
300
|
+
Format an assistant message with tool use blocks.
|
|
301
|
+
|
|
302
|
+
Useful for reconstructing conversation history.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
text_content: Text content of the message.
|
|
306
|
+
tool_calls: List of tool calls to include.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Dictionary suitable for Anthropic messages list.
|
|
310
|
+
"""
|
|
311
|
+
content = []
|
|
312
|
+
|
|
313
|
+
if text_content:
|
|
314
|
+
content.append({
|
|
315
|
+
"type": "text",
|
|
316
|
+
"text": text_content,
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
for tc in tool_calls:
|
|
320
|
+
content.append({
|
|
321
|
+
"type": "tool_use",
|
|
322
|
+
"id": tc.id,
|
|
323
|
+
"name": tc.name,
|
|
324
|
+
"input": tc.arguments,
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
"role": "assistant",
|
|
329
|
+
"content": content,
|
|
330
|
+
}
|