axonflow 0.4.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.
- axonflow/__init__.py +140 -0
- axonflow/client.py +1612 -0
- axonflow/exceptions.py +103 -0
- axonflow/interceptors/__init__.py +20 -0
- axonflow/interceptors/anthropic.py +184 -0
- axonflow/interceptors/base.py +58 -0
- axonflow/interceptors/bedrock.py +231 -0
- axonflow/interceptors/gemini.py +281 -0
- axonflow/interceptors/ollama.py +253 -0
- axonflow/interceptors/openai.py +160 -0
- axonflow/policies.py +289 -0
- axonflow/py.typed +0 -0
- axonflow/types.py +214 -0
- axonflow/utils/__init__.py +12 -0
- axonflow/utils/cache.py +102 -0
- axonflow/utils/logging.py +89 -0
- axonflow/utils/retry.py +111 -0
- axonflow-0.4.0.dist-info/METADATA +316 -0
- axonflow-0.4.0.dist-info/RECORD +22 -0
- axonflow-0.4.0.dist-info/WHEEL +5 -0
- axonflow-0.4.0.dist-info/licenses/LICENSE +21 -0
- axonflow-0.4.0.dist-info/top_level.txt +1 -0
axonflow/policies.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""AxonFlow SDK Policy Types and Methods.
|
|
2
|
+
|
|
3
|
+
Policy CRUD types and methods for the Unified Policy Architecture v2.0.0.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
# ============================================================================
|
|
15
|
+
# Policy Categories and Tiers
|
|
16
|
+
# ============================================================================
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PolicyCategory(str, Enum):
|
|
20
|
+
"""Policy categories for organization and filtering."""
|
|
21
|
+
|
|
22
|
+
SECURITY_SQLI = "security-sqli"
|
|
23
|
+
SECURITY_ADMIN = "security-admin"
|
|
24
|
+
PII_GLOBAL = "pii-global"
|
|
25
|
+
PII_US = "pii-us"
|
|
26
|
+
PII_EU = "pii-eu"
|
|
27
|
+
PII_INDIA = "pii-india"
|
|
28
|
+
DYNAMIC_RISK = "dynamic-risk"
|
|
29
|
+
DYNAMIC_COMPLIANCE = "dynamic-compliance"
|
|
30
|
+
DYNAMIC_SECURITY = "dynamic-security"
|
|
31
|
+
DYNAMIC_COST = "dynamic-cost"
|
|
32
|
+
DYNAMIC_ACCESS = "dynamic-access"
|
|
33
|
+
CUSTOM = "custom"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PolicyTier(str, Enum):
|
|
37
|
+
"""Policy tiers determine where policies apply."""
|
|
38
|
+
|
|
39
|
+
SYSTEM = "system"
|
|
40
|
+
ORGANIZATION = "organization"
|
|
41
|
+
TENANT = "tenant"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class OverrideAction(str, Enum):
|
|
45
|
+
"""Override action for policy overrides."""
|
|
46
|
+
|
|
47
|
+
BLOCK = "block"
|
|
48
|
+
WARN = "warn"
|
|
49
|
+
LOG = "log"
|
|
50
|
+
REDACT = "redact"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PolicyAction(str, Enum):
|
|
54
|
+
"""Action to take when policy matches."""
|
|
55
|
+
|
|
56
|
+
BLOCK = "block"
|
|
57
|
+
WARN = "warn"
|
|
58
|
+
LOG = "log"
|
|
59
|
+
REDACT = "redact"
|
|
60
|
+
ALLOW = "allow"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PolicySeverity(str, Enum):
|
|
64
|
+
"""Policy severity levels."""
|
|
65
|
+
|
|
66
|
+
CRITICAL = "critical"
|
|
67
|
+
HIGH = "high"
|
|
68
|
+
MEDIUM = "medium"
|
|
69
|
+
LOW = "low"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ============================================================================
|
|
73
|
+
# Static Policy Types
|
|
74
|
+
# ============================================================================
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class PolicyOverride(BaseModel):
|
|
78
|
+
"""Policy override configuration."""
|
|
79
|
+
|
|
80
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
81
|
+
|
|
82
|
+
policy_id: str = Field(..., alias="policyId")
|
|
83
|
+
action: OverrideAction
|
|
84
|
+
reason: str
|
|
85
|
+
created_by: str | None = Field(default=None, alias="createdBy")
|
|
86
|
+
created_at: datetime = Field(..., alias="createdAt")
|
|
87
|
+
expires_at: datetime | None = Field(default=None, alias="expiresAt")
|
|
88
|
+
active: bool = True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class StaticPolicy(BaseModel):
|
|
92
|
+
"""Static policy definition."""
|
|
93
|
+
|
|
94
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
95
|
+
|
|
96
|
+
id: str
|
|
97
|
+
name: str
|
|
98
|
+
description: str | None = None
|
|
99
|
+
category: PolicyCategory
|
|
100
|
+
tier: PolicyTier
|
|
101
|
+
pattern: str
|
|
102
|
+
severity: PolicySeverity = PolicySeverity.MEDIUM
|
|
103
|
+
enabled: bool = True
|
|
104
|
+
action: PolicyAction = PolicyAction.BLOCK
|
|
105
|
+
organization_id: str | None = Field(default=None, alias="organizationId")
|
|
106
|
+
tenant_id: str | None = Field(default=None, alias="tenantId")
|
|
107
|
+
created_at: datetime = Field(..., alias="createdAt")
|
|
108
|
+
updated_at: datetime = Field(..., alias="updatedAt")
|
|
109
|
+
version: int | None = None
|
|
110
|
+
has_override: bool | None = Field(default=None, alias="hasOverride")
|
|
111
|
+
override: PolicyOverride | None = None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ListStaticPoliciesOptions(BaseModel):
|
|
115
|
+
"""Options for listing static policies."""
|
|
116
|
+
|
|
117
|
+
category: PolicyCategory | None = None
|
|
118
|
+
tier: PolicyTier | None = None
|
|
119
|
+
enabled: bool | None = None
|
|
120
|
+
limit: int | None = Field(default=None, ge=1)
|
|
121
|
+
offset: int | None = Field(default=None, ge=0)
|
|
122
|
+
sort_by: str | None = None
|
|
123
|
+
sort_order: str | None = None
|
|
124
|
+
search: str | None = None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class CreateStaticPolicyRequest(BaseModel):
|
|
128
|
+
"""Request to create a new static policy."""
|
|
129
|
+
|
|
130
|
+
name: str = Field(..., min_length=1)
|
|
131
|
+
description: str | None = None
|
|
132
|
+
category: PolicyCategory
|
|
133
|
+
tier: PolicyTier = PolicyTier.TENANT # Default to tenant tier for custom policies
|
|
134
|
+
pattern: str = Field(..., min_length=1)
|
|
135
|
+
severity: PolicySeverity = PolicySeverity.MEDIUM
|
|
136
|
+
enabled: bool = True
|
|
137
|
+
action: PolicyAction = PolicyAction.BLOCK
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class UpdateStaticPolicyRequest(BaseModel):
|
|
141
|
+
"""Request to update an existing static policy."""
|
|
142
|
+
|
|
143
|
+
name: str | None = None
|
|
144
|
+
description: str | None = None
|
|
145
|
+
category: PolicyCategory | None = None
|
|
146
|
+
pattern: str | None = None
|
|
147
|
+
severity: PolicySeverity | None = None
|
|
148
|
+
enabled: bool | None = None
|
|
149
|
+
action: PolicyAction | None = None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class CreatePolicyOverrideRequest(BaseModel):
|
|
153
|
+
"""Request to create a policy override."""
|
|
154
|
+
|
|
155
|
+
action: OverrideAction
|
|
156
|
+
reason: str = Field(..., min_length=1)
|
|
157
|
+
expires_at: datetime | None = Field(default=None, alias="expiresAt")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ============================================================================
|
|
161
|
+
# Dynamic Policy Types
|
|
162
|
+
# ============================================================================
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class DynamicPolicyCondition(BaseModel):
|
|
166
|
+
"""Condition for dynamic policy evaluation."""
|
|
167
|
+
|
|
168
|
+
field: str
|
|
169
|
+
operator: str
|
|
170
|
+
value: Any
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class DynamicPolicyConfig(BaseModel):
|
|
174
|
+
"""Configuration for a dynamic policy."""
|
|
175
|
+
|
|
176
|
+
type: str
|
|
177
|
+
rules: dict[str, Any] = Field(default_factory=dict)
|
|
178
|
+
conditions: list[DynamicPolicyCondition] | None = None
|
|
179
|
+
action: PolicyAction
|
|
180
|
+
parameters: dict[str, Any] | None = None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class DynamicPolicy(BaseModel):
|
|
184
|
+
"""Dynamic policy definition."""
|
|
185
|
+
|
|
186
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
187
|
+
|
|
188
|
+
id: str
|
|
189
|
+
name: str
|
|
190
|
+
description: str | None = None
|
|
191
|
+
category: PolicyCategory
|
|
192
|
+
tier: PolicyTier
|
|
193
|
+
enabled: bool = True
|
|
194
|
+
organization_id: str | None = Field(default=None, alias="organizationId")
|
|
195
|
+
tenant_id: str | None = Field(default=None, alias="tenantId")
|
|
196
|
+
config: DynamicPolicyConfig
|
|
197
|
+
created_at: datetime = Field(..., alias="createdAt")
|
|
198
|
+
updated_at: datetime = Field(..., alias="updatedAt")
|
|
199
|
+
version: int | None = None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ListDynamicPoliciesOptions(BaseModel):
|
|
203
|
+
"""Options for listing dynamic policies."""
|
|
204
|
+
|
|
205
|
+
category: PolicyCategory | None = None
|
|
206
|
+
tier: PolicyTier | None = None
|
|
207
|
+
enabled: bool | None = None
|
|
208
|
+
limit: int | None = Field(default=None, ge=1)
|
|
209
|
+
offset: int | None = Field(default=None, ge=0)
|
|
210
|
+
sort_by: str | None = None
|
|
211
|
+
sort_order: str | None = None
|
|
212
|
+
search: str | None = None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class CreateDynamicPolicyRequest(BaseModel):
|
|
216
|
+
"""Request to create a dynamic policy."""
|
|
217
|
+
|
|
218
|
+
name: str = Field(..., min_length=1)
|
|
219
|
+
description: str | None = None
|
|
220
|
+
category: PolicyCategory
|
|
221
|
+
config: DynamicPolicyConfig
|
|
222
|
+
enabled: bool = True
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class UpdateDynamicPolicyRequest(BaseModel):
|
|
226
|
+
"""Request to update a dynamic policy."""
|
|
227
|
+
|
|
228
|
+
name: str | None = None
|
|
229
|
+
description: str | None = None
|
|
230
|
+
category: PolicyCategory | None = None
|
|
231
|
+
config: DynamicPolicyConfig | None = None
|
|
232
|
+
enabled: bool | None = None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ============================================================================
|
|
236
|
+
# Pattern Testing Types
|
|
237
|
+
# ============================================================================
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class TestPatternMatch(BaseModel):
|
|
241
|
+
"""Individual pattern match result."""
|
|
242
|
+
|
|
243
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
244
|
+
|
|
245
|
+
input: str
|
|
246
|
+
matched: bool
|
|
247
|
+
groups: list[str] | None = None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class TestPatternResult(BaseModel):
|
|
251
|
+
"""Result of testing a regex pattern."""
|
|
252
|
+
|
|
253
|
+
valid: bool
|
|
254
|
+
error: str | None = None
|
|
255
|
+
pattern: str = ""
|
|
256
|
+
inputs: list[str] = Field(default_factory=list)
|
|
257
|
+
matches: list[TestPatternMatch] = Field(default_factory=list)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ============================================================================
|
|
261
|
+
# Policy Version Types
|
|
262
|
+
# ============================================================================
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class PolicyVersion(BaseModel):
|
|
266
|
+
"""Policy version history entry."""
|
|
267
|
+
|
|
268
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
269
|
+
|
|
270
|
+
version: int
|
|
271
|
+
changed_by: str | None = Field(default=None, alias="changedBy")
|
|
272
|
+
changed_at: datetime = Field(..., alias="changedAt")
|
|
273
|
+
change_type: str = Field(..., alias="changeType")
|
|
274
|
+
change_description: str | None = Field(default=None, alias="changeDescription")
|
|
275
|
+
previous_values: dict[str, Any] | None = Field(default=None, alias="previousValues")
|
|
276
|
+
new_values: dict[str, Any] | None = Field(default=None, alias="newValues")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# ============================================================================
|
|
280
|
+
# Effective Policies Types
|
|
281
|
+
# ============================================================================
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class EffectivePoliciesOptions(BaseModel):
|
|
285
|
+
"""Options for getting effective policies."""
|
|
286
|
+
|
|
287
|
+
category: PolicyCategory | None = None
|
|
288
|
+
include_disabled: bool = False
|
|
289
|
+
include_overridden: bool = False
|
axonflow/py.typed
ADDED
|
File without changes
|
axonflow/types.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""AxonFlow SDK Type Definitions.
|
|
2
|
+
|
|
3
|
+
All types are defined using Pydantic v2 for runtime validation
|
|
4
|
+
and automatic JSON serialization/deserialization.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Mode(str, Enum):
|
|
17
|
+
"""SDK operation mode."""
|
|
18
|
+
|
|
19
|
+
PRODUCTION = "production"
|
|
20
|
+
SANDBOX = "sandbox"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RetryConfig(BaseModel):
|
|
24
|
+
"""Retry configuration with exponential backoff."""
|
|
25
|
+
|
|
26
|
+
model_config = ConfigDict(frozen=True)
|
|
27
|
+
|
|
28
|
+
enabled: bool = Field(default=True, description="Enable retry logic")
|
|
29
|
+
max_attempts: int = Field(default=3, ge=1, le=10, description="Max retry attempts")
|
|
30
|
+
initial_delay: float = Field(default=1.0, gt=0, description="Initial delay (seconds)")
|
|
31
|
+
max_delay: float = Field(default=30.0, gt=0, description="Max delay (seconds)")
|
|
32
|
+
exponential_base: float = Field(default=2.0, gt=1, description="Backoff multiplier")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CacheConfig(BaseModel):
|
|
36
|
+
"""Cache configuration."""
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(frozen=True)
|
|
39
|
+
|
|
40
|
+
enabled: bool = Field(default=True, description="Enable caching")
|
|
41
|
+
ttl: float = Field(default=60.0, gt=0, description="Cache TTL (seconds)")
|
|
42
|
+
max_size: int = Field(default=1000, gt=0, description="Max cache entries")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AxonFlowConfig(BaseModel):
|
|
46
|
+
"""Configuration for AxonFlow client.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
agent_url: AxonFlow Agent URL (required)
|
|
50
|
+
client_id: Client ID for authentication (required)
|
|
51
|
+
client_secret: Client secret for authentication (required)
|
|
52
|
+
license_key: Optional license key for organization-level auth
|
|
53
|
+
mode: Operation mode (production or sandbox)
|
|
54
|
+
debug: Enable debug logging
|
|
55
|
+
timeout: Request timeout in seconds
|
|
56
|
+
insecure_skip_verify: Skip TLS verification (dev only)
|
|
57
|
+
retry: Retry configuration
|
|
58
|
+
cache: Cache configuration
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(frozen=True)
|
|
62
|
+
|
|
63
|
+
agent_url: str = Field(..., min_length=1, description="AxonFlow Agent URL")
|
|
64
|
+
client_id: str = Field(..., min_length=1, description="Client ID")
|
|
65
|
+
client_secret: str = Field(..., min_length=1, description="Client secret")
|
|
66
|
+
license_key: str | None = Field(default=None, description="License key")
|
|
67
|
+
mode: Mode = Field(default=Mode.PRODUCTION, description="Operation mode")
|
|
68
|
+
debug: bool = Field(default=False, description="Enable debug logging")
|
|
69
|
+
timeout: float = Field(default=60.0, gt=0, description="Request timeout (seconds)")
|
|
70
|
+
map_timeout: float = Field(default=120.0, gt=0, description="MAP operations timeout (seconds)")
|
|
71
|
+
insecure_skip_verify: bool = Field(default=False, description="Skip TLS verify")
|
|
72
|
+
retry: RetryConfig = Field(default_factory=RetryConfig)
|
|
73
|
+
cache: CacheConfig = Field(default_factory=CacheConfig)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ClientRequest(BaseModel):
|
|
77
|
+
"""Request to AxonFlow Agent."""
|
|
78
|
+
|
|
79
|
+
query: str = Field(..., description="Query or prompt")
|
|
80
|
+
user_token: str = Field(..., description="User token for auth")
|
|
81
|
+
client_id: str = Field(..., description="Client ID")
|
|
82
|
+
request_type: str = Field(..., description="Request type")
|
|
83
|
+
context: dict[str, Any] = Field(default_factory=dict, description="Additional context")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PolicyEvaluationInfo(BaseModel):
|
|
87
|
+
"""Policy evaluation metadata."""
|
|
88
|
+
|
|
89
|
+
policies_evaluated: list[str] = Field(default_factory=list)
|
|
90
|
+
static_checks: list[str] = Field(default_factory=list)
|
|
91
|
+
processing_time: str = Field(default="0ms")
|
|
92
|
+
tenant_id: str = Field(default="")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ClientResponse(BaseModel):
|
|
96
|
+
"""Response from AxonFlow Agent."""
|
|
97
|
+
|
|
98
|
+
success: bool = Field(..., description="Whether request succeeded")
|
|
99
|
+
data: Any | None = Field(default=None, description="Response data")
|
|
100
|
+
result: str | None = Field(default=None, description="Result for planning")
|
|
101
|
+
plan_id: str | None = Field(default=None, description="Plan ID if applicable")
|
|
102
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
103
|
+
error: str | None = Field(default=None, description="Error message if failed")
|
|
104
|
+
blocked: bool = Field(default=False, description="Whether request was blocked")
|
|
105
|
+
block_reason: str | None = Field(default=None, description="Block reason")
|
|
106
|
+
policy_info: PolicyEvaluationInfo | None = Field(default=None)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ConnectorMetadata(BaseModel):
|
|
110
|
+
"""MCP connector metadata."""
|
|
111
|
+
|
|
112
|
+
id: str
|
|
113
|
+
name: str
|
|
114
|
+
type: str
|
|
115
|
+
version: str = ""
|
|
116
|
+
description: str = ""
|
|
117
|
+
category: str = ""
|
|
118
|
+
icon: str = ""
|
|
119
|
+
tags: list[str] = Field(default_factory=list)
|
|
120
|
+
capabilities: list[str] = Field(default_factory=list)
|
|
121
|
+
config_schema: dict[str, Any] = Field(default_factory=dict)
|
|
122
|
+
installed: bool = False
|
|
123
|
+
healthy: bool = False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ConnectorInstallRequest(BaseModel):
|
|
127
|
+
"""Request to install an MCP connector."""
|
|
128
|
+
|
|
129
|
+
connector_id: str
|
|
130
|
+
name: str
|
|
131
|
+
tenant_id: str
|
|
132
|
+
options: dict[str, Any] = Field(default_factory=dict)
|
|
133
|
+
credentials: dict[str, str] = Field(default_factory=dict)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ConnectorResponse(BaseModel):
|
|
137
|
+
"""Response from MCP connector query."""
|
|
138
|
+
|
|
139
|
+
success: bool
|
|
140
|
+
data: Any | None = None
|
|
141
|
+
error: str | None = None
|
|
142
|
+
meta: dict[str, Any] = Field(default_factory=dict)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class PlanStep(BaseModel):
|
|
146
|
+
"""A step in a multi-agent plan."""
|
|
147
|
+
|
|
148
|
+
id: str
|
|
149
|
+
name: str
|
|
150
|
+
type: str
|
|
151
|
+
description: str = ""
|
|
152
|
+
depends_on: list[str] = Field(default_factory=list)
|
|
153
|
+
agent: str = ""
|
|
154
|
+
parameters: dict[str, Any] = Field(default_factory=dict)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class PlanResponse(BaseModel):
|
|
158
|
+
"""Multi-agent plan response."""
|
|
159
|
+
|
|
160
|
+
plan_id: str
|
|
161
|
+
steps: list[PlanStep] = Field(default_factory=list)
|
|
162
|
+
domain: str = "generic"
|
|
163
|
+
complexity: int = 0
|
|
164
|
+
parallel: bool = False
|
|
165
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class PlanExecutionResponse(BaseModel):
|
|
169
|
+
"""Plan execution result."""
|
|
170
|
+
|
|
171
|
+
plan_id: str
|
|
172
|
+
status: str # "running", "completed", "failed"
|
|
173
|
+
result: str | None = None
|
|
174
|
+
step_results: dict[str, Any] = Field(default_factory=dict)
|
|
175
|
+
error: str | None = None
|
|
176
|
+
duration: str | None = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Gateway Mode Types
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class RateLimitInfo(BaseModel):
|
|
183
|
+
"""Rate limiting status."""
|
|
184
|
+
|
|
185
|
+
limit: int
|
|
186
|
+
remaining: int
|
|
187
|
+
reset_at: datetime
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class PolicyApprovalResult(BaseModel):
|
|
191
|
+
"""Pre-check result from Gateway Mode."""
|
|
192
|
+
|
|
193
|
+
context_id: str = Field(..., description="Context ID for audit linking")
|
|
194
|
+
approved: bool = Field(..., description="Whether request is approved")
|
|
195
|
+
approved_data: dict[str, Any] = Field(default_factory=dict)
|
|
196
|
+
policies: list[str] = Field(default_factory=list)
|
|
197
|
+
rate_limit_info: RateLimitInfo | None = None
|
|
198
|
+
expires_at: datetime
|
|
199
|
+
block_reason: str | None = None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class TokenUsage(BaseModel):
|
|
203
|
+
"""LLM token usage tracking."""
|
|
204
|
+
|
|
205
|
+
prompt_tokens: int = Field(ge=0)
|
|
206
|
+
completion_tokens: int = Field(ge=0)
|
|
207
|
+
total_tokens: int = Field(ge=0)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class AuditResult(BaseModel):
|
|
211
|
+
"""Audit confirmation."""
|
|
212
|
+
|
|
213
|
+
success: bool
|
|
214
|
+
audit_id: str
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""AxonFlow SDK Utilities."""
|
|
2
|
+
|
|
3
|
+
from axonflow.utils.cache import CacheManager
|
|
4
|
+
from axonflow.utils.logging import configure_logging, get_logger
|
|
5
|
+
from axonflow.utils.retry import RetryHandler
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CacheManager",
|
|
9
|
+
"RetryHandler",
|
|
10
|
+
"configure_logging",
|
|
11
|
+
"get_logger",
|
|
12
|
+
]
|
axonflow/utils/cache.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Cache utilities for AxonFlow SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Generic, TypeVar
|
|
6
|
+
|
|
7
|
+
from cachetools import TTLCache
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CacheManager(Generic[T]):
|
|
13
|
+
"""Generic cache manager with TTL support.
|
|
14
|
+
|
|
15
|
+
Provides a simple interface for caching with automatic expiration.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, maxsize: int = 1000, ttl: float = 60.0) -> None:
|
|
19
|
+
"""Initialize cache manager.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
maxsize: Maximum number of entries
|
|
23
|
+
ttl: Time-to-live in seconds
|
|
24
|
+
"""
|
|
25
|
+
self._cache: TTLCache[str, T] = TTLCache(maxsize=maxsize, ttl=ttl)
|
|
26
|
+
self._ttl = ttl
|
|
27
|
+
self._maxsize = maxsize
|
|
28
|
+
|
|
29
|
+
def get(self, key: str) -> T | None:
|
|
30
|
+
"""Get a value from cache.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
key: Cache key
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Cached value or None if not found
|
|
37
|
+
"""
|
|
38
|
+
result: T | None = self._cache.get(key)
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
def set(self, key: str, value: T) -> None:
|
|
42
|
+
"""Set a value in cache.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
key: Cache key
|
|
46
|
+
value: Value to cache
|
|
47
|
+
"""
|
|
48
|
+
self._cache[key] = value
|
|
49
|
+
|
|
50
|
+
def delete(self, key: str) -> None:
|
|
51
|
+
"""Delete a value from cache.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
key: Cache key
|
|
55
|
+
"""
|
|
56
|
+
self._cache.pop(key, None)
|
|
57
|
+
|
|
58
|
+
def clear(self) -> None:
|
|
59
|
+
"""Clear all cached values."""
|
|
60
|
+
self._cache.clear()
|
|
61
|
+
|
|
62
|
+
def contains(self, key: str) -> bool:
|
|
63
|
+
"""Check if key exists in cache.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
key: Cache key
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if key exists
|
|
70
|
+
"""
|
|
71
|
+
return key in self._cache
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def size(self) -> int:
|
|
75
|
+
"""Get current cache size."""
|
|
76
|
+
return len(self._cache)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def ttl(self) -> float:
|
|
80
|
+
"""Get cache TTL."""
|
|
81
|
+
return self._ttl
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def maxsize(self) -> int:
|
|
85
|
+
"""Get maximum cache size."""
|
|
86
|
+
return self._maxsize
|
|
87
|
+
|
|
88
|
+
def get_or_set(self, key: str, factory: Any) -> T:
|
|
89
|
+
"""Get from cache or create using factory.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
key: Cache key
|
|
93
|
+
factory: Callable to create value if not cached
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Cached or newly created value
|
|
97
|
+
"""
|
|
98
|
+
value = self.get(key)
|
|
99
|
+
if value is None:
|
|
100
|
+
value = factory()
|
|
101
|
+
self.set(key, value)
|
|
102
|
+
return value
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Logging utilities for AxonFlow SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def configure_logging(
|
|
13
|
+
level: int = logging.INFO,
|
|
14
|
+
json_format: bool = False,
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Configure structured logging for AxonFlow SDK.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
level: Logging level (default: INFO)
|
|
20
|
+
json_format: Use JSON format for logs (default: False)
|
|
21
|
+
"""
|
|
22
|
+
# Configure structlog processors
|
|
23
|
+
processors: list[Any] = [
|
|
24
|
+
structlog.stdlib.filter_by_level,
|
|
25
|
+
structlog.stdlib.add_logger_name,
|
|
26
|
+
structlog.stdlib.add_log_level,
|
|
27
|
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
28
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
29
|
+
structlog.processors.StackInfoRenderer(),
|
|
30
|
+
structlog.processors.UnicodeDecoder(),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
if json_format:
|
|
34
|
+
processors.append(structlog.processors.JSONRenderer())
|
|
35
|
+
else:
|
|
36
|
+
processors.append(structlog.dev.ConsoleRenderer())
|
|
37
|
+
|
|
38
|
+
structlog.configure(
|
|
39
|
+
processors=processors,
|
|
40
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
41
|
+
context_class=dict,
|
|
42
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
43
|
+
cache_logger_on_first_use=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Configure stdlib logging
|
|
47
|
+
logging.basicConfig(
|
|
48
|
+
format="%(message)s",
|
|
49
|
+
stream=sys.stdout,
|
|
50
|
+
level=level,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_logger(name: str) -> structlog.stdlib.BoundLogger:
|
|
55
|
+
"""Get a structured logger.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: Logger name
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Configured structured logger
|
|
62
|
+
"""
|
|
63
|
+
logger: structlog.stdlib.BoundLogger = structlog.get_logger(name)
|
|
64
|
+
return logger
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class LogContext:
|
|
68
|
+
"""Context manager for adding log context."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, logger: structlog.stdlib.BoundLogger, **context: Any) -> None:
|
|
71
|
+
"""Initialize log context.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
logger: Logger to bind context to
|
|
75
|
+
**context: Context key-value pairs
|
|
76
|
+
"""
|
|
77
|
+
self._logger = logger
|
|
78
|
+
self._context = context
|
|
79
|
+
self._original_context: dict[str, Any] = {}
|
|
80
|
+
|
|
81
|
+
def __enter__(self) -> structlog.stdlib.BoundLogger:
|
|
82
|
+
"""Enter context and bind values."""
|
|
83
|
+
self._original_context = dict(getattr(self._logger, "_context", {}))
|
|
84
|
+
return self._logger.bind(**self._context)
|
|
85
|
+
|
|
86
|
+
def __exit__(self, *args: Any) -> None:
|
|
87
|
+
"""Exit context and restore original values."""
|
|
88
|
+
# Restore original context
|
|
89
|
+
self._logger._context = self._original_context # noqa: B010, SLF001
|