ailoy-py 0.2.4__cp310-abi3-manylinux_2_28_x86_64.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.
- ailoy/__init__.py +5 -0
- ailoy/_core.abi3.so +0 -0
- ailoy/_core.pyi +918 -0
- ailoy/_patches.py +256 -0
- ailoy_py-0.2.4.dist-info/METADATA +126 -0
- ailoy_py-0.2.4.dist-info/RECORD +13 -0
- ailoy_py-0.2.4.dist-info/WHEEL +5 -0
- ailoy_py-0.2.4.dist-info/entry_points.txt +2 -0
- ailoy_py-0.2.4.dist-info/sboms/auditwheel.cdx.json +1 -0
- ailoy_py.libs/libfaiss-b004fceb.so +0 -0
- ailoy_py.libs/libgomp-e985bcbb.so.1.0.0 +0 -0
- ailoy_py.libs/libtvm_ffi-9e4a703d.so +0 -0
- ailoy_py.libs/libtvm_runtime-b87d417d.so +0 -0
ailoy/__init__.py
ADDED
ailoy/_core.abi3.so
ADDED
|
Binary file
|
ailoy/_core.pyi
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
# This file is automatically generated by pyo3_stub_gen
|
|
2
|
+
# ruff: noqa: E501, F401
|
|
3
|
+
|
|
4
|
+
import builtins
|
|
5
|
+
import enum
|
|
6
|
+
import typing
|
|
7
|
+
|
|
8
|
+
@typing.final
|
|
9
|
+
class Agent:
|
|
10
|
+
r"""
|
|
11
|
+
The Agent is the central orchestrator that connects the **language model**, **tools**, and **knowledge** components.
|
|
12
|
+
It manages the entire reasoning and action loop, coordinating how each subsystem contributes to the final response.
|
|
13
|
+
|
|
14
|
+
In essence, the Agent:
|
|
15
|
+
|
|
16
|
+
- Understands user input
|
|
17
|
+
- Interprets structured responses from the language model (such as tool calls)
|
|
18
|
+
- Executes tools as needed
|
|
19
|
+
- Retrieves and integrates contextual knowledge before or during inference
|
|
20
|
+
|
|
21
|
+
# Public APIs
|
|
22
|
+
- `run_delta`: Runs a user query and streams incremental deltas (partial outputs)
|
|
23
|
+
- `run`: Runs a user query and returns a complete message once all deltas are accumulated
|
|
24
|
+
|
|
25
|
+
## Delta vs. Complete Message
|
|
26
|
+
A *delta* represents a partial piece of model output, such as a text fragment or intermediate reasoning step.
|
|
27
|
+
Deltas can be accumulated into a full message using the provided accumulation utilities.
|
|
28
|
+
This allows real-time streaming while preserving the ability to reconstruct the final structured result.
|
|
29
|
+
|
|
30
|
+
See `MessageDelta`.
|
|
31
|
+
|
|
32
|
+
# Components
|
|
33
|
+
- **Language Model**: Generates natural language and structured outputs. It interprets the conversation context and predicts the assistant’s next action.
|
|
34
|
+
- **Tool**: Represents external functions or APIs that the model can dynamically invoke. The `Agent` detects tool calls and automatically executes them during the reasoning loop.
|
|
35
|
+
- **Knowledge**: Provides retrieval-augmented reasoning by fetching relevant information from stored documents or databases. When available, the `Agent` enriches model input with these results before generating an answer.
|
|
36
|
+
"""
|
|
37
|
+
@property
|
|
38
|
+
def lm(self) -> LangModel: ...
|
|
39
|
+
@property
|
|
40
|
+
def tools(self) -> builtins.list[Tool]: ...
|
|
41
|
+
def __new__(cls, lm: LangModel, tools: typing.Optional[typing.Sequence[Tool]] = None, knowledge: typing.Optional[Knowledge] = None) -> Agent: ...
|
|
42
|
+
def __repr__(self) -> builtins.str: ...
|
|
43
|
+
def add_tools(self, tools: typing.Sequence[Tool]) -> None: ...
|
|
44
|
+
def add_tool(self, tool: Tool) -> None: ...
|
|
45
|
+
def remove_tools(self, tool_names: typing.Sequence[builtins.str]) -> None: ...
|
|
46
|
+
def remove_tool(self, tool_name: builtins.str) -> None: ...
|
|
47
|
+
def clear_tools(self) -> None: ...
|
|
48
|
+
def set_knowledge(self, knowledge: Knowledge) -> None: ...
|
|
49
|
+
def remove_knowledge(self) -> None: ...
|
|
50
|
+
def run_delta(self, messages: str | list[Message], config: typing.Optional[AgentConfig] = None) -> MessageDeltaOutputIterator: ...
|
|
51
|
+
def run_delta_sync(self, messages: str | list[Message], config: typing.Optional[AgentConfig] = None) -> MessageDeltaOutputSyncIterator: ...
|
|
52
|
+
def run(self, messages: str | list[Message], config: typing.Optional[AgentConfig] = None) -> MessageOutputIterator: ...
|
|
53
|
+
def run_sync(self, messages: str | list[Message], config: typing.Optional[AgentConfig] = None) -> MessageOutputSyncIterator: ...
|
|
54
|
+
|
|
55
|
+
@typing.final
|
|
56
|
+
class AgentConfig:
|
|
57
|
+
r"""
|
|
58
|
+
Configuration for running the agent.
|
|
59
|
+
|
|
60
|
+
See `LangModelInferConfig` and `KnowledgeConfig` for more details.
|
|
61
|
+
"""
|
|
62
|
+
@property
|
|
63
|
+
def inference(self) -> typing.Optional[LangModelInferConfig]: ...
|
|
64
|
+
@inference.setter
|
|
65
|
+
def inference(self, value: typing.Optional[LangModelInferConfig]) -> None: ...
|
|
66
|
+
@property
|
|
67
|
+
def knowledge(self) -> typing.Optional[KnowledgeConfig]: ...
|
|
68
|
+
@knowledge.setter
|
|
69
|
+
def knowledge(self, value: typing.Optional[KnowledgeConfig]) -> None: ...
|
|
70
|
+
def __new__(cls, inference: typing.Optional[LangModelInferConfig] = None, knowledge: typing.Optional[KnowledgeConfig] = None) -> AgentConfig: ...
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_dict(cls, config: dict) -> AgentConfig: ...
|
|
73
|
+
|
|
74
|
+
@typing.final
|
|
75
|
+
class CacheProgress:
|
|
76
|
+
@property
|
|
77
|
+
def comment(self) -> builtins.str: ...
|
|
78
|
+
@property
|
|
79
|
+
def current(self) -> builtins.int: ...
|
|
80
|
+
@property
|
|
81
|
+
def total(self) -> builtins.int: ...
|
|
82
|
+
def __repr__(self) -> builtins.str: ...
|
|
83
|
+
|
|
84
|
+
@typing.final
|
|
85
|
+
class Document:
|
|
86
|
+
@property
|
|
87
|
+
def id(self) -> builtins.str: ...
|
|
88
|
+
@property
|
|
89
|
+
def title(self) -> typing.Optional[builtins.str]: ...
|
|
90
|
+
@property
|
|
91
|
+
def text(self) -> builtins.str: ...
|
|
92
|
+
def __eq__(self, other: builtins.object) -> builtins.bool: ...
|
|
93
|
+
def __new__(cls, id: builtins.str, text: builtins.str, title: typing.Optional[builtins.str] = None) -> Document: ...
|
|
94
|
+
|
|
95
|
+
@typing.final
|
|
96
|
+
class DocumentPolyfill:
|
|
97
|
+
r"""
|
|
98
|
+
Provides a polyfill for LLMs that do not natively support the Document feature.
|
|
99
|
+
"""
|
|
100
|
+
@property
|
|
101
|
+
def system_message_template(self) -> typing.Optional[builtins.str]: ...
|
|
102
|
+
@system_message_template.setter
|
|
103
|
+
def system_message_template(self, value: typing.Optional[builtins.str]) -> None: ...
|
|
104
|
+
@property
|
|
105
|
+
def query_message_template(self) -> typing.Optional[builtins.str]: ...
|
|
106
|
+
@query_message_template.setter
|
|
107
|
+
def query_message_template(self, value: typing.Optional[builtins.str]) -> None: ...
|
|
108
|
+
def __new__(cls, system_message_template: typing.Optional[builtins.str] = None, query_message_template: typing.Optional[builtins.str] = None) -> DocumentPolyfill: ...
|
|
109
|
+
@classmethod
|
|
110
|
+
def get(cls, kind: typing.Literal["Qwen3"]) -> DocumentPolyfill: ...
|
|
111
|
+
|
|
112
|
+
@typing.final
|
|
113
|
+
class EmbeddingModel:
|
|
114
|
+
@classmethod
|
|
115
|
+
def new_local(cls, model_name: builtins.str, device_id: typing.Optional[builtins.int] = None, validate_checksum: typing.Optional[builtins.bool] = None, progress_callback: typing.Callable[[CacheProgress], None] = None) -> typing.Awaitable[EmbeddingModel]: ...
|
|
116
|
+
@classmethod
|
|
117
|
+
def new_local_sync(cls, model_name: builtins.str, device_id: typing.Optional[builtins.int] = None, validate_checksum: typing.Optional[builtins.bool] = None, progress_callback: typing.Callable[[CacheProgress], None] = None) -> EmbeddingModel: ...
|
|
118
|
+
@classmethod
|
|
119
|
+
def download(cls, model_name: builtins.str, progress_callback: typing.Callable[[CacheProgress], None] = None) -> None: ...
|
|
120
|
+
@classmethod
|
|
121
|
+
def remove(cls, model_name: builtins.str) -> None: ...
|
|
122
|
+
async def infer(self, text: builtins.str) -> builtins.list[float]: ...
|
|
123
|
+
def infer_sync(self, text: builtins.str) -> builtins.list[float]: ...
|
|
124
|
+
|
|
125
|
+
class Grammar:
|
|
126
|
+
@typing.final
|
|
127
|
+
class Plain(Grammar):
|
|
128
|
+
__match_args__ = ()
|
|
129
|
+
def __new__(cls) -> Grammar.Plain: ...
|
|
130
|
+
|
|
131
|
+
@typing.final
|
|
132
|
+
class JSON(Grammar):
|
|
133
|
+
__match_args__ = ()
|
|
134
|
+
def __new__(cls) -> Grammar.JSON: ...
|
|
135
|
+
|
|
136
|
+
@typing.final
|
|
137
|
+
class JSONSchema(Grammar):
|
|
138
|
+
__match_args__ = ("schema",)
|
|
139
|
+
@property
|
|
140
|
+
def schema(self) -> builtins.str: ...
|
|
141
|
+
def __new__(cls, schema: builtins.str) -> Grammar.JSONSchema: ...
|
|
142
|
+
|
|
143
|
+
@typing.final
|
|
144
|
+
class Regex(Grammar):
|
|
145
|
+
__match_args__ = ("regex",)
|
|
146
|
+
@property
|
|
147
|
+
def regex(self) -> builtins.str: ...
|
|
148
|
+
def __new__(cls, regex: builtins.str) -> Grammar.Regex: ...
|
|
149
|
+
|
|
150
|
+
@typing.final
|
|
151
|
+
class CFG(Grammar):
|
|
152
|
+
__match_args__ = ("cfg",)
|
|
153
|
+
@property
|
|
154
|
+
def cfg(self) -> builtins.str: ...
|
|
155
|
+
def __new__(cls, cfg: builtins.str) -> Grammar.CFG: ...
|
|
156
|
+
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
@typing.final
|
|
160
|
+
class KVCacheConfig:
|
|
161
|
+
@property
|
|
162
|
+
def context_window_size(self) -> typing.Optional[builtins.int]: ...
|
|
163
|
+
@context_window_size.setter
|
|
164
|
+
def context_window_size(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
165
|
+
@property
|
|
166
|
+
def prefill_chunk_size(self) -> typing.Optional[builtins.int]: ...
|
|
167
|
+
@prefill_chunk_size.setter
|
|
168
|
+
def prefill_chunk_size(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
169
|
+
@property
|
|
170
|
+
def sliding_window_size(self) -> typing.Optional[builtins.int]: ...
|
|
171
|
+
@sliding_window_size.setter
|
|
172
|
+
def sliding_window_size(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
173
|
+
def __new__(cls, context_window_size: typing.Optional[builtins.int], prefill_chunk_size: typing.Optional[builtins.int], sliding_window_size: typing.Optional[builtins.int]) -> KVCacheConfig: ...
|
|
174
|
+
|
|
175
|
+
@typing.final
|
|
176
|
+
class Knowledge:
|
|
177
|
+
@classmethod
|
|
178
|
+
def new_vector_store(cls, store: VectorStore, embedding_model: EmbeddingModel) -> Knowledge: ...
|
|
179
|
+
async def retrieve(self, query: builtins.str, config: KnowledgeConfig) -> builtins.list[Document]: ...
|
|
180
|
+
def as_tool(self) -> Tool: ...
|
|
181
|
+
|
|
182
|
+
@typing.final
|
|
183
|
+
class KnowledgeConfig:
|
|
184
|
+
@property
|
|
185
|
+
def top_k(self) -> typing.Optional[builtins.int]: ...
|
|
186
|
+
@top_k.setter
|
|
187
|
+
def top_k(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
188
|
+
def __new__(cls, top_k: typing.Optional[builtins.int] = None) -> KnowledgeConfig: ...
|
|
189
|
+
@classmethod
|
|
190
|
+
def from_dict(cls, config: dict) -> KnowledgeConfig: ...
|
|
191
|
+
|
|
192
|
+
@typing.final
|
|
193
|
+
class LangModel:
|
|
194
|
+
@classmethod
|
|
195
|
+
def new_local(cls, model_name: builtins.str, device_id: typing.Optional[builtins.int] = None, validate_checksum: typing.Optional[builtins.bool] = None, kv_cache: typing.Optional[KVCacheConfig] = None, progress_callback: typing.Callable[[CacheProgress], None] = None) -> typing.Awaitable[LangModel]: ...
|
|
196
|
+
@classmethod
|
|
197
|
+
def new_local_sync(cls, model_name: builtins.str, device_id: typing.Optional[builtins.int] = None, validate_checksum: typing.Optional[builtins.bool] = None, kv_cache: typing.Optional[KVCacheConfig] = None, progress_callback: typing.Callable[[CacheProgress], None] = None) -> LangModel: ...
|
|
198
|
+
@classmethod
|
|
199
|
+
def new_stream_api(cls, spec: typing.Literal["ChatCompletion", "OpenAI", "Gemini", "Claude", "Responses", "Grok"], model_name: builtins.str, api_key: builtins.str) -> LangModel: ...
|
|
200
|
+
@classmethod
|
|
201
|
+
def download(cls, model_name: builtins.str, progress_callback: typing.Callable[[CacheProgress], None] = None) -> None: ...
|
|
202
|
+
@classmethod
|
|
203
|
+
def remove(cls, model_name: builtins.str) -> None: ...
|
|
204
|
+
def infer_delta(self, messages: str | list[Message], tools: typing.Optional[typing.Sequence[ToolDesc]] = None, documents: typing.Optional[typing.Sequence[Document]] = None, config: typing.Optional[LangModelInferConfig] = None) -> MessageDeltaOutputIterator: ...
|
|
205
|
+
def infer_delta_sync(self, messages: str | list[Message], tools: typing.Optional[typing.Sequence[ToolDesc]] = None, documents: typing.Optional[typing.Sequence[Document]] = None, config: typing.Optional[LangModelInferConfig] = None) -> MessageDeltaOutputSyncIterator: ...
|
|
206
|
+
def infer(self, messages: str | list[Message], tools: typing.Optional[typing.Sequence[ToolDesc]] = None, documents: typing.Optional[typing.Sequence[Document]] = None, config: typing.Optional[LangModelInferConfig] = None) -> typing.Awaitable[MessageOutput]: ...
|
|
207
|
+
def infer_sync(self, messages: str | list[Message], tools: typing.Optional[typing.Sequence[ToolDesc]] = None, documents: typing.Optional[typing.Sequence[Document]] = None, config: typing.Optional[LangModelInferConfig] = None) -> MessageOutput: ...
|
|
208
|
+
def __repr__(self) -> builtins.str: ...
|
|
209
|
+
|
|
210
|
+
@typing.final
|
|
211
|
+
class LangModelInferConfig:
|
|
212
|
+
r"""
|
|
213
|
+
Configuration parameters that control the behavior of language model inference.
|
|
214
|
+
|
|
215
|
+
# Fields
|
|
216
|
+
|
|
217
|
+
## `document_polyfill`
|
|
218
|
+
Configuration describing how retrieved documents are embedded into the model input.
|
|
219
|
+
If `None`, it does not perform any polyfill, (ignoring documents).
|
|
220
|
+
|
|
221
|
+
## `think_effort`
|
|
222
|
+
Controls the model's reasoning intensity.
|
|
223
|
+
In local models, `low`, `medium`, `high` is ignored.
|
|
224
|
+
In API models, it is up to it's API. See API parameters.
|
|
225
|
+
|
|
226
|
+
Possible values: `disable`, `enable`, `low`, `medium`, `high`.
|
|
227
|
+
|
|
228
|
+
## `temperature`
|
|
229
|
+
Sampling temperature controlling randomness of output.
|
|
230
|
+
Lower values make output more deterministic; higher values increase diversity.
|
|
231
|
+
|
|
232
|
+
## `top_p`
|
|
233
|
+
Nucleus sampling parameter (probability mass cutoff).
|
|
234
|
+
Limits token sampling to a cumulative probability ≤ `top_p`.`
|
|
235
|
+
|
|
236
|
+
## `max_tokens`
|
|
237
|
+
Maximum number of tokens to generate for a single inference.
|
|
238
|
+
|
|
239
|
+
## `grammar`
|
|
240
|
+
Optional grammar constraint that restricts valid output forms.
|
|
241
|
+
Supported types include:
|
|
242
|
+
- `Plain`: unconstrained text
|
|
243
|
+
- `JSON`: ensures valid JSON output
|
|
244
|
+
- `JSONSchema { schema }`: validates JSON against the given schema
|
|
245
|
+
- `Regex { regex }`: constrains generation by a regular expression
|
|
246
|
+
- `CFG { cfg }`: uses a context-free grammar definition
|
|
247
|
+
"""
|
|
248
|
+
@property
|
|
249
|
+
def document_polyfill(self) -> typing.Optional[DocumentPolyfill]: ...
|
|
250
|
+
@document_polyfill.setter
|
|
251
|
+
def document_polyfill(self, value: typing.Optional[DocumentPolyfill]) -> None: ...
|
|
252
|
+
@property
|
|
253
|
+
def think_effort(self) -> typing.Optional[typing.Literal["disable", "enable", "low", "medium", "high"]]: ...
|
|
254
|
+
@think_effort.setter
|
|
255
|
+
def think_effort(self, value: typing.Optional[typing.Literal["disable", "enable", "low", "medium", "high"]]) -> None: ...
|
|
256
|
+
@property
|
|
257
|
+
def temperature(self) -> typing.Optional[builtins.float]: ...
|
|
258
|
+
@temperature.setter
|
|
259
|
+
def temperature(self, value: typing.Optional[builtins.float]) -> None: ...
|
|
260
|
+
@property
|
|
261
|
+
def top_p(self) -> typing.Optional[builtins.float]: ...
|
|
262
|
+
@top_p.setter
|
|
263
|
+
def top_p(self, value: typing.Optional[builtins.float]) -> None: ...
|
|
264
|
+
@property
|
|
265
|
+
def max_tokens(self) -> typing.Optional[builtins.int]: ...
|
|
266
|
+
@max_tokens.setter
|
|
267
|
+
def max_tokens(self, value: typing.Optional[builtins.int]) -> None: ...
|
|
268
|
+
@property
|
|
269
|
+
def grammar(self) -> typing.Optional[Grammar]: ...
|
|
270
|
+
@grammar.setter
|
|
271
|
+
def grammar(self, value: typing.Optional[Grammar]) -> None: ...
|
|
272
|
+
def __new__(cls, document_polyfill: typing.Optional[DocumentPolyfill] = None, think_effort: typing.Optional[typing.Literal["disable", "enable", "low", "medium", "high"]] = None, temperature: typing.Optional[builtins.float] = None, top_p: typing.Optional[builtins.float] = None, max_tokens: typing.Optional[builtins.int] = None) -> LangModelInferConfig: ...
|
|
273
|
+
@classmethod
|
|
274
|
+
def from_dict(cls, config: dict) -> LangModelInferConfig: ...
|
|
275
|
+
|
|
276
|
+
@typing.final
|
|
277
|
+
class MCPClient:
|
|
278
|
+
@property
|
|
279
|
+
def tools(self) -> builtins.list[Tool]: ...
|
|
280
|
+
def __repr__(self) -> builtins.str: ...
|
|
281
|
+
@classmethod
|
|
282
|
+
def from_stdio(cls, command: builtins.str, args: typing.Sequence[builtins.str]) -> typing.Awaitable[MCPClient]: ...
|
|
283
|
+
@classmethod
|
|
284
|
+
def from_streamable_http(cls, url: builtins.str) -> typing.Awaitable[MCPClient]: ...
|
|
285
|
+
def get_tool(self, name: builtins.str) -> typing.Optional[Tool]: ...
|
|
286
|
+
|
|
287
|
+
@typing.final
|
|
288
|
+
class Message:
|
|
289
|
+
r"""
|
|
290
|
+
A chat message generated by a user, model, or tool.
|
|
291
|
+
|
|
292
|
+
`Message` is the concrete, non-streaming container used by the application to store, transmit, or feed structured content into models or tools.
|
|
293
|
+
It can represent various kinds of messages, including user input, assistant responses, tool-call outputs, or signed *thinking* metadata.
|
|
294
|
+
|
|
295
|
+
Note that many different kinds of messages can be produced.
|
|
296
|
+
For example, a language model may internally generate a `thinking` trace before emitting its final output, in order to improve reasoning accuracy.
|
|
297
|
+
In other cases, a model may produce *function calls* — structured outputs that instruct external tools to perform specific actions.
|
|
298
|
+
|
|
299
|
+
This struct is designed to handle all of these situations in a unified way.
|
|
300
|
+
|
|
301
|
+
# Example
|
|
302
|
+
|
|
303
|
+
## Rust
|
|
304
|
+
```rust
|
|
305
|
+
let msg = Message::new(Role::User).with_contents([Part::text("hello")]);
|
|
306
|
+
assert_eq!(msg.role, Role::User);
|
|
307
|
+
assert_eq!(msg.contents.len(), 1);
|
|
308
|
+
```
|
|
309
|
+
"""
|
|
310
|
+
@property
|
|
311
|
+
def role(self) -> typing.Literal["system", "user", "assistant", "tool"]:
|
|
312
|
+
r"""
|
|
313
|
+
Author of the message.
|
|
314
|
+
"""
|
|
315
|
+
@role.setter
|
|
316
|
+
def role(self, value: typing.Literal["system", "user", "assistant", "tool"]) -> None:
|
|
317
|
+
r"""
|
|
318
|
+
Author of the message.
|
|
319
|
+
"""
|
|
320
|
+
@property
|
|
321
|
+
def contents(self) -> builtins.list[Part]:
|
|
322
|
+
r"""
|
|
323
|
+
Primary parts of the message (e.g., text, image, value, or function).
|
|
324
|
+
"""
|
|
325
|
+
@contents.setter
|
|
326
|
+
def contents(self, value: typing.Optional[str | list[Part]]) -> None: ...
|
|
327
|
+
@property
|
|
328
|
+
def id(self) -> typing.Optional[builtins.str]:
|
|
329
|
+
r"""
|
|
330
|
+
Optional stable identifier for deduplication or threading.
|
|
331
|
+
"""
|
|
332
|
+
@id.setter
|
|
333
|
+
def id(self, value: typing.Optional[builtins.str]) -> None:
|
|
334
|
+
r"""
|
|
335
|
+
Optional stable identifier for deduplication or threading.
|
|
336
|
+
"""
|
|
337
|
+
@property
|
|
338
|
+
def thinking(self) -> typing.Optional[builtins.str]:
|
|
339
|
+
r"""
|
|
340
|
+
Internal “thinking” text used by some models before producing final output.
|
|
341
|
+
"""
|
|
342
|
+
@thinking.setter
|
|
343
|
+
def thinking(self, value: typing.Optional[builtins.str]) -> None:
|
|
344
|
+
r"""
|
|
345
|
+
Internal “thinking” text used by some models before producing final output.
|
|
346
|
+
"""
|
|
347
|
+
@property
|
|
348
|
+
def tool_calls(self) -> typing.Optional[builtins.list[Part]]:
|
|
349
|
+
r"""
|
|
350
|
+
Tool-call parts emitted alongside the main contents.
|
|
351
|
+
"""
|
|
352
|
+
@tool_calls.setter
|
|
353
|
+
def tool_calls(self, value: typing.Optional[builtins.list[Part]]) -> None:
|
|
354
|
+
r"""
|
|
355
|
+
Tool-call parts emitted alongside the main contents.
|
|
356
|
+
"""
|
|
357
|
+
@property
|
|
358
|
+
def signature(self) -> typing.Optional[builtins.str]:
|
|
359
|
+
r"""
|
|
360
|
+
Optional signature for the `thinking` field.
|
|
361
|
+
|
|
362
|
+
This is only applicable to certain LLM APIs that require a signature as part of the `thinking` payload.
|
|
363
|
+
"""
|
|
364
|
+
@signature.setter
|
|
365
|
+
def signature(self, value: typing.Optional[builtins.str]) -> None:
|
|
366
|
+
r"""
|
|
367
|
+
Optional signature for the `thinking` field.
|
|
368
|
+
|
|
369
|
+
This is only applicable to certain LLM APIs that require a signature as part of the `thinking` payload.
|
|
370
|
+
"""
|
|
371
|
+
def __new__(cls, role: typing.Literal["system", "user", "assistant", "tool"], contents: typing.Optional[str | list[Part]] = None, id: typing.Optional[builtins.str] = None, thinking: typing.Optional[builtins.str] = None, tool_calls: typing.Optional[typing.Sequence[Part]] = None, signature: typing.Optional[builtins.str] = None) -> Message: ...
|
|
372
|
+
def __repr__(self) -> builtins.str: ...
|
|
373
|
+
def append_tool_call(self, part: Part) -> None: ...
|
|
374
|
+
|
|
375
|
+
@typing.final
|
|
376
|
+
class MessageDelta:
|
|
377
|
+
r"""
|
|
378
|
+
A streaming, incremental update to a [`Message`].
|
|
379
|
+
|
|
380
|
+
`MessageDelta` accumulates partial outputs (text chunks, tool-call fragments, IDs, signatures, etc.) until they can be materialized as a full [`Message`].
|
|
381
|
+
It implements [`Delta`] to support accumulation.
|
|
382
|
+
|
|
383
|
+
# Accumulation Rules
|
|
384
|
+
- `role`: merging two distinct roles fails.
|
|
385
|
+
- `thinking`: concatenated in arrival order.
|
|
386
|
+
- `contents`/`tool_calls`: last element is accumulated with the incoming delta when both are compatible (e.g., Text+Text, Function+Function with matching ID policy), otherwise appended as a new fragment.
|
|
387
|
+
- `id`/`signature`: last-writer-wins.
|
|
388
|
+
|
|
389
|
+
# Finalization
|
|
390
|
+
- `finish()` converts the accumulated deltas into a fully-formed [`Message`].
|
|
391
|
+
Fails if required fields (e.g., `role`) are missing or inner deltas cannot be finalized.
|
|
392
|
+
|
|
393
|
+
# Examples
|
|
394
|
+
```rust
|
|
395
|
+
let d1 = MessageDelta::new().with_role(Role::Assistant).with_contents([PartDelta::Text { text: "Hel".into() }]);
|
|
396
|
+
let d2 = MessageDelta::new().with_contents([PartDelta::Text { text: "lo".into() }]);
|
|
397
|
+
|
|
398
|
+
let merged = d1.accumulate(d2).unwrap();
|
|
399
|
+
let msg = merged.finish().unwrap();
|
|
400
|
+
assert_eq!(msg.contents[0].as_text().unwrap(), "Hello");
|
|
401
|
+
```
|
|
402
|
+
"""
|
|
403
|
+
@property
|
|
404
|
+
def role(self) -> typing.Optional[typing.Literal["system", "user", "assistant", "tool"]]: ...
|
|
405
|
+
@role.setter
|
|
406
|
+
def role(self, value: typing.Optional[typing.Literal["system", "user", "assistant", "tool"]]) -> None: ...
|
|
407
|
+
@property
|
|
408
|
+
def contents(self) -> builtins.list[PartDelta]: ...
|
|
409
|
+
@contents.setter
|
|
410
|
+
def contents(self, value: builtins.list[PartDelta]) -> None: ...
|
|
411
|
+
@property
|
|
412
|
+
def id(self) -> typing.Optional[builtins.str]: ...
|
|
413
|
+
@id.setter
|
|
414
|
+
def id(self, value: typing.Optional[builtins.str]) -> None: ...
|
|
415
|
+
@property
|
|
416
|
+
def thinking(self) -> typing.Optional[builtins.str]: ...
|
|
417
|
+
@thinking.setter
|
|
418
|
+
def thinking(self, value: typing.Optional[builtins.str]) -> None: ...
|
|
419
|
+
@property
|
|
420
|
+
def tool_calls(self) -> builtins.list[PartDelta]: ...
|
|
421
|
+
@tool_calls.setter
|
|
422
|
+
def tool_calls(self, value: builtins.list[PartDelta]) -> None: ...
|
|
423
|
+
@property
|
|
424
|
+
def signature(self) -> typing.Optional[builtins.str]: ...
|
|
425
|
+
@signature.setter
|
|
426
|
+
def signature(self, value: typing.Optional[builtins.str]) -> None: ...
|
|
427
|
+
def __new__(cls, role: typing.Optional[typing.Literal["system", "user", "assistant", "tool"]] = None, contents: typing.Optional[typing.Sequence[PartDelta]] = None, id: typing.Optional[builtins.str] = None, thinking: typing.Optional[builtins.str] = None, tool_calls: typing.Optional[typing.Sequence[PartDelta]] = None, signature: typing.Optional[builtins.str] = None) -> MessageDelta: ...
|
|
428
|
+
def __repr__(self) -> builtins.str: ...
|
|
429
|
+
def __add__(self, other: MessageDelta) -> MessageDelta: ...
|
|
430
|
+
def finish(self) -> Message: ...
|
|
431
|
+
|
|
432
|
+
@typing.final
|
|
433
|
+
class MessageDeltaOutput:
|
|
434
|
+
r"""
|
|
435
|
+
A container for a streamed message delta and its termination signal.
|
|
436
|
+
|
|
437
|
+
During streaming, `delta` carries the incremental payload; once a terminal
|
|
438
|
+
condition is reached, `finish_reason` may be populated to explain why.
|
|
439
|
+
|
|
440
|
+
# Examples
|
|
441
|
+
```rust
|
|
442
|
+
let mut out = MessageOutput::new();
|
|
443
|
+
out.delta = MessageDelta::new().with_role(Role::Assistant).with_contents([PartDelta::Text { text: "Hi".into() }]);
|
|
444
|
+
assert!(out.finish_reason.is_none());
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
# Lifecycle
|
|
448
|
+
- While streaming: `finish_reason` is typically `None`.
|
|
449
|
+
- On completion: `finish_reason` is set; callers can then `finish()` the delta to obtain a concrete [`Message`].
|
|
450
|
+
"""
|
|
451
|
+
@property
|
|
452
|
+
def delta(self) -> MessageDelta: ...
|
|
453
|
+
@delta.setter
|
|
454
|
+
def delta(self, value: MessageDelta) -> None: ...
|
|
455
|
+
@property
|
|
456
|
+
def finish_reason(self) -> typing.Optional[FinishReason]: ...
|
|
457
|
+
@finish_reason.setter
|
|
458
|
+
def finish_reason(self, value: typing.Optional[FinishReason]) -> None: ...
|
|
459
|
+
def __repr__(self) -> builtins.str: ...
|
|
460
|
+
|
|
461
|
+
@typing.final
|
|
462
|
+
class MessageDeltaOutputIterator:
|
|
463
|
+
def __aiter__(self) -> MessageDeltaOutputIterator: ...
|
|
464
|
+
def __anext__(self) -> typing.Awaitable[MessageDeltaOutput]: ...
|
|
465
|
+
|
|
466
|
+
@typing.final
|
|
467
|
+
class MessageDeltaOutputSyncIterator:
|
|
468
|
+
def __iter__(self) -> MessageDeltaOutputSyncIterator: ...
|
|
469
|
+
def __next__(self) -> MessageDeltaOutput: ...
|
|
470
|
+
|
|
471
|
+
@typing.final
|
|
472
|
+
class MessageOutput:
|
|
473
|
+
@property
|
|
474
|
+
def message(self) -> Message: ...
|
|
475
|
+
@message.setter
|
|
476
|
+
def message(self, value: Message) -> None: ...
|
|
477
|
+
@property
|
|
478
|
+
def finish_reason(self) -> FinishReason: ...
|
|
479
|
+
@finish_reason.setter
|
|
480
|
+
def finish_reason(self, value: FinishReason) -> None: ...
|
|
481
|
+
def __repr__(self) -> builtins.str: ...
|
|
482
|
+
|
|
483
|
+
@typing.final
|
|
484
|
+
class MessageOutputIterator:
|
|
485
|
+
def __aiter__(self) -> MessageOutputIterator: ...
|
|
486
|
+
def __anext__(self) -> typing.Awaitable[MessageOutput]: ...
|
|
487
|
+
|
|
488
|
+
@typing.final
|
|
489
|
+
class MessageOutputSyncIterator:
|
|
490
|
+
def __iter__(self) -> MessageOutputSyncIterator: ...
|
|
491
|
+
def __next__(self) -> MessageOutput: ...
|
|
492
|
+
|
|
493
|
+
class Part:
|
|
494
|
+
r"""
|
|
495
|
+
Represents a semantically meaningful content unit exchanged between the model and the user.
|
|
496
|
+
|
|
497
|
+
Conceptually, each `Part` encapsulates a piece of **data** that contributes
|
|
498
|
+
to a chat message — such as text, a function invocation, or an image.
|
|
499
|
+
|
|
500
|
+
For example, a single message consisting of a sequence like
|
|
501
|
+
`(text..., image, text...)` is represented as a `Message` containing
|
|
502
|
+
an array of three `Part` elements.
|
|
503
|
+
|
|
504
|
+
Note that a `Part` does **not** carry "intent", such as "reasoning" or "tool call".
|
|
505
|
+
These higher-level semantics are determined by the context of a [`Message`].
|
|
506
|
+
|
|
507
|
+
# Example
|
|
508
|
+
|
|
509
|
+
## Rust
|
|
510
|
+
```rust
|
|
511
|
+
let part = Part::text("Hello, world!");
|
|
512
|
+
assert!(part.is_text());
|
|
513
|
+
```
|
|
514
|
+
"""
|
|
515
|
+
@property
|
|
516
|
+
def part_type(self) -> builtins.str: ...
|
|
517
|
+
def __repr__(self) -> builtins.str: ...
|
|
518
|
+
@classmethod
|
|
519
|
+
def image_from_bytes(cls, data: bytes) -> Part: ...
|
|
520
|
+
@classmethod
|
|
521
|
+
def image_from_base64(cls, data: builtins.str) -> Part: ...
|
|
522
|
+
@classmethod
|
|
523
|
+
def image_from_url(cls, url: builtins.str) -> Part: ...
|
|
524
|
+
@typing.final
|
|
525
|
+
class Text(Part):
|
|
526
|
+
r"""
|
|
527
|
+
Plain utf-8 encoded text.
|
|
528
|
+
"""
|
|
529
|
+
__match_args__ = ("text",)
|
|
530
|
+
@property
|
|
531
|
+
def text(self) -> builtins.str: ...
|
|
532
|
+
def __new__(cls, text: builtins.str) -> Part.Text: ...
|
|
533
|
+
|
|
534
|
+
@typing.final
|
|
535
|
+
class Function(Part):
|
|
536
|
+
r"""
|
|
537
|
+
Represents a structured function call to an external tool.
|
|
538
|
+
|
|
539
|
+
Many language models (LLMs) use a **function calling** mechanism to extend their capabilities.
|
|
540
|
+
When an LLM decides to use external *tools*, it produces a structured output called a `function`.
|
|
541
|
+
A function conventionally consists of two fields: a `name`, and an `arguments` field formatted as JSON.
|
|
542
|
+
This is conceptually similar to making an HTTP POST request, where the request body carries a single JSON object.
|
|
543
|
+
|
|
544
|
+
This struct models that convention, representing a function invocation request
|
|
545
|
+
from an LLM to an external tool or API.
|
|
546
|
+
|
|
547
|
+
# Examples
|
|
548
|
+
```rust
|
|
549
|
+
let f = PartFunction {
|
|
550
|
+
name: "translate".to_string(),
|
|
551
|
+
arguments: Value::from_json(r#"{"source": "hello", "lang": "cn"}"#).unwrap(),
|
|
552
|
+
};
|
|
553
|
+
```
|
|
554
|
+
"""
|
|
555
|
+
__match_args__ = ("id", "function",)
|
|
556
|
+
@property
|
|
557
|
+
def id(self) -> typing.Optional[builtins.str]: ...
|
|
558
|
+
@property
|
|
559
|
+
def function(self) -> PartFunction: ...
|
|
560
|
+
def __new__(cls, id: typing.Optional[builtins.str], function: PartFunction) -> Part.Function: ...
|
|
561
|
+
|
|
562
|
+
@typing.final
|
|
563
|
+
class Value(Part):
|
|
564
|
+
r"""
|
|
565
|
+
Holds a structured data value, typically considered as a JSON structure.
|
|
566
|
+
"""
|
|
567
|
+
__match_args__ = ("value",)
|
|
568
|
+
@property
|
|
569
|
+
def value(self) -> typing.Any: ...
|
|
570
|
+
def __new__(cls, value: typing.Any) -> Part.Value: ...
|
|
571
|
+
|
|
572
|
+
@typing.final
|
|
573
|
+
class Image(Part):
|
|
574
|
+
r"""
|
|
575
|
+
Contains an image payload or reference used within a message part.
|
|
576
|
+
The image may be provided as raw binary data or an encoded format (e.g., PNG, JPEG),
|
|
577
|
+
or as a reference via a URL. Optional metadata can be included alongside the image.
|
|
578
|
+
"""
|
|
579
|
+
__match_args__ = ("image",)
|
|
580
|
+
@property
|
|
581
|
+
def image(self) -> PartImage: ...
|
|
582
|
+
def __new__(cls, image: PartImage) -> Part.Image: ...
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class PartDelta:
|
|
586
|
+
r"""
|
|
587
|
+
Represents a partial or incremental update (delta) of a [`Part`].
|
|
588
|
+
|
|
589
|
+
This type enables composable, streaming updates to message parts.
|
|
590
|
+
For example, text may be produced token-by-token, or a function call
|
|
591
|
+
may be emitted gradually as its arguments stream in.
|
|
592
|
+
|
|
593
|
+
# Example
|
|
594
|
+
|
|
595
|
+
## Rust
|
|
596
|
+
```rust
|
|
597
|
+
let d1 = PartDelta::Text { text: "Hel".into() };
|
|
598
|
+
let d2 = PartDelta::Text { text: "lo".into() };
|
|
599
|
+
let merged = d1.accumulate(d2).unwrap();
|
|
600
|
+
assert_eq!(merged.to_text().unwrap(), "Hello");
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
# Error Handling
|
|
604
|
+
Accumulation or finalization may return an error if incompatible deltas
|
|
605
|
+
(e.g. mismatched function IDs) are combined or invalid JSON arguments are given.
|
|
606
|
+
"""
|
|
607
|
+
@property
|
|
608
|
+
def part_type(self) -> builtins.str: ...
|
|
609
|
+
def __repr__(self) -> builtins.str: ...
|
|
610
|
+
@typing.final
|
|
611
|
+
class Text(PartDelta):
|
|
612
|
+
r"""
|
|
613
|
+
Incremental text fragment.
|
|
614
|
+
"""
|
|
615
|
+
__match_args__ = ("text",)
|
|
616
|
+
@property
|
|
617
|
+
def text(self) -> builtins.str: ...
|
|
618
|
+
def __new__(cls, text: builtins.str) -> PartDelta.Text: ...
|
|
619
|
+
|
|
620
|
+
@typing.final
|
|
621
|
+
class Function(PartDelta):
|
|
622
|
+
r"""
|
|
623
|
+
Incremental function call fragment.
|
|
624
|
+
"""
|
|
625
|
+
__match_args__ = ("id", "function",)
|
|
626
|
+
@property
|
|
627
|
+
def id(self) -> typing.Optional[builtins.str]: ...
|
|
628
|
+
@property
|
|
629
|
+
def function(self) -> PartDeltaFunction: ...
|
|
630
|
+
def __new__(cls, id: typing.Optional[builtins.str], function: PartDeltaFunction) -> PartDelta.Function: ...
|
|
631
|
+
|
|
632
|
+
@typing.final
|
|
633
|
+
class Value(PartDelta):
|
|
634
|
+
r"""
|
|
635
|
+
JSON-like value update.
|
|
636
|
+
"""
|
|
637
|
+
__match_args__ = ("value",)
|
|
638
|
+
@property
|
|
639
|
+
def value(self) -> typing.Any: ...
|
|
640
|
+
def __new__(cls, value: typing.Any) -> PartDelta.Value: ...
|
|
641
|
+
|
|
642
|
+
@typing.final
|
|
643
|
+
class Null(PartDelta):
|
|
644
|
+
r"""
|
|
645
|
+
Placeholder representing no data yet.
|
|
646
|
+
"""
|
|
647
|
+
__match_args__ = ()
|
|
648
|
+
def __new__(cls) -> PartDelta.Null: ...
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
class PartDeltaFunction:
|
|
652
|
+
r"""
|
|
653
|
+
Represents an incremental update (delta) of a function part.
|
|
654
|
+
|
|
655
|
+
This type is used during streaming or partial message generation, when function calls are being streamed as text chunks or partial JSON fragments.
|
|
656
|
+
|
|
657
|
+
# Variants
|
|
658
|
+
* `Verbatim(String)` — Raw text content, typically a partial JSON fragment.
|
|
659
|
+
* `WithStringArgs { name, arguments }` — Function name and its serialized arguments as strings.
|
|
660
|
+
* `WithParsedArgs { name, arguments }` — Function name and parsed arguments as a `Value`.
|
|
661
|
+
|
|
662
|
+
# Use Case
|
|
663
|
+
When the model streams out a function call response (e.g., `"function_call":{"name":...}`),
|
|
664
|
+
the incremental deltas can be accumulated until the full function payload is formed.
|
|
665
|
+
|
|
666
|
+
# Example
|
|
667
|
+
```rust
|
|
668
|
+
let delta = PartDeltaFunction::WithStringArgs {
|
|
669
|
+
name: "translate".into(),
|
|
670
|
+
arguments: r#"{"text":"hi"}"#.into(),
|
|
671
|
+
};
|
|
672
|
+
```
|
|
673
|
+
"""
|
|
674
|
+
@typing.final
|
|
675
|
+
class Verbatim(PartDeltaFunction):
|
|
676
|
+
__match_args__ = ("text",)
|
|
677
|
+
@property
|
|
678
|
+
def text(self) -> builtins.str: ...
|
|
679
|
+
def __new__(cls, text: builtins.str) -> PartDeltaFunction.Verbatim: ...
|
|
680
|
+
|
|
681
|
+
@typing.final
|
|
682
|
+
class WithStringArgs(PartDeltaFunction):
|
|
683
|
+
__match_args__ = ("name", "arguments",)
|
|
684
|
+
@property
|
|
685
|
+
def name(self) -> builtins.str: ...
|
|
686
|
+
@property
|
|
687
|
+
def arguments(self) -> builtins.str: ...
|
|
688
|
+
def __new__(cls, name: builtins.str, arguments: builtins.str) -> PartDeltaFunction.WithStringArgs: ...
|
|
689
|
+
|
|
690
|
+
@typing.final
|
|
691
|
+
class WithParsedArgs(PartDeltaFunction):
|
|
692
|
+
__match_args__ = ("name", "arguments",)
|
|
693
|
+
@property
|
|
694
|
+
def name(self) -> builtins.str: ...
|
|
695
|
+
@property
|
|
696
|
+
def arguments(self) -> typing.Any: ...
|
|
697
|
+
def __new__(cls, name: builtins.str, arguments: typing.Any) -> PartDeltaFunction.WithParsedArgs: ...
|
|
698
|
+
|
|
699
|
+
...
|
|
700
|
+
|
|
701
|
+
@typing.final
|
|
702
|
+
class PartFunction:
|
|
703
|
+
r"""
|
|
704
|
+
Represents a function call contained within a message part.
|
|
705
|
+
"""
|
|
706
|
+
@property
|
|
707
|
+
def name(self) -> builtins.str: ...
|
|
708
|
+
@property
|
|
709
|
+
def arguments(self) -> dict[str, typing.Any]: ...
|
|
710
|
+
def __eq__(self, other: builtins.object) -> builtins.bool: ...
|
|
711
|
+
def __new__(cls, name: builtins.str, arguments: typing.Any) -> PartFunction: ...
|
|
712
|
+
def __repr__(self) -> builtins.str: ...
|
|
713
|
+
|
|
714
|
+
class PartImage:
|
|
715
|
+
r"""
|
|
716
|
+
Represents the image data contained in a [`Part`].
|
|
717
|
+
|
|
718
|
+
`PartImage` provides structured access to image data.
|
|
719
|
+
Currently, it only implments "binary" types.
|
|
720
|
+
|
|
721
|
+
# Example
|
|
722
|
+
```rust
|
|
723
|
+
let part = Part::image_binary(640, 480, "rgb", (0..640*480*3).map(|i| (i % 255) as u8)).unwrap();
|
|
724
|
+
|
|
725
|
+
if let Some(img) = part.as_image() {
|
|
726
|
+
assert_eq!(img.height(), 640);
|
|
727
|
+
assert_eq!(img.width(), 480);
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
"""
|
|
731
|
+
@typing.final
|
|
732
|
+
class Binary(PartImage):
|
|
733
|
+
__match_args__ = ("height", "width", "colorspace", "data",)
|
|
734
|
+
@property
|
|
735
|
+
def height(self) -> builtins.int: ...
|
|
736
|
+
@property
|
|
737
|
+
def width(self) -> builtins.int: ...
|
|
738
|
+
@property
|
|
739
|
+
def colorspace(self) -> typing.Literal["grayscale", "rgb", "rgba"]: ...
|
|
740
|
+
@property
|
|
741
|
+
def data(self) -> typing.Any: ...
|
|
742
|
+
def __new__(cls, height: builtins.int, width: builtins.int, colorspace: typing.Literal["grayscale", "rgb", "rgba"], data: typing.Any) -> PartImage.Binary: ...
|
|
743
|
+
|
|
744
|
+
@typing.final
|
|
745
|
+
class Url(PartImage):
|
|
746
|
+
__match_args__ = ("url",)
|
|
747
|
+
@property
|
|
748
|
+
def url(self) -> builtins.str: ...
|
|
749
|
+
def __new__(cls, url: builtins.str) -> PartImage.Url: ...
|
|
750
|
+
|
|
751
|
+
...
|
|
752
|
+
|
|
753
|
+
class Tool:
|
|
754
|
+
@classmethod
|
|
755
|
+
def new_builtin(cls, kind: typing.Literal["terminal", "web_search_duckduckgo", "web_fetch"], **kwargs: typing.Any) -> Tool: ...
|
|
756
|
+
@classmethod
|
|
757
|
+
def new_py_function(cls, func: typing.Any, desc: typing.Optional[ToolDesc] = None) -> Tool: ...
|
|
758
|
+
def __repr__(self) -> builtins.str: ...
|
|
759
|
+
def get_description(self) -> ToolDesc: ...
|
|
760
|
+
def __call__(self, **kwargs: typing.Any) -> typing.Awaitable[typing.Any]: ...
|
|
761
|
+
def call(self, **kwargs: typing.Any) -> typing.Awaitable[typing.Any]: ...
|
|
762
|
+
def call_sync(self, **kwargs: typing.Any) -> typing.Any: ...
|
|
763
|
+
|
|
764
|
+
@typing.final
|
|
765
|
+
class ToolDesc:
|
|
766
|
+
r"""
|
|
767
|
+
Describes a **tool** (or function) that a language model can invoke.
|
|
768
|
+
|
|
769
|
+
`ToolDesc` defines the schema, behavior, and input/output specification of a callable
|
|
770
|
+
external function, allowing an LLM to understand how to use it.
|
|
771
|
+
|
|
772
|
+
The primary role of this struct is to describe to the LLM what a *tool* does,
|
|
773
|
+
how it can be invoked, and what input (`parameters`) and output (`returns`) schemas it expects.
|
|
774
|
+
|
|
775
|
+
The format follows the same **schema conventions** used by Hugging Face’s
|
|
776
|
+
`transformers` library, as well as APIs such as *OpenAI* and *Anthropic*.
|
|
777
|
+
The `parameters` and `returns` fields are typically defined using **JSON Schema**.
|
|
778
|
+
|
|
779
|
+
We provide a builder [`ToolDescBuilder`] helper for convenient and fluent construction.
|
|
780
|
+
Please refer to [`ToolDescBuilder`].
|
|
781
|
+
|
|
782
|
+
# Example
|
|
783
|
+
```rust
|
|
784
|
+
use crate::value::{ToolDescBuilder, to_value};
|
|
785
|
+
|
|
786
|
+
let desc = ToolDescBuilder::new("temperature")
|
|
787
|
+
.description("Get the current temperature for a given city")
|
|
788
|
+
.parameters(to_value!({
|
|
789
|
+
"type": "object",
|
|
790
|
+
"properties": {
|
|
791
|
+
"location": {
|
|
792
|
+
"type": "string",
|
|
793
|
+
"description": "The city name"
|
|
794
|
+
},
|
|
795
|
+
"unit": {
|
|
796
|
+
"type": "string",
|
|
797
|
+
"description": "Temperature unit (default: Celsius)",
|
|
798
|
+
"enum": ["Celsius", "Fahrenheit"]
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
"required": ["location"]
|
|
802
|
+
}))
|
|
803
|
+
.returns(to_value!({
|
|
804
|
+
"type": "number"
|
|
805
|
+
}))
|
|
806
|
+
.build();
|
|
807
|
+
|
|
808
|
+
assert_eq!(desc.name, "temperature");
|
|
809
|
+
```
|
|
810
|
+
"""
|
|
811
|
+
@property
|
|
812
|
+
def name(self) -> builtins.str: ...
|
|
813
|
+
@property
|
|
814
|
+
def description(self) -> typing.Optional[builtins.str]: ...
|
|
815
|
+
@property
|
|
816
|
+
def parameters(self) -> dict: ...
|
|
817
|
+
@property
|
|
818
|
+
def returns(self) -> typing.Optional[dict]: ...
|
|
819
|
+
def __new__(cls, name: builtins.str, description: typing.Optional[builtins.str], parameters: dict, *, returns: typing.Optional[dict] = None) -> ToolDesc: ...
|
|
820
|
+
def __repr__(self) -> builtins.str: ...
|
|
821
|
+
|
|
822
|
+
@typing.final
|
|
823
|
+
class VectorStore:
|
|
824
|
+
@classmethod
|
|
825
|
+
def new_faiss(cls, dim: builtins.int) -> VectorStore: ...
|
|
826
|
+
@classmethod
|
|
827
|
+
def new_chroma(cls, url: builtins.str, collection_name: typing.Optional[builtins.str]) -> VectorStore: ...
|
|
828
|
+
def add_vector(self, input: VectorStoreAddInput) -> builtins.str: ...
|
|
829
|
+
def add_vectors(self, inputs: typing.Sequence[VectorStoreAddInput]) -> builtins.list[builtins.str]: ...
|
|
830
|
+
def get_by_id(self, id: builtins.str) -> typing.Optional[VectorStoreGetResult]: ...
|
|
831
|
+
def get_by_ids(self, ids: typing.Sequence[builtins.str]) -> builtins.list[VectorStoreGetResult]: ...
|
|
832
|
+
def retrieve(self, query_embedding: builtins.list[float], top_k: builtins.int) -> builtins.list[VectorStoreRetrieveResult]: ...
|
|
833
|
+
def batch_retrieve(self, query_embeddings: typing.Sequence[builtins.list[float]], top_k: builtins.int) -> builtins.list[builtins.list[VectorStoreRetrieveResult]]: ...
|
|
834
|
+
def remove_vector(self, id: builtins.str) -> None: ...
|
|
835
|
+
def remove_vectors(self, ids: typing.Sequence[builtins.str]) -> None: ...
|
|
836
|
+
def clear(self) -> None: ...
|
|
837
|
+
def count(self) -> builtins.int: ...
|
|
838
|
+
|
|
839
|
+
@typing.final
|
|
840
|
+
class VectorStoreAddInput:
|
|
841
|
+
@property
|
|
842
|
+
def embedding(self) -> builtins.list[float]: ...
|
|
843
|
+
@embedding.setter
|
|
844
|
+
def embedding(self, value: builtins.list[float]) -> None: ...
|
|
845
|
+
@property
|
|
846
|
+
def document(self) -> builtins.str: ...
|
|
847
|
+
@document.setter
|
|
848
|
+
def document(self, value: builtins.str) -> None: ...
|
|
849
|
+
@property
|
|
850
|
+
def metadata(self) -> typing.Optional[builtins.dict[builtins.str, typing.Any]]: ...
|
|
851
|
+
@metadata.setter
|
|
852
|
+
def metadata(self, value: typing.Optional[builtins.dict[builtins.str, typing.Any]]) -> None: ...
|
|
853
|
+
def __new__(cls, embedding: builtins.list[float], document: builtins.str, metadata: typing.Optional[typing.Mapping[builtins.str, typing.Any]] = None) -> VectorStoreAddInput: ...
|
|
854
|
+
|
|
855
|
+
@typing.final
|
|
856
|
+
class VectorStoreGetResult:
|
|
857
|
+
@property
|
|
858
|
+
def id(self) -> builtins.str: ...
|
|
859
|
+
@id.setter
|
|
860
|
+
def id(self, value: builtins.str) -> None: ...
|
|
861
|
+
@property
|
|
862
|
+
def document(self) -> builtins.str: ...
|
|
863
|
+
@document.setter
|
|
864
|
+
def document(self, value: builtins.str) -> None: ...
|
|
865
|
+
@property
|
|
866
|
+
def metadata(self) -> typing.Optional[builtins.dict[builtins.str, typing.Any]]: ...
|
|
867
|
+
@metadata.setter
|
|
868
|
+
def metadata(self, value: typing.Optional[builtins.dict[builtins.str, typing.Any]]) -> None: ...
|
|
869
|
+
@property
|
|
870
|
+
def embedding(self) -> builtins.list[float]: ...
|
|
871
|
+
@embedding.setter
|
|
872
|
+
def embedding(self, value: builtins.list[float]) -> None: ...
|
|
873
|
+
|
|
874
|
+
@typing.final
|
|
875
|
+
class VectorStoreRetrieveResult:
|
|
876
|
+
@property
|
|
877
|
+
def id(self) -> builtins.str: ...
|
|
878
|
+
@id.setter
|
|
879
|
+
def id(self, value: builtins.str) -> None: ...
|
|
880
|
+
@property
|
|
881
|
+
def document(self) -> builtins.str: ...
|
|
882
|
+
@document.setter
|
|
883
|
+
def document(self, value: builtins.str) -> None: ...
|
|
884
|
+
@property
|
|
885
|
+
def metadata(self) -> typing.Optional[builtins.dict[builtins.str, typing.Any]]: ...
|
|
886
|
+
@metadata.setter
|
|
887
|
+
def metadata(self, value: typing.Optional[builtins.dict[builtins.str, typing.Any]]) -> None: ...
|
|
888
|
+
@property
|
|
889
|
+
def distance(self) -> builtins.float: ...
|
|
890
|
+
@distance.setter
|
|
891
|
+
def distance(self, value: builtins.float) -> None: ...
|
|
892
|
+
|
|
893
|
+
@typing.final
|
|
894
|
+
class FinishReason(enum.Enum):
|
|
895
|
+
r"""
|
|
896
|
+
Explains why a language model's streamed generation finished.
|
|
897
|
+
"""
|
|
898
|
+
Stop = ...
|
|
899
|
+
r"""
|
|
900
|
+
The model stopped naturally (e.g., EOS token or stop sequence).
|
|
901
|
+
"""
|
|
902
|
+
Length = ...
|
|
903
|
+
r"""
|
|
904
|
+
Hit the maximum token/length limit.
|
|
905
|
+
"""
|
|
906
|
+
ToolCall = ...
|
|
907
|
+
r"""
|
|
908
|
+
Stopped because a tool call was produced, waiting for it's execution.
|
|
909
|
+
"""
|
|
910
|
+
Refusal = ...
|
|
911
|
+
r"""
|
|
912
|
+
Content was refused/filtered; string provides reason.
|
|
913
|
+
"""
|
|
914
|
+
|
|
915
|
+
def __repr__(self) -> builtins.str: ...
|
|
916
|
+
|
|
917
|
+
def ailoy_model_cli() -> None: ...
|
|
918
|
+
|
ailoy/_patches.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import types
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Literal,
|
|
9
|
+
Optional,
|
|
10
|
+
Union,
|
|
11
|
+
get_args,
|
|
12
|
+
get_origin,
|
|
13
|
+
get_type_hints,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from ailoy._core import Tool, ToolDesc
|
|
17
|
+
|
|
18
|
+
description_re = re.compile(r"^(.*?)[\n\s]*(Args:|Returns:|Raises:|\Z)", re.DOTALL)
|
|
19
|
+
# Extracts the Args: block from the docstring
|
|
20
|
+
args_re = re.compile(r"\n\s*Args:\n\s*(.*?)[\n\s]*(Returns:|Raises:|\Z)", re.DOTALL)
|
|
21
|
+
# Splits the Args: block into individual arguments
|
|
22
|
+
args_split_re = re.compile(
|
|
23
|
+
r"""
|
|
24
|
+
(?:^|\n) # Match the start of the args block, or a newline
|
|
25
|
+
\s*(\w+):\s* # Capture the argument name and strip spacing
|
|
26
|
+
(.*?)\s* # Capture the argument description, which can span multiple lines, and strip trailing spacing
|
|
27
|
+
(?=\n\s*\w+:|\Z) # Stop when you hit the next argument or the end of the block
|
|
28
|
+
""",
|
|
29
|
+
re.DOTALL | re.VERBOSE,
|
|
30
|
+
)
|
|
31
|
+
# Extracts the Returns: block from the docstring, if present. Note that most chat templates ignore the return type/doc!
|
|
32
|
+
returns_re = re.compile(r"\n\s*Returns:\n\s*(.*?)[\n\s]*(Raises:|\Z)", re.DOTALL)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TypeHintParsingException(Exception):
|
|
36
|
+
"""Exception raised for errors in parsing type hints to generate JSON schemas"""
|
|
37
|
+
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DocstringParsingException(Exception):
|
|
42
|
+
"""Exception raised for errors in parsing docstrings to generate JSON schemas"""
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_json_schema_type(param_type: type) -> dict[str, str]:
|
|
48
|
+
type_mapping = {
|
|
49
|
+
int: {"type": "integer"},
|
|
50
|
+
float: {"type": "number"},
|
|
51
|
+
str: {"type": "string"},
|
|
52
|
+
bool: {"type": "boolean"},
|
|
53
|
+
type(None): {"type": "null"},
|
|
54
|
+
Any: {},
|
|
55
|
+
}
|
|
56
|
+
# if is_vision_available():
|
|
57
|
+
# type_mapping[Image] = {"type": "image"}
|
|
58
|
+
# if is_torch_available():
|
|
59
|
+
# type_mapping[Tensor] = {"type": "audio"}
|
|
60
|
+
return type_mapping.get(param_type, {"type": "object"})
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _parse_type_hint(hint: str) -> dict:
|
|
64
|
+
origin = get_origin(hint)
|
|
65
|
+
args = get_args(hint)
|
|
66
|
+
|
|
67
|
+
if origin is None:
|
|
68
|
+
try:
|
|
69
|
+
return _get_json_schema_type(hint)
|
|
70
|
+
except KeyError:
|
|
71
|
+
raise TypeHintParsingException(
|
|
72
|
+
"Couldn't parse this type hint, likely due to a custom class or object: ",
|
|
73
|
+
hint,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
elif origin is Union or (hasattr(types, "UnionType") and origin is types.UnionType):
|
|
77
|
+
# Recurse into each of the subtypes in the Union, except None, which is handled separately at the end
|
|
78
|
+
subtypes = [_parse_type_hint(t) for t in args if t is not type(None)]
|
|
79
|
+
if len(subtypes) == 1:
|
|
80
|
+
# A single non-null type can be expressed directly
|
|
81
|
+
return_dict = subtypes[0]
|
|
82
|
+
elif all(isinstance(subtype["type"], str) for subtype in subtypes):
|
|
83
|
+
# A union of basic types can be expressed as a list in the schema
|
|
84
|
+
return_dict = {"type": sorted([subtype["type"] for subtype in subtypes])}
|
|
85
|
+
else:
|
|
86
|
+
# A union of more complex types requires "anyOf"
|
|
87
|
+
return_dict = {"anyOf": subtypes}
|
|
88
|
+
if type(None) in args:
|
|
89
|
+
return_dict["nullable"] = True
|
|
90
|
+
return return_dict
|
|
91
|
+
|
|
92
|
+
elif origin is Literal and len(args) > 0:
|
|
93
|
+
LITERAL_TYPES = (int, float, str, bool, type(None))
|
|
94
|
+
args_types = []
|
|
95
|
+
for arg in args:
|
|
96
|
+
if type(arg) not in LITERAL_TYPES:
|
|
97
|
+
raise TypeHintParsingException(
|
|
98
|
+
"Only the valid python literals can be listed in typing.Literal."
|
|
99
|
+
)
|
|
100
|
+
arg_type = _get_json_schema_type(type(arg)).get("type")
|
|
101
|
+
if arg_type is not None and arg_type not in args_types:
|
|
102
|
+
args_types.append(arg_type)
|
|
103
|
+
return {
|
|
104
|
+
"type": args_types.pop() if len(args_types) == 1 else list(args_types),
|
|
105
|
+
"enum": list(args),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
elif origin is list:
|
|
109
|
+
if not args:
|
|
110
|
+
return {"type": "array"}
|
|
111
|
+
else:
|
|
112
|
+
# Lists can only have a single type argument, so recurse into it
|
|
113
|
+
return {"type": "array", "items": _parse_type_hint(args[0])}
|
|
114
|
+
|
|
115
|
+
elif origin is tuple:
|
|
116
|
+
if not args:
|
|
117
|
+
return {"type": "array"}
|
|
118
|
+
if len(args) == 1:
|
|
119
|
+
raise TypeHintParsingException(
|
|
120
|
+
f"The type hint {str(hint).replace('typing.', '')} is a Tuple with a single element, which "
|
|
121
|
+
"we do not automatically convert to JSON schema as it is rarely necessary. If this input can contain "
|
|
122
|
+
"more than one element, we recommend "
|
|
123
|
+
"using a list[] type instead, or if it really is a single element, remove the tuple[] wrapper and just "
|
|
124
|
+
"pass the element directly."
|
|
125
|
+
)
|
|
126
|
+
if ... in args:
|
|
127
|
+
raise TypeHintParsingException(
|
|
128
|
+
"Conversion of '...' is not supported in Tuple type hints. "
|
|
129
|
+
"Use list[] types for variable-length"
|
|
130
|
+
" inputs instead."
|
|
131
|
+
)
|
|
132
|
+
return {"type": "array", "prefixItems": [_parse_type_hint(t) for t in args]}
|
|
133
|
+
|
|
134
|
+
elif origin is dict:
|
|
135
|
+
# The JSON equivalent to a dict is 'object', which mandates that all keys are strings
|
|
136
|
+
# However, we can specify the type of the dict values with "additionalProperties"
|
|
137
|
+
out = {"type": "object"}
|
|
138
|
+
if len(args) == 2:
|
|
139
|
+
out["additionalProperties"] = _parse_type_hint(args[1])
|
|
140
|
+
return out
|
|
141
|
+
|
|
142
|
+
raise TypeHintParsingException(
|
|
143
|
+
"Couldn't parse this type hint, likely due to a custom class or object: ", hint
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _convert_type_hints_to_json_schema(func: Callable) -> dict:
|
|
148
|
+
type_hints = get_type_hints(func)
|
|
149
|
+
signature = inspect.signature(func)
|
|
150
|
+
required = []
|
|
151
|
+
for param_name, param in signature.parameters.items():
|
|
152
|
+
if param.annotation == inspect.Parameter.empty:
|
|
153
|
+
raise TypeHintParsingException(
|
|
154
|
+
f"Argument {param.name} is missing a type hint in function {func.__name__}"
|
|
155
|
+
)
|
|
156
|
+
if param.default == inspect.Parameter.empty:
|
|
157
|
+
required.append(param_name)
|
|
158
|
+
|
|
159
|
+
properties = {}
|
|
160
|
+
for param_name, param_type in type_hints.items():
|
|
161
|
+
properties[param_name] = _parse_type_hint(param_type)
|
|
162
|
+
|
|
163
|
+
schema = {"type": "object", "properties": properties}
|
|
164
|
+
if required:
|
|
165
|
+
schema["required"] = required
|
|
166
|
+
|
|
167
|
+
return schema
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def parse_google_format_docstring(
|
|
171
|
+
docstring: str,
|
|
172
|
+
) -> tuple[Optional[str], Optional[dict], Optional[str]]:
|
|
173
|
+
"""
|
|
174
|
+
Parses a Google-style docstring to extract the function description,
|
|
175
|
+
argument descriptions, and return description.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
docstring (str): The docstring to parse.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
The function description, arguments, and return description.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
# Extract the sections
|
|
185
|
+
description_match = description_re.search(docstring)
|
|
186
|
+
args_match = args_re.search(docstring)
|
|
187
|
+
returns_match = returns_re.search(docstring)
|
|
188
|
+
|
|
189
|
+
# Clean and store the sections
|
|
190
|
+
description = description_match.group(1).strip() if description_match else None
|
|
191
|
+
docstring_args = args_match.group(1).strip() if args_match else None
|
|
192
|
+
returns = returns_match.group(1).strip() if returns_match else None
|
|
193
|
+
|
|
194
|
+
# Parsing the arguments into a dictionary
|
|
195
|
+
if docstring_args is not None:
|
|
196
|
+
docstring_args = "\n".join(
|
|
197
|
+
[line for line in docstring_args.split("\n") if line.strip()]
|
|
198
|
+
) # Remove blank lines
|
|
199
|
+
matches = args_split_re.findall(docstring_args)
|
|
200
|
+
args_dict = {
|
|
201
|
+
match[0]: re.sub(r"\s*\n+\s*", " ", match[1].strip()) for match in matches
|
|
202
|
+
}
|
|
203
|
+
else:
|
|
204
|
+
args_dict = {}
|
|
205
|
+
|
|
206
|
+
return description, args_dict, returns
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_json_schema(func: Callable) -> dict:
|
|
210
|
+
doc = inspect.getdoc(func)
|
|
211
|
+
if not doc:
|
|
212
|
+
raise DocstringParsingException(
|
|
213
|
+
f"Cannot generate JSON schema for {func.__name__} because it has no docstring!"
|
|
214
|
+
)
|
|
215
|
+
doc = doc.strip()
|
|
216
|
+
main_doc, param_descriptions, return_doc = parse_google_format_docstring(doc)
|
|
217
|
+
|
|
218
|
+
json_schema = _convert_type_hints_to_json_schema(func)
|
|
219
|
+
if (return_dict := json_schema["properties"].pop("return", None)) is not None:
|
|
220
|
+
if (
|
|
221
|
+
return_doc is not None
|
|
222
|
+
): # We allow a missing return docstring since most templates ignore it
|
|
223
|
+
return_dict["description"] = return_doc
|
|
224
|
+
for arg, schema in json_schema["properties"].items():
|
|
225
|
+
if arg not in param_descriptions:
|
|
226
|
+
raise DocstringParsingException(
|
|
227
|
+
f"Cannot generate JSON schema for {func.__name__} because the docstring has no description for the argument '{arg}'"
|
|
228
|
+
)
|
|
229
|
+
desc = param_descriptions[arg]
|
|
230
|
+
enum_choices = re.search(r"\(choices:\s*(.*?)\)\s*$", desc, flags=re.IGNORECASE)
|
|
231
|
+
if enum_choices:
|
|
232
|
+
schema["enum"] = [c.strip() for c in json.loads(enum_choices.group(1))]
|
|
233
|
+
desc = enum_choices.string[: enum_choices.start()].strip()
|
|
234
|
+
schema["description"] = desc
|
|
235
|
+
|
|
236
|
+
output = {"name": func.__name__, "description": main_doc, "parameters": json_schema}
|
|
237
|
+
if return_dict is not None:
|
|
238
|
+
output["returns"] = return_dict
|
|
239
|
+
return {"type": "function", "function": output}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def new_py_function(
|
|
243
|
+
cls: Tool, func: Callable, tool_desc: Optional[ToolDesc] = None
|
|
244
|
+
) -> Tool:
|
|
245
|
+
if tool_desc is None:
|
|
246
|
+
try:
|
|
247
|
+
json_schema = get_json_schema(func)
|
|
248
|
+
except (TypeHintParsingException, DocstringParsingException) as e:
|
|
249
|
+
raise ValueError("Failed to parse docstring", e)
|
|
250
|
+
|
|
251
|
+
tool_desc = ToolDesc(**json_schema.get("function"))
|
|
252
|
+
|
|
253
|
+
return cls.__new_py_function__(tool_desc, func)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
setattr(Tool, "new_py_function", classmethod(new_py_function))
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ailoy-py
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
7
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
8
|
+
Classifier: Programming Language :: Rust
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
16
|
+
Summary: A lightweight library for building AI applications
|
|
17
|
+
Author-email: "Brekkylab Inc." <contact@brekkylab.com>
|
|
18
|
+
License-Expression: Apache-2.0
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
21
|
+
Project-URL: Documentation, https://brekkylab.github.io/ailoy
|
|
22
|
+
Project-URL: Homepage, https://brekkylab.github.io/ailoy
|
|
23
|
+
Project-URL: Issues, https://github.com/brekkylab/ailoy/issues
|
|
24
|
+
Project-URL: Repository, https://github.com/brekkylab/ailoy
|
|
25
|
+
|
|
26
|
+
# ailoy-py
|
|
27
|
+
|
|
28
|
+
Ailoy is a lightweight library for building AI applications — such as **agent systems** or **RAG pipelines** — with ease. It is designed to enable AI features effortlessly, one can just import and use.
|
|
29
|
+
|
|
30
|
+
See our [documentation](https://brekkylab.github.io/ailoy) for more details.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install ailoy-py
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
### Asynchronous version (recommended)
|
|
41
|
+
```python
|
|
42
|
+
import asyncio
|
|
43
|
+
|
|
44
|
+
import ailoy as ai
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def main():
|
|
48
|
+
# Create Qwen3-0.6B local LangModel
|
|
49
|
+
model = await ai.LangModel.new_local("Qwen/Qwen3-0.6B")
|
|
50
|
+
|
|
51
|
+
# Create an agent using this model
|
|
52
|
+
agent = ai.Agent(model)
|
|
53
|
+
|
|
54
|
+
# Ask a prompt and iterate over agent's responses
|
|
55
|
+
async for resp in agent.run("What is your name?"):
|
|
56
|
+
print(resp)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Synchronous version
|
|
64
|
+
```python
|
|
65
|
+
import ailoy as ai
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main():
|
|
69
|
+
# Create Qwen3-0.6B LocalLanguageModel
|
|
70
|
+
model = ai.LangModel.new_local_sync("Qwen/Qwen3-0.6B")
|
|
71
|
+
|
|
72
|
+
# Create an agent using this model
|
|
73
|
+
agent = ai.Agent(model)
|
|
74
|
+
|
|
75
|
+
# Ask a prompt and iterate over agent's responses
|
|
76
|
+
for resp in agent.run_sync("What is your name?"):
|
|
77
|
+
print(resp)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Building from source
|
|
85
|
+
|
|
86
|
+
### Prerequisites
|
|
87
|
+
|
|
88
|
+
- Rust >= 1.88
|
|
89
|
+
- Python >= 3.10
|
|
90
|
+
- C/C++ compiler
|
|
91
|
+
(recommended versions are below)
|
|
92
|
+
- GCC >= 13
|
|
93
|
+
- LLVM Clang >= 17
|
|
94
|
+
- Apple Clang >= 15
|
|
95
|
+
- MSVC >= 19.29
|
|
96
|
+
- CMake >= 3.28.0
|
|
97
|
+
- Git
|
|
98
|
+
- OpenMP (required to build Faiss)
|
|
99
|
+
- BLAS (required to build Faiss)
|
|
100
|
+
- LAPACK (required to build Faiss)
|
|
101
|
+
- Vulkan SDK (on Windows and Linux)
|
|
102
|
+
|
|
103
|
+
> [!WARNING]
|
|
104
|
+
> To build binding, you must change the crate type to **`cdylib`** in [Cargo.toml](../../Cargo.toml).
|
|
105
|
+
>
|
|
106
|
+
> ```toml
|
|
107
|
+
> [lib]
|
|
108
|
+
> crate-type = ["dylib"] # <- Change this to ["cdylib"]
|
|
109
|
+
> ```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Setup development environment
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install maturin
|
|
116
|
+
|
|
117
|
+
# This generates `_core.cpython-3xx-darwin.so` under `ailoy/`
|
|
118
|
+
maturin develop
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Generate wheel
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
maturin build --out ./dist
|
|
125
|
+
```
|
|
126
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
ailoy/__init__.py,sha256=gjSajwEPDaWJAXvM744fj_wXHYrpRLR38sYWj_xUp-k,96
|
|
2
|
+
ailoy/_core.abi3.so,sha256=wVzbAwMfXsiuURQ8lQfGNFg3D-vBlEmPUwLua0n_EnA,34447561
|
|
3
|
+
ailoy/_core.pyi,sha256=HTPWsidoLyoLH2jBjlg7vt-23biqRXMKhqBtMQnUz30,38704
|
|
4
|
+
ailoy/_patches.py,sha256=bJg-jl64VKWbFbLyxqM-ZY3a3CK8RoNGzMF6CpWU2pI,9602
|
|
5
|
+
ailoy_py.libs/libfaiss-b004fceb.so,sha256=e02JSyUH4KERET9e1rNexUbZSWyImWkKCk7ZN8t4ymg,11257937
|
|
6
|
+
ailoy_py.libs/libgomp-e985bcbb.so.1.0.0,sha256=pDkE5PopcwHUZA3BuzyKNIC0BvmeSY66mxkUtoqrYEo,253289
|
|
7
|
+
ailoy_py.libs/libtvm_ffi-9e4a703d.so,sha256=exaT3IeM7GD5KXIRSUMlb53-6AQlYklHAZuuZefcNhk,1135113
|
|
8
|
+
ailoy_py.libs/libtvm_runtime-b87d417d.so,sha256=CPsHFl8zASSjgwfPAalBv8WkDwhMP2A4xfCaieO9ndI,5927721
|
|
9
|
+
ailoy_py-0.2.4.dist-info/METADATA,sha256=IyNWRcnuFmfYOD1Tghlv5buoNz-TYgPTRz9StoirpYI,3183
|
|
10
|
+
ailoy_py-0.2.4.dist-info/WHEEL,sha256=IFJVkQ8769pcslF3jPXXkSmy4yVHIAHaSOV_0yFIexE,109
|
|
11
|
+
ailoy_py-0.2.4.dist-info/entry_points.txt,sha256=2QfqZEtN7J34DEJaPEf_zmQalbrI3NMDGJT81mdgXjQ,58
|
|
12
|
+
ailoy_py-0.2.4.dist-info/RECORD,,
|
|
13
|
+
ailoy_py-0.2.4.dist-info/sboms/auditwheel.cdx.json,sha256=7d965nwURfbckBSej25GURuVgxaimx51zflOxOd_qPg,1315
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"bomFormat": "CycloneDX", "specVersion": "1.4", "version": 1, "metadata": {"component": {"type": "library", "bom-ref": "pkg:pypi/ailoy_py@0.2.4?file_name=ailoy_py-0.2.4-cp310-abi3-manylinux_2_28_x86_64.whl", "name": "ailoy_py", "version": "0.2.4", "purl": "pkg:pypi/ailoy_py@0.2.4?file_name=ailoy_py-0.2.4-cp310-abi3-manylinux_2_28_x86_64.whl"}, "tools": [{"name": "auditwheel", "version": "6.5.0"}]}, "components": [{"type": "library", "bom-ref": "pkg:pypi/ailoy_py@0.2.4?file_name=ailoy_py-0.2.4-cp310-abi3-manylinux_2_28_x86_64.whl", "name": "ailoy_py", "version": "0.2.4", "purl": "pkg:pypi/ailoy_py@0.2.4?file_name=ailoy_py-0.2.4-cp310-abi3-manylinux_2_28_x86_64.whl"}, {"type": "library", "bom-ref": "pkg:rpm/almalinux/libgomp@8.5.0-28.el8_10.alma.1#c61017c9a24eb6e1e1a3cdc9becd004a6419cbda3d54b4848b98f240a4829571", "name": "libgomp", "version": "8.5.0-28.el8_10.alma.1", "purl": "pkg:rpm/almalinux/libgomp@8.5.0-28.el8_10.alma.1"}], "dependencies": [{"ref": "pkg:pypi/ailoy_py@0.2.4?file_name=ailoy_py-0.2.4-cp310-abi3-manylinux_2_28_x86_64.whl", "dependsOn": ["pkg:rpm/almalinux/libgomp@8.5.0-28.el8_10.alma.1#c61017c9a24eb6e1e1a3cdc9becd004a6419cbda3d54b4848b98f240a4829571"]}, {"ref": "pkg:rpm/almalinux/libgomp@8.5.0-28.el8_10.alma.1#c61017c9a24eb6e1e1a3cdc9becd004a6419cbda3d54b4848b98f240a4829571"}]}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|