ai-lib-python 0.5.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 (84) hide show
  1. ai_lib_python/__init__.py +43 -0
  2. ai_lib_python/batch/__init__.py +15 -0
  3. ai_lib_python/batch/collector.py +244 -0
  4. ai_lib_python/batch/executor.py +224 -0
  5. ai_lib_python/cache/__init__.py +26 -0
  6. ai_lib_python/cache/backends.py +380 -0
  7. ai_lib_python/cache/key.py +237 -0
  8. ai_lib_python/cache/manager.py +332 -0
  9. ai_lib_python/client/__init__.py +37 -0
  10. ai_lib_python/client/builder.py +528 -0
  11. ai_lib_python/client/cancel.py +368 -0
  12. ai_lib_python/client/core.py +433 -0
  13. ai_lib_python/client/response.py +134 -0
  14. ai_lib_python/embeddings/__init__.py +36 -0
  15. ai_lib_python/embeddings/client.py +339 -0
  16. ai_lib_python/embeddings/types.py +234 -0
  17. ai_lib_python/embeddings/vectors.py +246 -0
  18. ai_lib_python/errors/__init__.py +41 -0
  19. ai_lib_python/errors/base.py +316 -0
  20. ai_lib_python/errors/classification.py +210 -0
  21. ai_lib_python/guardrails/__init__.py +35 -0
  22. ai_lib_python/guardrails/base.py +336 -0
  23. ai_lib_python/guardrails/filters.py +583 -0
  24. ai_lib_python/guardrails/validators.py +475 -0
  25. ai_lib_python/pipeline/__init__.py +55 -0
  26. ai_lib_python/pipeline/accumulate.py +248 -0
  27. ai_lib_python/pipeline/base.py +240 -0
  28. ai_lib_python/pipeline/decode.py +281 -0
  29. ai_lib_python/pipeline/event_map.py +506 -0
  30. ai_lib_python/pipeline/fan_out.py +284 -0
  31. ai_lib_python/pipeline/select.py +297 -0
  32. ai_lib_python/plugins/__init__.py +32 -0
  33. ai_lib_python/plugins/base.py +294 -0
  34. ai_lib_python/plugins/hooks.py +296 -0
  35. ai_lib_python/plugins/middleware.py +285 -0
  36. ai_lib_python/plugins/registry.py +294 -0
  37. ai_lib_python/protocol/__init__.py +71 -0
  38. ai_lib_python/protocol/loader.py +317 -0
  39. ai_lib_python/protocol/manifest.py +385 -0
  40. ai_lib_python/protocol/validator.py +460 -0
  41. ai_lib_python/py.typed +1 -0
  42. ai_lib_python/resilience/__init__.py +102 -0
  43. ai_lib_python/resilience/backpressure.py +225 -0
  44. ai_lib_python/resilience/circuit_breaker.py +318 -0
  45. ai_lib_python/resilience/executor.py +343 -0
  46. ai_lib_python/resilience/fallback.py +341 -0
  47. ai_lib_python/resilience/preflight.py +413 -0
  48. ai_lib_python/resilience/rate_limiter.py +291 -0
  49. ai_lib_python/resilience/retry.py +299 -0
  50. ai_lib_python/resilience/signals.py +283 -0
  51. ai_lib_python/routing/__init__.py +118 -0
  52. ai_lib_python/routing/manager.py +593 -0
  53. ai_lib_python/routing/strategy.py +345 -0
  54. ai_lib_python/routing/types.py +397 -0
  55. ai_lib_python/structured/__init__.py +33 -0
  56. ai_lib_python/structured/json_mode.py +281 -0
  57. ai_lib_python/structured/schema.py +316 -0
  58. ai_lib_python/structured/validator.py +334 -0
  59. ai_lib_python/telemetry/__init__.py +127 -0
  60. ai_lib_python/telemetry/exporters/__init__.py +9 -0
  61. ai_lib_python/telemetry/exporters/prometheus.py +111 -0
  62. ai_lib_python/telemetry/feedback.py +446 -0
  63. ai_lib_python/telemetry/health.py +409 -0
  64. ai_lib_python/telemetry/logger.py +389 -0
  65. ai_lib_python/telemetry/metrics.py +496 -0
  66. ai_lib_python/telemetry/tracer.py +473 -0
  67. ai_lib_python/tokens/__init__.py +25 -0
  68. ai_lib_python/tokens/counter.py +282 -0
  69. ai_lib_python/tokens/estimator.py +286 -0
  70. ai_lib_python/transport/__init__.py +34 -0
  71. ai_lib_python/transport/auth.py +141 -0
  72. ai_lib_python/transport/http.py +364 -0
  73. ai_lib_python/transport/pool.py +425 -0
  74. ai_lib_python/types/__init__.py +41 -0
  75. ai_lib_python/types/events.py +343 -0
  76. ai_lib_python/types/message.py +332 -0
  77. ai_lib_python/types/tool.py +191 -0
  78. ai_lib_python/utils/__init__.py +21 -0
  79. ai_lib_python/utils/tool_call_assembler.py +317 -0
  80. ai_lib_python-0.5.0.dist-info/METADATA +837 -0
  81. ai_lib_python-0.5.0.dist-info/RECORD +84 -0
  82. ai_lib_python-0.5.0.dist-info/WHEEL +4 -0
  83. ai_lib_python-0.5.0.dist-info/licenses/LICENSE-APACHE +201 -0
  84. ai_lib_python-0.5.0.dist-info/licenses/LICENSE-MIT +21 -0
@@ -0,0 +1,191 @@
1
+ """
2
+ Tool types for function calling support.
3
+
4
+ Based on AI-Protocol standard_schema for tool definitions and calls.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import Enum
10
+ from typing import Any
11
+
12
+ from pydantic import BaseModel, ConfigDict, Field
13
+
14
+
15
+ class ToolChoice(str, Enum):
16
+ """Tool choice policy for requests."""
17
+
18
+ AUTO = "auto"
19
+ NONE = "none"
20
+ REQUIRED = "required"
21
+
22
+
23
+ class FunctionDefinition(BaseModel):
24
+ """Function definition within a tool.
25
+
26
+ Defines the schema for a callable function including:
27
+ - name: Function identifier
28
+ - description: What the function does
29
+ - parameters: JSON Schema for function parameters
30
+ """
31
+
32
+ model_config = ConfigDict(extra="allow")
33
+
34
+ name: str = Field(description="Function name")
35
+ description: str | None = Field(default=None, description="Function description")
36
+ parameters: dict[str, Any] = Field(
37
+ default_factory=lambda: {"type": "object", "properties": {}},
38
+ description="JSON Schema for parameters",
39
+ )
40
+ strict: bool | None = Field(
41
+ default=None,
42
+ description="Whether to enforce strict schema validation (provider-specific)",
43
+ )
44
+
45
+
46
+ class ToolDefinition(BaseModel):
47
+ """Tool definition for function calling.
48
+
49
+ Wraps a FunctionDefinition with the standard tool format.
50
+
51
+ Example:
52
+ >>> tool = ToolDefinition.from_function(
53
+ ... name="get_weather",
54
+ ... description="Get weather for a city",
55
+ ... parameters={
56
+ ... "type": "object",
57
+ ... "properties": {
58
+ ... "city": {"type": "string", "description": "City name"},
59
+ ... "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
60
+ ... },
61
+ ... "required": ["city"]
62
+ ... }
63
+ ... )
64
+ """
65
+
66
+ model_config = ConfigDict(extra="allow")
67
+
68
+ type: str = Field(default="function", description="Tool type")
69
+ function: FunctionDefinition = Field(description="Function definition")
70
+
71
+ @classmethod
72
+ def from_function(
73
+ cls,
74
+ name: str,
75
+ description: str | None = None,
76
+ parameters: dict[str, Any] | None = None,
77
+ strict: bool | None = None,
78
+ ) -> ToolDefinition:
79
+ """Create a tool definition from function details.
80
+
81
+ Args:
82
+ name: Function name
83
+ description: Function description
84
+ parameters: JSON Schema for parameters
85
+ strict: Whether to enforce strict validation
86
+
87
+ Returns:
88
+ ToolDefinition instance
89
+ """
90
+ func_def = FunctionDefinition(
91
+ name=name,
92
+ description=description,
93
+ parameters=parameters or {"type": "object", "properties": {}},
94
+ strict=strict,
95
+ )
96
+ return cls(function=func_def)
97
+
98
+ @property
99
+ def name(self) -> str:
100
+ """Get the function name."""
101
+ return self.function.name
102
+
103
+ @property
104
+ def description(self) -> str | None:
105
+ """Get the function description."""
106
+ return self.function.description
107
+
108
+
109
+ class ToolCall(BaseModel):
110
+ """A tool call from the model response.
111
+
112
+ Represents a request from the model to invoke a specific tool.
113
+
114
+ Attributes:
115
+ id: Unique identifier for this tool call
116
+ type: Tool type (typically "function")
117
+ function_name: Name of the function to call
118
+ arguments: Parsed arguments for the function
119
+
120
+ Example:
121
+ >>> tool_call = ToolCall(
122
+ ... id="call_abc123",
123
+ ... function_name="get_weather",
124
+ ... arguments={"city": "Beijing", "unit": "celsius"}
125
+ ... )
126
+ """
127
+
128
+ model_config = ConfigDict(extra="allow")
129
+
130
+ id: str = Field(description="Unique tool call identifier")
131
+ type: str = Field(default="function", description="Tool type")
132
+ function_name: str = Field(description="Name of the function to call")
133
+ arguments: dict[str, Any] = Field(
134
+ default_factory=dict,
135
+ description="Parsed function arguments",
136
+ )
137
+
138
+ # Raw arguments string (useful for debugging/logging)
139
+ arguments_raw: str | None = Field(
140
+ default=None,
141
+ description="Raw arguments string before parsing",
142
+ )
143
+
144
+ @classmethod
145
+ def from_openai_format(
146
+ cls,
147
+ id: str,
148
+ function_name: str,
149
+ arguments: str | dict[str, Any],
150
+ ) -> ToolCall:
151
+ """Create from OpenAI-style tool call format.
152
+
153
+ Args:
154
+ id: Tool call ID
155
+ function_name: Function name
156
+ arguments: Arguments (string or dict)
157
+
158
+ Returns:
159
+ ToolCall instance
160
+ """
161
+ import json
162
+
163
+ if isinstance(arguments, str):
164
+ try:
165
+ parsed_args = json.loads(arguments) if arguments else {}
166
+ except json.JSONDecodeError:
167
+ parsed_args = {}
168
+ return cls(
169
+ id=id,
170
+ function_name=function_name,
171
+ arguments=parsed_args,
172
+ arguments_raw=arguments,
173
+ )
174
+ return cls(
175
+ id=id,
176
+ function_name=function_name,
177
+ arguments=arguments,
178
+ )
179
+
180
+ def to_content_block(self) -> dict[str, Any]:
181
+ """Convert to content block format for message construction.
182
+
183
+ Returns:
184
+ Dictionary suitable for use as a tool_use content block.
185
+ """
186
+ return {
187
+ "type": "tool_use",
188
+ "id": self.id,
189
+ "name": self.function_name,
190
+ "input": self.arguments,
191
+ }
@@ -0,0 +1,21 @@
1
+ """
2
+ Utility functions and helpers.
3
+
4
+ This module contains:
5
+ - JSONPath utilities
6
+ - Tool decorator
7
+ - ToolCallAssembler for streaming tool calls
8
+ - Other helper functions
9
+ """
10
+
11
+ from ai_lib_python.utils.tool_call_assembler import (
12
+ MultiToolCallAssembler,
13
+ ToolCallAssembler,
14
+ ToolCallFragment,
15
+ )
16
+
17
+ __all__ = [
18
+ "MultiToolCallAssembler",
19
+ "ToolCallAssembler",
20
+ "ToolCallFragment",
21
+ ]
@@ -0,0 +1,317 @@
1
+ """
2
+ Tool call assembler for streaming responses.
3
+
4
+ Collects tool call fragments from streaming events and assembles
5
+ them into complete ToolCall objects.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from ai_lib_python.types.tool import ToolCall
15
+
16
+
17
+ @dataclass
18
+ class ToolCallFragment:
19
+ """Fragment of a tool call being assembled.
20
+
21
+ Attributes:
22
+ id: Tool call identifier
23
+ name: Tool/function name
24
+ arguments_buffer: Accumulated arguments string
25
+ index: Position in the tool calls array
26
+ """
27
+
28
+ id: str
29
+ name: str = ""
30
+ arguments_buffer: str = ""
31
+ index: int = 0
32
+
33
+
34
+ class ToolCallAssembler:
35
+ """Assembles tool calls from streaming fragments.
36
+
37
+ Collects tool call started events and argument fragments,
38
+ then finalizes them into complete ToolCall objects.
39
+
40
+ This is intentionally tolerant: if JSON parsing fails,
41
+ it keeps the raw string as arguments.
42
+
43
+ Example:
44
+ >>> assembler = ToolCallAssembler()
45
+ >>>
46
+ >>> # Process streaming events
47
+ >>> assembler.on_started("call_123", "get_weather")
48
+ >>> assembler.on_partial("call_123", '{"loc')
49
+ >>> assembler.on_partial("call_123", 'ation": "NYC"}')
50
+ >>>
51
+ >>> # Finalize
52
+ >>> tool_calls = assembler.finalize()
53
+ >>> print(tool_calls[0].arguments)
54
+ {'location': 'NYC'}
55
+ """
56
+
57
+ def __init__(self) -> None:
58
+ """Initialize assembler."""
59
+ self._fragments: dict[str, ToolCallFragment] = {}
60
+ self._order: list[str] = [] # Maintain insertion order
61
+
62
+ def on_started(
63
+ self,
64
+ tool_call_id: str,
65
+ tool_name: str,
66
+ index: int | None = None,
67
+ ) -> None:
68
+ """Handle tool call started event.
69
+
70
+ Args:
71
+ tool_call_id: Unique tool call identifier
72
+ tool_name: Name of the tool/function
73
+ index: Optional index in tool calls array
74
+ """
75
+ if tool_call_id in self._fragments:
76
+ # Update existing fragment with name if empty
77
+ if tool_name and not self._fragments[tool_call_id].name:
78
+ self._fragments[tool_call_id].name = tool_name
79
+ return
80
+
81
+ fragment = ToolCallFragment(
82
+ id=tool_call_id,
83
+ name=tool_name,
84
+ index=index if index is not None else len(self._order),
85
+ )
86
+ self._fragments[tool_call_id] = fragment
87
+ self._order.append(tool_call_id)
88
+
89
+ def on_partial(
90
+ self,
91
+ tool_call_id: str,
92
+ arguments_fragment: str,
93
+ ) -> None:
94
+ """Handle partial tool call arguments.
95
+
96
+ Args:
97
+ tool_call_id: Tool call identifier
98
+ arguments_fragment: Partial arguments string
99
+ """
100
+ if tool_call_id not in self._fragments:
101
+ # Create fragment if it doesn't exist
102
+ self.on_started(tool_call_id, "")
103
+
104
+ self._fragments[tool_call_id].arguments_buffer += arguments_fragment
105
+
106
+ def on_name(self, tool_call_id: str, name_fragment: str) -> None:
107
+ """Handle partial tool name (some APIs stream the name too).
108
+
109
+ Args:
110
+ tool_call_id: Tool call identifier
111
+ name_fragment: Partial name string
112
+ """
113
+ if tool_call_id not in self._fragments:
114
+ self.on_started(tool_call_id, name_fragment)
115
+ else:
116
+ self._fragments[tool_call_id].name += name_fragment
117
+
118
+ def finalize(self) -> list[ToolCall]:
119
+ """Finalize all tool calls.
120
+
121
+ Parses accumulated argument strings as JSON and creates
122
+ ToolCall objects. If JSON parsing fails, uses empty dict.
123
+
124
+ Returns:
125
+ List of assembled ToolCall objects
126
+ """
127
+ tool_calls: list[ToolCall] = []
128
+
129
+ for tool_call_id in self._order:
130
+ fragment = self._fragments[tool_call_id]
131
+
132
+ # Try to parse arguments as JSON
133
+ raw_arguments = fragment.arguments_buffer.strip()
134
+ arguments: dict[str, Any] = {}
135
+ arguments_raw: str | None = None
136
+
137
+ if raw_arguments:
138
+ try:
139
+ parsed = json.loads(raw_arguments)
140
+ if isinstance(parsed, dict):
141
+ arguments = parsed
142
+ else:
143
+ # Non-dict JSON, keep as raw
144
+ arguments_raw = raw_arguments
145
+ except json.JSONDecodeError:
146
+ # Invalid JSON, store as raw
147
+ arguments_raw = raw_arguments
148
+
149
+ tool_call = ToolCall(
150
+ id=fragment.id,
151
+ function_name=fragment.name,
152
+ arguments=arguments,
153
+ arguments_raw=arguments_raw,
154
+ )
155
+ tool_calls.append(tool_call)
156
+
157
+ return tool_calls
158
+
159
+ def finalize_and_reset(self) -> list[ToolCall]:
160
+ """Finalize tool calls and reset state.
161
+
162
+ Returns:
163
+ List of assembled ToolCall objects
164
+ """
165
+ result = self.finalize()
166
+ self.reset()
167
+ return result
168
+
169
+ def reset(self) -> None:
170
+ """Reset assembler state."""
171
+ self._fragments.clear()
172
+ self._order.clear()
173
+
174
+ def has_tool_calls(self) -> bool:
175
+ """Check if any tool calls have been started.
176
+
177
+ Returns:
178
+ True if any tool calls exist
179
+ """
180
+ return len(self._fragments) > 0
181
+
182
+ def get_fragment(self, tool_call_id: str) -> ToolCallFragment | None:
183
+ """Get a specific fragment.
184
+
185
+ Args:
186
+ tool_call_id: Tool call identifier
187
+
188
+ Returns:
189
+ ToolCallFragment or None
190
+ """
191
+ return self._fragments.get(tool_call_id)
192
+
193
+ @property
194
+ def count(self) -> int:
195
+ """Get number of tool calls being assembled."""
196
+ return len(self._fragments)
197
+
198
+ def __len__(self) -> int:
199
+ """Get number of tool calls."""
200
+ return len(self._fragments)
201
+
202
+
203
+ class MultiToolCallAssembler:
204
+ """Assembles tool calls from multiple messages/responses.
205
+
206
+ Manages multiple ToolCallAssembler instances keyed by message/turn ID.
207
+
208
+ Example:
209
+ >>> assembler = MultiToolCallAssembler()
210
+ >>>
211
+ >>> # First turn
212
+ >>> assembler.on_started("turn1", "call_1", "search")
213
+ >>> assembler.on_partial("turn1", "call_1", '{"query": "weather"}')
214
+ >>>
215
+ >>> # Second turn
216
+ >>> assembler.on_started("turn2", "call_2", "calculate")
217
+ >>> assembler.on_partial("turn2", "call_2", '{"expression": "2+2"}')
218
+ >>>
219
+ >>> # Finalize by turn
220
+ >>> turn1_calls = assembler.finalize_turn("turn1")
221
+ >>> turn2_calls = assembler.finalize_turn("turn2")
222
+ """
223
+
224
+ def __init__(self) -> None:
225
+ """Initialize multi-assembler."""
226
+ self._assemblers: dict[str, ToolCallAssembler] = {}
227
+
228
+ def _get_assembler(self, turn_id: str) -> ToolCallAssembler:
229
+ """Get or create assembler for a turn.
230
+
231
+ Args:
232
+ turn_id: Turn/message identifier
233
+
234
+ Returns:
235
+ ToolCallAssembler instance
236
+ """
237
+ if turn_id not in self._assemblers:
238
+ self._assemblers[turn_id] = ToolCallAssembler()
239
+ return self._assemblers[turn_id]
240
+
241
+ def on_started(
242
+ self,
243
+ turn_id: str,
244
+ tool_call_id: str,
245
+ tool_name: str,
246
+ index: int | None = None,
247
+ ) -> None:
248
+ """Handle tool call started event.
249
+
250
+ Args:
251
+ turn_id: Turn/message identifier
252
+ tool_call_id: Tool call identifier
253
+ tool_name: Tool name
254
+ index: Optional index
255
+ """
256
+ self._get_assembler(turn_id).on_started(tool_call_id, tool_name, index)
257
+
258
+ def on_partial(
259
+ self,
260
+ turn_id: str,
261
+ tool_call_id: str,
262
+ arguments_fragment: str,
263
+ ) -> None:
264
+ """Handle partial arguments.
265
+
266
+ Args:
267
+ turn_id: Turn/message identifier
268
+ tool_call_id: Tool call identifier
269
+ arguments_fragment: Arguments fragment
270
+ """
271
+ self._get_assembler(turn_id).on_partial(tool_call_id, arguments_fragment)
272
+
273
+ def finalize_turn(self, turn_id: str) -> list[ToolCall]:
274
+ """Finalize tool calls for a turn.
275
+
276
+ Args:
277
+ turn_id: Turn/message identifier
278
+
279
+ Returns:
280
+ List of ToolCall objects
281
+ """
282
+ if turn_id not in self._assemblers:
283
+ return []
284
+ return self._assemblers[turn_id].finalize()
285
+
286
+ def finalize_all(self) -> dict[str, list[ToolCall]]:
287
+ """Finalize all turns.
288
+
289
+ Returns:
290
+ Dictionary mapping turn IDs to tool call lists
291
+ """
292
+ return {
293
+ turn_id: assembler.finalize()
294
+ for turn_id, assembler in self._assemblers.items()
295
+ }
296
+
297
+ def reset(self) -> None:
298
+ """Reset all assemblers."""
299
+ self._assemblers.clear()
300
+
301
+ def reset_turn(self, turn_id: str) -> None:
302
+ """Reset a specific turn.
303
+
304
+ Args:
305
+ turn_id: Turn/message identifier
306
+ """
307
+ if turn_id in self._assemblers:
308
+ del self._assemblers[turn_id]
309
+
310
+ @property
311
+ def turns(self) -> list[str]:
312
+ """Get list of turn IDs."""
313
+ return list(self._assemblers.keys())
314
+
315
+ def __len__(self) -> int:
316
+ """Get number of turns."""
317
+ return len(self._assemblers)