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.
Files changed (94) hide show
  1. proxilion/__init__.py +136 -0
  2. proxilion/audit/__init__.py +133 -0
  3. proxilion/audit/base_exporters.py +527 -0
  4. proxilion/audit/compliance/__init__.py +130 -0
  5. proxilion/audit/compliance/base.py +457 -0
  6. proxilion/audit/compliance/eu_ai_act.py +603 -0
  7. proxilion/audit/compliance/iso27001.py +544 -0
  8. proxilion/audit/compliance/soc2.py +491 -0
  9. proxilion/audit/events.py +493 -0
  10. proxilion/audit/explainability.py +1173 -0
  11. proxilion/audit/exporters/__init__.py +58 -0
  12. proxilion/audit/exporters/aws_s3.py +636 -0
  13. proxilion/audit/exporters/azure_storage.py +608 -0
  14. proxilion/audit/exporters/cloud_base.py +468 -0
  15. proxilion/audit/exporters/gcp_storage.py +570 -0
  16. proxilion/audit/exporters/multi_exporter.py +498 -0
  17. proxilion/audit/hash_chain.py +652 -0
  18. proxilion/audit/logger.py +543 -0
  19. proxilion/caching/__init__.py +49 -0
  20. proxilion/caching/tool_cache.py +633 -0
  21. proxilion/context/__init__.py +73 -0
  22. proxilion/context/context_window.py +556 -0
  23. proxilion/context/message_history.py +505 -0
  24. proxilion/context/session.py +735 -0
  25. proxilion/contrib/__init__.py +51 -0
  26. proxilion/contrib/anthropic.py +609 -0
  27. proxilion/contrib/google.py +1012 -0
  28. proxilion/contrib/langchain.py +641 -0
  29. proxilion/contrib/mcp.py +893 -0
  30. proxilion/contrib/openai.py +646 -0
  31. proxilion/core.py +3058 -0
  32. proxilion/decorators.py +966 -0
  33. proxilion/engines/__init__.py +287 -0
  34. proxilion/engines/base.py +266 -0
  35. proxilion/engines/casbin_engine.py +412 -0
  36. proxilion/engines/opa_engine.py +493 -0
  37. proxilion/engines/simple.py +437 -0
  38. proxilion/exceptions.py +887 -0
  39. proxilion/guards/__init__.py +54 -0
  40. proxilion/guards/input_guard.py +522 -0
  41. proxilion/guards/output_guard.py +634 -0
  42. proxilion/observability/__init__.py +198 -0
  43. proxilion/observability/cost_tracker.py +866 -0
  44. proxilion/observability/hooks.py +683 -0
  45. proxilion/observability/metrics.py +798 -0
  46. proxilion/observability/session_cost_tracker.py +1063 -0
  47. proxilion/policies/__init__.py +67 -0
  48. proxilion/policies/base.py +304 -0
  49. proxilion/policies/builtin.py +486 -0
  50. proxilion/policies/registry.py +376 -0
  51. proxilion/providers/__init__.py +201 -0
  52. proxilion/providers/adapter.py +468 -0
  53. proxilion/providers/anthropic_adapter.py +330 -0
  54. proxilion/providers/gemini_adapter.py +391 -0
  55. proxilion/providers/openai_adapter.py +294 -0
  56. proxilion/py.typed +0 -0
  57. proxilion/resilience/__init__.py +81 -0
  58. proxilion/resilience/degradation.py +615 -0
  59. proxilion/resilience/fallback.py +555 -0
  60. proxilion/resilience/retry.py +554 -0
  61. proxilion/scheduling/__init__.py +57 -0
  62. proxilion/scheduling/priority_queue.py +419 -0
  63. proxilion/scheduling/scheduler.py +459 -0
  64. proxilion/security/__init__.py +244 -0
  65. proxilion/security/agent_trust.py +968 -0
  66. proxilion/security/behavioral_drift.py +794 -0
  67. proxilion/security/cascade_protection.py +869 -0
  68. proxilion/security/circuit_breaker.py +428 -0
  69. proxilion/security/cost_limiter.py +690 -0
  70. proxilion/security/idor_protection.py +460 -0
  71. proxilion/security/intent_capsule.py +849 -0
  72. proxilion/security/intent_validator.py +495 -0
  73. proxilion/security/memory_integrity.py +767 -0
  74. proxilion/security/rate_limiter.py +509 -0
  75. proxilion/security/scope_enforcer.py +680 -0
  76. proxilion/security/sequence_validator.py +636 -0
  77. proxilion/security/trust_boundaries.py +784 -0
  78. proxilion/streaming/__init__.py +70 -0
  79. proxilion/streaming/detector.py +761 -0
  80. proxilion/streaming/transformer.py +674 -0
  81. proxilion/timeouts/__init__.py +55 -0
  82. proxilion/timeouts/decorators.py +477 -0
  83. proxilion/timeouts/manager.py +545 -0
  84. proxilion/tools/__init__.py +69 -0
  85. proxilion/tools/decorators.py +493 -0
  86. proxilion/tools/registry.py +732 -0
  87. proxilion/types.py +339 -0
  88. proxilion/validation/__init__.py +93 -0
  89. proxilion/validation/pydantic_schema.py +351 -0
  90. proxilion/validation/schema.py +651 -0
  91. proxilion-0.0.1.dist-info/METADATA +872 -0
  92. proxilion-0.0.1.dist-info/RECORD +94 -0
  93. proxilion-0.0.1.dist-info/WHEEL +4 -0
  94. 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
+ }