ailoy-py 0.0.2__cp312-cp312-win_amd64.whl → 0.0.5__cp312-cp312-win_amd64.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.
Potentially problematic release.
This version of ailoy-py might be problematic. Click here for more details.
- ailoy/__init__.py +5 -4
- ailoy/agent.py +134 -166
- ailoy/ailoy_py.cp312-win_amd64.pyd +0 -0
- ailoy/mcp.py +60 -48
- ailoy/models/__init__.py +7 -0
- ailoy/models/api_model.py +86 -0
- ailoy/models/local_model.py +44 -0
- ailoy/tools.py +18 -3
- ailoy/utils/__init__.py +0 -0
- ailoy/utils/image.py +11 -0
- ailoy_py-0.0.5.dist-info/DELVEWHEEL +2 -0
- {ailoy_py-0.0.2.dist-info → ailoy_py-0.0.5.dist-info}/METADATA +4 -3
- ailoy_py-0.0.5.dist-info/RECORD +27 -0
- {ailoy_py-0.0.2.dist-info → ailoy_py-0.0.5.dist-info}/WHEEL +1 -1
- ailoy_py.libs/msvcp140-0f885b509a685d2bbfa652fed26b5fb3.dll +0 -0
- ailoy_py.libs/tvm_runtime-c447f68e9478167bfe4a5c4da0fdc0b6.dll +0 -0
- ailoy_py.libs/vcomp140-55aba23cdcd6484fbb06f4155b8ca75a.dll +0 -0
- ailoy_py-0.0.2.dist-info/DELVEWHEEL +0 -2
- ailoy_py-0.0.2.dist-info/RECORD +0 -22
- ailoy_py.libs/msvcp140-9867ece6bcf7e4746fa7e6671b0a17bd.dll +0 -0
- ailoy_py.libs/tvm_runtime-fb74f5f1c02e740e0ef70887ee9b4d3c.dll +0 -0
- ailoy_py.libs/vcomp140-f99ecd9a7e9d3df487b10cf7a201d515.dll +0 -0
- {ailoy_py-0.0.2.dist-info → ailoy_py-0.0.5.dist-info}/entry_points.txt +0 -0
ailoy/__init__.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""""" # start delvewheel patch
|
|
2
|
-
def
|
|
2
|
+
def _delvewheel_patch_1_11_0():
|
|
3
3
|
import os
|
|
4
4
|
if os.path.isdir(libs_dir := os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'ailoy_py.libs'))):
|
|
5
5
|
os.add_dll_directory(libs_dir)
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
del
|
|
8
|
+
_delvewheel_patch_1_11_0()
|
|
9
|
+
del _delvewheel_patch_1_11_0
|
|
10
10
|
# end delvewheel patch
|
|
11
11
|
|
|
12
12
|
if __doc__ is None:
|
|
@@ -27,6 +27,7 @@ if __doc__ is None:
|
|
|
27
27
|
else: # fallback docstring
|
|
28
28
|
__doc__ = "# ailoy-py\n\nPython binding for Ailoy runtime APIs"
|
|
29
29
|
|
|
30
|
-
from .agent import Agent # noqa: F401
|
|
30
|
+
from .agent import Agent, AudioContent, BearerAuthenticator, ImageContent, TextContent, ToolAuthenticator # noqa: F401
|
|
31
|
+
from .models import APIModel, LocalModel # noqa: F401
|
|
31
32
|
from .runtime import AsyncRuntime, Runtime # noqa: F401
|
|
32
33
|
from .vector_store import VectorStore # noqa: F401
|
ailoy/agent.py
CHANGED
|
@@ -1,69 +1,102 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import json
|
|
2
3
|
import warnings
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
|
-
from collections.abc import
|
|
5
|
+
from collections.abc import Callable, Generator
|
|
5
6
|
from functools import partial
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import (
|
|
9
|
+
Annotated,
|
|
8
10
|
Any,
|
|
9
11
|
Literal,
|
|
10
12
|
Optional,
|
|
11
|
-
TypeVar,
|
|
12
13
|
Union,
|
|
13
14
|
)
|
|
14
15
|
from urllib.parse import urlencode, urlparse, urlunparse
|
|
15
16
|
|
|
16
17
|
import jmespath
|
|
17
|
-
from
|
|
18
|
+
from PIL.Image import Image
|
|
19
|
+
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
|
|
18
20
|
from rich.console import Console
|
|
19
21
|
from rich.panel import Panel
|
|
20
22
|
|
|
21
23
|
from ailoy.ailoy_py import generate_uuid
|
|
22
24
|
from ailoy.mcp import MCPServer, MCPTool, StdioServerParameters
|
|
25
|
+
from ailoy.models import APIModel, LocalModel
|
|
23
26
|
from ailoy.runtime import Runtime
|
|
24
27
|
from ailoy.tools import DocstringParsingException, TypeHintParsingException, get_json_schema
|
|
25
|
-
|
|
26
|
-
__all__ = ["Agent"]
|
|
28
|
+
from ailoy.utils.image import pillow_image_to_base64
|
|
27
29
|
|
|
28
30
|
## Types for internal data structures
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
class
|
|
32
|
-
type: Literal["text"]
|
|
33
|
+
class TextContent(BaseModel):
|
|
34
|
+
type: Literal["text"] = "text"
|
|
33
35
|
text: str
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
class ImageContent(BaseModel):
|
|
39
|
+
class UrlData(BaseModel):
|
|
40
|
+
url: str
|
|
41
|
+
|
|
42
|
+
type: Literal["image_url"] = "image_url"
|
|
43
|
+
image_url: UrlData
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def from_url(url: str):
|
|
47
|
+
return ImageContent(image_url={"url": url})
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def from_pillow(image: Image):
|
|
51
|
+
return ImageContent(image_url={"url": pillow_image_to_base64(image)})
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AudioContent(BaseModel):
|
|
55
|
+
class AudioData(BaseModel):
|
|
56
|
+
data: str
|
|
57
|
+
format: Literal["mp3", "wav"]
|
|
58
|
+
|
|
59
|
+
type: Literal["input_audio"] = "input_audio"
|
|
60
|
+
input_audio: AudioData
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def from_bytes(data: bytes, format: Literal["mp3", "wav"]):
|
|
64
|
+
return AudioContent(input_audio={"data": base64.b64encode(data).decode("utf-8"), "format": format})
|
|
65
|
+
|
|
66
|
+
|
|
36
67
|
class FunctionData(BaseModel):
|
|
37
68
|
class FunctionBody(BaseModel):
|
|
38
69
|
name: str
|
|
39
70
|
arguments: Any
|
|
40
71
|
|
|
41
|
-
type: Literal["function"]
|
|
72
|
+
type: Literal["function"] = "function"
|
|
42
73
|
id: Optional[str] = None
|
|
43
74
|
function: FunctionBody
|
|
44
75
|
|
|
45
76
|
|
|
46
77
|
class SystemMessage(BaseModel):
|
|
47
|
-
role: Literal["system"]
|
|
48
|
-
content: list[
|
|
78
|
+
role: Literal["system"] = "system"
|
|
79
|
+
content: str | list[TextContent]
|
|
49
80
|
|
|
50
81
|
|
|
51
82
|
class UserMessage(BaseModel):
|
|
52
|
-
role: Literal["user"]
|
|
53
|
-
content: list[
|
|
83
|
+
role: Literal["user"] = "user"
|
|
84
|
+
content: str | list[TextContent | ImageContent | AudioContent]
|
|
54
85
|
|
|
55
86
|
|
|
56
87
|
class AssistantMessage(BaseModel):
|
|
57
|
-
role: Literal["assistant"]
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
role: Literal["assistant"] = "assistant"
|
|
89
|
+
content: Optional[str | list[TextContent]] = None
|
|
90
|
+
name: Optional[str] = None
|
|
60
91
|
tool_calls: Optional[list[FunctionData]] = None
|
|
61
92
|
|
|
93
|
+
# Non-OpenAI fields
|
|
94
|
+
reasoning: Optional[list[TextContent]] = None
|
|
95
|
+
|
|
62
96
|
|
|
63
97
|
class ToolMessage(BaseModel):
|
|
64
|
-
role: Literal["tool"]
|
|
65
|
-
|
|
66
|
-
content: list[TextData]
|
|
98
|
+
role: Literal["tool"] = "tool"
|
|
99
|
+
content: str | list[TextContent]
|
|
67
100
|
tool_call_id: Optional[str] = None
|
|
68
101
|
|
|
69
102
|
|
|
@@ -76,72 +109,10 @@ Message = Union[
|
|
|
76
109
|
|
|
77
110
|
|
|
78
111
|
class MessageOutput(BaseModel):
|
|
79
|
-
|
|
80
|
-
content: Optional[list[TextData]] = None
|
|
81
|
-
reasoning: Optional[list[TextData]] = None
|
|
82
|
-
tool_calls: Optional[list[FunctionData]] = None
|
|
83
|
-
|
|
84
|
-
message: AssistantMessageDelta
|
|
112
|
+
message: AssistantMessage
|
|
85
113
|
finish_reason: Optional[Literal["stop", "tool_calls", "invalid_tool_call", "length", "error"]] = None
|
|
86
114
|
|
|
87
115
|
|
|
88
|
-
## Types for LLM Model Definitions
|
|
89
|
-
|
|
90
|
-
TVMModelName = Literal["Qwen/Qwen3-0.6B", "Qwen/Qwen3-1.7B", "Qwen/Qwen3-4B", "Qwen/Qwen3-8B"]
|
|
91
|
-
OpenAIModelName = Literal["gpt-4o"]
|
|
92
|
-
ModelName = Union[TVMModelName, OpenAIModelName]
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class TVMModel(BaseModel):
|
|
96
|
-
name: TVMModelName
|
|
97
|
-
quantization: Optional[Literal["q4f16_1"]] = None
|
|
98
|
-
mode: Optional[Literal["interactive"]] = None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class OpenAIModel(BaseModel):
|
|
102
|
-
name: OpenAIModelName
|
|
103
|
-
api_key: str
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
class ModelDescription(BaseModel):
|
|
107
|
-
model_id: str
|
|
108
|
-
component_type: str
|
|
109
|
-
default_system_message: Optional[str] = None
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
model_descriptions: dict[ModelName, ModelDescription] = {
|
|
113
|
-
"Qwen/Qwen3-0.6B": ModelDescription(
|
|
114
|
-
model_id="Qwen/Qwen3-0.6B",
|
|
115
|
-
component_type="tvm_language_model",
|
|
116
|
-
default_system_message="You are Qwen, created by Alibaba Cloud. You are a helpful assistant.",
|
|
117
|
-
),
|
|
118
|
-
"Qwen/Qwen3-1.7B": ModelDescription(
|
|
119
|
-
model_id="Qwen/Qwen3-1.7B",
|
|
120
|
-
component_type="tvm_language_model",
|
|
121
|
-
default_system_message="You are Qwen, created by Alibaba Cloud. You are a helpful assistant.",
|
|
122
|
-
),
|
|
123
|
-
"Qwen/Qwen3-4B": ModelDescription(
|
|
124
|
-
model_id="Qwen/Qwen3-4B",
|
|
125
|
-
component_type="tvm_language_model",
|
|
126
|
-
default_system_message="You are Qwen, created by Alibaba Cloud. You are a helpful assistant.",
|
|
127
|
-
),
|
|
128
|
-
"Qwen/Qwen3-8B": ModelDescription(
|
|
129
|
-
model_id="Qwen/Qwen3-8B",
|
|
130
|
-
component_type="tvm_language_model",
|
|
131
|
-
default_system_message="You are Qwen, created by Alibaba Cloud. You are a helpful assistant.",
|
|
132
|
-
),
|
|
133
|
-
"gpt-4o": ModelDescription(
|
|
134
|
-
model_id="gpt-4o",
|
|
135
|
-
component_type="openai",
|
|
136
|
-
),
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
class ComponentState(BaseModel):
|
|
141
|
-
name: str
|
|
142
|
-
valid: bool
|
|
143
|
-
|
|
144
|
-
|
|
145
116
|
## Types for agent's responses
|
|
146
117
|
|
|
147
118
|
_console = Console(highlight=False, force_jupyter=False, force_terminal=True)
|
|
@@ -149,7 +120,7 @@ _console = Console(highlight=False, force_jupyter=False, force_terminal=True)
|
|
|
149
120
|
|
|
150
121
|
class AgentResponseOutputText(BaseModel):
|
|
151
122
|
type: Literal["output_text", "reasoning"]
|
|
152
|
-
role: Literal["assistant"]
|
|
123
|
+
role: Literal["assistant"] = "assistant"
|
|
153
124
|
is_type_switched: bool = False
|
|
154
125
|
content: str
|
|
155
126
|
|
|
@@ -160,14 +131,14 @@ class AgentResponseOutputText(BaseModel):
|
|
|
160
131
|
|
|
161
132
|
|
|
162
133
|
class AgentResponseToolCall(BaseModel):
|
|
163
|
-
type: Literal["tool_call"]
|
|
164
|
-
role: Literal["assistant"]
|
|
134
|
+
type: Literal["tool_call"] = "tool_call"
|
|
135
|
+
role: Literal["assistant"] = "assistant"
|
|
165
136
|
is_type_switched: bool = False
|
|
166
137
|
content: FunctionData
|
|
167
138
|
|
|
168
139
|
def print(self):
|
|
169
140
|
title = f"[magenta]Tool Call[/magenta]: [bold]{self.content.function.name}[/bold]"
|
|
170
|
-
if self.content.id is not None:
|
|
141
|
+
if self.content.id is not None and len(self.content.id) > 0:
|
|
171
142
|
title += f" ({self.content.id})"
|
|
172
143
|
panel = Panel(
|
|
173
144
|
json.dumps(self.content.function.arguments, indent=2),
|
|
@@ -178,8 +149,8 @@ class AgentResponseToolCall(BaseModel):
|
|
|
178
149
|
|
|
179
150
|
|
|
180
151
|
class AgentResponseToolResult(BaseModel):
|
|
181
|
-
type: Literal["tool_call_result"]
|
|
182
|
-
role: Literal["tool"]
|
|
152
|
+
type: Literal["tool_call_result"] = "tool_call_result"
|
|
153
|
+
role: Literal["tool"] = "tool"
|
|
183
154
|
is_type_switched: bool = False
|
|
184
155
|
content: ToolMessage
|
|
185
156
|
|
|
@@ -194,8 +165,8 @@ class AgentResponseToolResult(BaseModel):
|
|
|
194
165
|
if len(content) > 500:
|
|
195
166
|
content = content[:500] + "...(truncated)"
|
|
196
167
|
|
|
197
|
-
title =
|
|
198
|
-
if self.content.tool_call_id is not None:
|
|
168
|
+
title = "[green]Tool Result[/green]"
|
|
169
|
+
if self.content.tool_call_id is not None and len(self.content.tool_call_id) > 0:
|
|
199
170
|
title += f" ({self.content.tool_call_id})"
|
|
200
171
|
panel = Panel(
|
|
201
172
|
content,
|
|
@@ -206,8 +177,8 @@ class AgentResponseToolResult(BaseModel):
|
|
|
206
177
|
|
|
207
178
|
|
|
208
179
|
class AgentResponseError(BaseModel):
|
|
209
|
-
type: Literal["error"]
|
|
210
|
-
role: Literal["assistant"]
|
|
180
|
+
type: Literal["error"] = "error"
|
|
181
|
+
role: Literal["assistant"] = "assistant"
|
|
211
182
|
is_type_switched: bool = False
|
|
212
183
|
content: str
|
|
213
184
|
|
|
@@ -245,8 +216,11 @@ class ToolParameters(BaseModel):
|
|
|
245
216
|
required: Optional[list[str]] = []
|
|
246
217
|
|
|
247
218
|
|
|
219
|
+
JsonSchemaTypes = Literal["string", "integer", "number", "boolean", "object", "array", "null"]
|
|
220
|
+
|
|
221
|
+
|
|
248
222
|
class ToolParametersProperty(BaseModel):
|
|
249
|
-
type:
|
|
223
|
+
type: JsonSchemaTypes | list[JsonSchemaTypes]
|
|
250
224
|
description: Optional[str] = None
|
|
251
225
|
model_config = ConfigDict(extra="allow")
|
|
252
226
|
|
|
@@ -308,22 +282,6 @@ class BearerAuthenticator(ToolAuthenticator):
|
|
|
308
282
|
return {**request, "headers": headers}
|
|
309
283
|
|
|
310
284
|
|
|
311
|
-
T_Retval = TypeVar("T_Retval")
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def run_async(coro: Callable[..., Awaitable[T_Retval]]) -> T_Retval:
|
|
315
|
-
try:
|
|
316
|
-
import anyio
|
|
317
|
-
|
|
318
|
-
# Running outside async loop
|
|
319
|
-
return anyio.run(lambda: coro)
|
|
320
|
-
except RuntimeError:
|
|
321
|
-
import anyio.from_thread
|
|
322
|
-
|
|
323
|
-
# Already in a running event loop: use anyio from_thread
|
|
324
|
-
return anyio.from_thread.run(coro)
|
|
325
|
-
|
|
326
|
-
|
|
327
285
|
class Agent:
|
|
328
286
|
"""
|
|
329
287
|
The `Agent` class provides a high-level interface for interacting with large language models (LLMs) in Ailoy.
|
|
@@ -337,28 +295,22 @@ class Agent:
|
|
|
337
295
|
def __init__(
|
|
338
296
|
self,
|
|
339
297
|
runtime: Runtime,
|
|
340
|
-
|
|
298
|
+
model: APIModel | LocalModel,
|
|
341
299
|
system_message: Optional[str] = None,
|
|
342
|
-
api_key: Optional[str] = None,
|
|
343
|
-
**attrs,
|
|
344
300
|
):
|
|
345
301
|
"""
|
|
346
302
|
Create an instance.
|
|
347
303
|
|
|
348
304
|
:param runtime: The runtime environment associated with the agent.
|
|
349
|
-
:param
|
|
305
|
+
:param model: The model instance.
|
|
350
306
|
:param system_message: Optional system message to set the initial assistant context.
|
|
351
|
-
:param api_key: (web agent only) The API key for AI API.
|
|
352
|
-
:param attrs: Additional initialization parameters (for `define_component` runtime call)
|
|
353
307
|
:raises ValueError: If model name is not supported or validation fails.
|
|
354
308
|
"""
|
|
355
309
|
self._runtime = runtime
|
|
356
310
|
|
|
357
311
|
# Initialize component state
|
|
358
|
-
self.
|
|
359
|
-
|
|
360
|
-
valid=False,
|
|
361
|
-
)
|
|
312
|
+
self._component_name = generate_uuid()
|
|
313
|
+
self._component_ready = False
|
|
362
314
|
|
|
363
315
|
# Initialize messages
|
|
364
316
|
self._messages: list[Message] = []
|
|
@@ -373,7 +325,7 @@ class Agent:
|
|
|
373
325
|
self._mcp_servers: list[MCPServer] = []
|
|
374
326
|
|
|
375
327
|
# Define the component
|
|
376
|
-
self.define(
|
|
328
|
+
self.define(model)
|
|
377
329
|
|
|
378
330
|
def __del__(self):
|
|
379
331
|
self.delete()
|
|
@@ -384,70 +336,55 @@ class Agent:
|
|
|
384
336
|
def __exit__(self, type, value, traceback):
|
|
385
337
|
self.delete()
|
|
386
338
|
|
|
387
|
-
def define(self,
|
|
339
|
+
def define(self, model: APIModel | LocalModel) -> None:
|
|
388
340
|
"""
|
|
389
341
|
Initializes the agent by defining its model in the runtime.
|
|
390
342
|
This must be called before running the agent. If already initialized, this is a no-op.
|
|
391
|
-
:param
|
|
392
|
-
:param api_key: (web agent only) The API key for AI API.
|
|
393
|
-
:param attrs: Additional initialization parameters (for `define_component` runtime call)
|
|
343
|
+
:param model: The model instance.
|
|
394
344
|
"""
|
|
395
|
-
if self.
|
|
345
|
+
if self._component_ready:
|
|
396
346
|
return
|
|
397
347
|
|
|
398
348
|
if not self._runtime.is_alive():
|
|
399
349
|
raise ValueError("Runtime is currently stopped.")
|
|
400
350
|
|
|
401
|
-
if model_name not in model_descriptions:
|
|
402
|
-
raise ValueError(f"Model `{model_name}` not supported")
|
|
403
|
-
|
|
404
|
-
model_desc = model_descriptions[model_name]
|
|
405
|
-
|
|
406
|
-
# Add model name into attrs
|
|
407
|
-
if "model" not in attrs:
|
|
408
|
-
attrs["model"] = model_desc.model_id
|
|
409
|
-
|
|
410
351
|
# Set default system message if not given; still can be None
|
|
411
352
|
if self._system_message is None:
|
|
412
|
-
self._system_message =
|
|
353
|
+
self._system_message = getattr(model, "default_system_message", None)
|
|
413
354
|
|
|
414
355
|
self.clear_messages()
|
|
415
356
|
|
|
416
|
-
# Add API key
|
|
417
|
-
if api_key:
|
|
418
|
-
attrs["api_key"] = api_key
|
|
419
|
-
|
|
420
357
|
# Call runtime's define
|
|
421
358
|
self._runtime.define(
|
|
422
|
-
|
|
423
|
-
self.
|
|
424
|
-
|
|
359
|
+
model.component_type,
|
|
360
|
+
self._component_name,
|
|
361
|
+
model.to_attrs(),
|
|
425
362
|
)
|
|
426
363
|
|
|
427
364
|
# Mark as defined
|
|
428
|
-
self.
|
|
365
|
+
self._component_ready = True
|
|
429
366
|
|
|
430
367
|
def delete(self) -> None:
|
|
431
368
|
"""
|
|
432
369
|
Deinitializes the agent and releases resources in the runtime.
|
|
433
370
|
This should be called when the agent is no longer needed. If already deinitialized, this is a no-op.
|
|
434
371
|
"""
|
|
435
|
-
if not self.
|
|
372
|
+
if not self._component_ready:
|
|
436
373
|
return
|
|
437
374
|
|
|
438
375
|
if self._runtime.is_alive():
|
|
439
|
-
self._runtime.delete(self.
|
|
376
|
+
self._runtime.delete(self._component_name)
|
|
440
377
|
|
|
441
378
|
self.clear_messages()
|
|
442
379
|
|
|
443
380
|
for mcp_server in self._mcp_servers:
|
|
444
381
|
mcp_server.cleanup()
|
|
445
382
|
|
|
446
|
-
self.
|
|
383
|
+
self._component_ready = False
|
|
447
384
|
|
|
448
385
|
def query(
|
|
449
386
|
self,
|
|
450
|
-
message: str,
|
|
387
|
+
message: str | list[str | Image | dict | TextContent | ImageContent | AudioContent],
|
|
451
388
|
reasoning: bool = False,
|
|
452
389
|
) -> Generator[AgentResponse, None, None]:
|
|
453
390
|
"""
|
|
@@ -458,13 +395,36 @@ class Agent:
|
|
|
458
395
|
:return: An iterator over the output, where each item represents either a generated token from the assistant or a tool call.
|
|
459
396
|
:rtype: Iterator[:class:`AgentResponse`]
|
|
460
397
|
""" # noqa: E501
|
|
461
|
-
if not self.
|
|
398
|
+
if not self._component_ready:
|
|
462
399
|
raise ValueError("Agent is not valid. Create one or define newly.")
|
|
463
400
|
|
|
464
401
|
if not self._runtime.is_alive():
|
|
465
402
|
raise ValueError("Runtime is currently stopped.")
|
|
466
403
|
|
|
467
|
-
|
|
404
|
+
if isinstance(message, str):
|
|
405
|
+
self._messages.append(UserMessage(content=[TextContent(text=message)]))
|
|
406
|
+
elif isinstance(message, list):
|
|
407
|
+
if len(message) == 0:
|
|
408
|
+
raise ValueError("Message is empty")
|
|
409
|
+
|
|
410
|
+
contents = []
|
|
411
|
+
for content in message:
|
|
412
|
+
if isinstance(content, str):
|
|
413
|
+
contents.append(TextContent(text=content))
|
|
414
|
+
elif isinstance(content, Image):
|
|
415
|
+
contents.append(ImageContent.from_pillow(image=content))
|
|
416
|
+
elif isinstance(content, dict):
|
|
417
|
+
ta: TypeAdapter[TextContent | ImageContent | AudioContent] = TypeAdapter(
|
|
418
|
+
Annotated[TextContent | ImageContent | AudioContent, Field(discriminator="type")]
|
|
419
|
+
)
|
|
420
|
+
validated_content = ta.validate_python(content)
|
|
421
|
+
contents.append(validated_content)
|
|
422
|
+
else:
|
|
423
|
+
contents.append(content)
|
|
424
|
+
|
|
425
|
+
self._messages.append(UserMessage(content=contents))
|
|
426
|
+
else:
|
|
427
|
+
raise ValueError(f"Invalid message type: {type(message)}")
|
|
468
428
|
|
|
469
429
|
prev_resp_type = None
|
|
470
430
|
|
|
@@ -480,7 +440,7 @@ class Agent:
|
|
|
480
440
|
assistant_content = None
|
|
481
441
|
assistant_tool_calls = None
|
|
482
442
|
finish_reason = ""
|
|
483
|
-
for result in self._runtime.call_iter_method(self.
|
|
443
|
+
for result in self._runtime.call_iter_method(self._component_name, "infer", infer_args):
|
|
484
444
|
msg = MessageOutput.model_validate(result)
|
|
485
445
|
|
|
486
446
|
if msg.message.reasoning:
|
|
@@ -491,13 +451,16 @@ class Agent:
|
|
|
491
451
|
assistant_reasoning[0].text += v.text
|
|
492
452
|
resp = AgentResponseOutputText(
|
|
493
453
|
type="reasoning",
|
|
494
|
-
role="assistant",
|
|
495
454
|
is_type_switched=(prev_resp_type != "reasoning"),
|
|
496
455
|
content=v.text,
|
|
497
456
|
)
|
|
498
457
|
prev_resp_type = resp.type
|
|
499
458
|
yield resp
|
|
500
|
-
if msg.message.content:
|
|
459
|
+
if msg.message.content is not None:
|
|
460
|
+
# Canonicalize message content to the array of TextContent
|
|
461
|
+
if isinstance(msg.message.content, str):
|
|
462
|
+
msg.message.content = [TextContent(text=msg.message.content)]
|
|
463
|
+
|
|
501
464
|
for v in msg.message.content:
|
|
502
465
|
if not assistant_content:
|
|
503
466
|
assistant_content = [v]
|
|
@@ -505,7 +468,6 @@ class Agent:
|
|
|
505
468
|
assistant_content[0].text += v.text
|
|
506
469
|
resp = AgentResponseOutputText(
|
|
507
470
|
type="output_text",
|
|
508
|
-
role="assistant",
|
|
509
471
|
is_type_switched=(prev_resp_type != "output_text"),
|
|
510
472
|
content=v.text,
|
|
511
473
|
)
|
|
@@ -518,8 +480,6 @@ class Agent:
|
|
|
518
480
|
else:
|
|
519
481
|
assistant_tool_calls.append(v)
|
|
520
482
|
resp = AgentResponseToolCall(
|
|
521
|
-
type="tool_call",
|
|
522
|
-
role="assistant",
|
|
523
483
|
is_type_switched=True,
|
|
524
484
|
content=v,
|
|
525
485
|
)
|
|
@@ -532,7 +492,6 @@ class Agent:
|
|
|
532
492
|
# Append output
|
|
533
493
|
self._messages.append(
|
|
534
494
|
AssistantMessage(
|
|
535
|
-
role="assistant",
|
|
536
495
|
reasoning=assistant_reasoning,
|
|
537
496
|
content=assistant_content,
|
|
538
497
|
tool_calls=assistant_tool_calls,
|
|
@@ -550,18 +509,16 @@ class Agent:
|
|
|
550
509
|
raise RuntimeError("Tool not found")
|
|
551
510
|
tool_result = tool_.call(**tool_call.function.arguments)
|
|
552
511
|
return ToolMessage(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
tool_call_id=tool_call.id
|
|
512
|
+
content=[
|
|
513
|
+
TextContent(text=tool_result if isinstance(tool_result, str) else json.dumps(tool_result))
|
|
514
|
+
],
|
|
515
|
+
tool_call_id=tool_call.id,
|
|
557
516
|
)
|
|
558
517
|
|
|
559
518
|
tool_call_results = [run_tool(tc) for tc in assistant_tool_calls]
|
|
560
519
|
for result_msg in tool_call_results:
|
|
561
520
|
self._messages.append(result_msg)
|
|
562
521
|
resp = AgentResponseToolResult(
|
|
563
|
-
type="tool_call_result",
|
|
564
|
-
role="tool",
|
|
565
522
|
is_type_switched=True,
|
|
566
523
|
content=result_msg,
|
|
567
524
|
)
|
|
@@ -571,6 +528,7 @@ class Agent:
|
|
|
571
528
|
continue
|
|
572
529
|
|
|
573
530
|
# Finish this generator
|
|
531
|
+
yield AgentResponseOutputText(type="output_text", content="\n")
|
|
574
532
|
break
|
|
575
533
|
|
|
576
534
|
def get_messages(self) -> list[Message]:
|
|
@@ -589,9 +547,7 @@ class Agent:
|
|
|
589
547
|
"""
|
|
590
548
|
self._messages.clear()
|
|
591
549
|
if self._system_message is not None:
|
|
592
|
-
self._messages.append(
|
|
593
|
-
SystemMessage(role="system", content=[TextData(type="text", text=self._system_message)])
|
|
594
|
-
)
|
|
550
|
+
self._messages.append(SystemMessage(role="system", content=[TextContent(text=self._system_message)]))
|
|
595
551
|
|
|
596
552
|
def print(self, resp: AgentResponse):
|
|
597
553
|
resp.print()
|
|
@@ -779,7 +735,7 @@ class Agent:
|
|
|
779
735
|
continue
|
|
780
736
|
|
|
781
737
|
desc = ToolDescription(
|
|
782
|
-
name=f"{name}
|
|
738
|
+
name=f"{name}-{tool.name}", description=tool.description, parameters=tool.inputSchema
|
|
783
739
|
)
|
|
784
740
|
|
|
785
741
|
def call(tool: MCPTool, **inputs: dict[str, Any]) -> list[str]:
|
|
@@ -803,4 +759,16 @@ class Agent:
|
|
|
803
759
|
mcp_server.cleanup()
|
|
804
760
|
|
|
805
761
|
# Remove tools registered from the MCP server
|
|
806
|
-
self._tools = list(filter(lambda t: not t.desc.name.startswith(f"{mcp_server.name}
|
|
762
|
+
self._tools = list(filter(lambda t: not t.desc.name.startswith(f"{mcp_server.name}-"), self._tools))
|
|
763
|
+
|
|
764
|
+
def get_tools(self):
|
|
765
|
+
"""
|
|
766
|
+
Get the list of registered tools.
|
|
767
|
+
"""
|
|
768
|
+
return self._tools
|
|
769
|
+
|
|
770
|
+
def clear_tools(self):
|
|
771
|
+
"""
|
|
772
|
+
Clear the registered tools.
|
|
773
|
+
"""
|
|
774
|
+
self._tools.clear()
|
|
Binary file
|
ailoy/mcp.py
CHANGED
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
import json
|
|
3
3
|
import multiprocessing
|
|
4
4
|
import platform
|
|
5
|
-
import
|
|
5
|
+
import tempfile
|
|
6
6
|
from multiprocessing.connection import Connection
|
|
7
7
|
from typing import Annotated, Any, Literal, Union
|
|
8
8
|
|
|
@@ -13,6 +13,7 @@ from mcp.client.stdio import (
|
|
|
13
13
|
StdioServerParameters,
|
|
14
14
|
stdio_client,
|
|
15
15
|
)
|
|
16
|
+
from mcp.shared.exceptions import McpError
|
|
16
17
|
from pydantic import BaseModel, Field, TypeAdapter
|
|
17
18
|
|
|
18
19
|
__all__ = ["MCPServer"]
|
|
@@ -73,11 +74,15 @@ class MCPServer:
|
|
|
73
74
|
self._parent_conn, self._child_conn = multiprocessing.Pipe()
|
|
74
75
|
|
|
75
76
|
ctx = multiprocessing.get_context("fork" if platform.system() != "Windows" else "spawn")
|
|
76
|
-
self._proc = ctx.Process(target=self._run_process, args=(self._child_conn,))
|
|
77
|
+
self._proc: multiprocessing.Process = ctx.Process(target=self._run_process, args=(self._child_conn,))
|
|
77
78
|
self._proc.start()
|
|
78
79
|
|
|
79
80
|
# Wait for subprocess to signal initialization complete
|
|
80
|
-
|
|
81
|
+
try:
|
|
82
|
+
self._recv_response()
|
|
83
|
+
except RuntimeError as e:
|
|
84
|
+
self.cleanup()
|
|
85
|
+
raise e
|
|
81
86
|
|
|
82
87
|
def __del__(self):
|
|
83
88
|
self.cleanup()
|
|
@@ -86,52 +91,59 @@ class MCPServer:
|
|
|
86
91
|
asyncio.run(self._process_main(conn))
|
|
87
92
|
|
|
88
93
|
async def _process_main(self, conn: Connection):
|
|
89
|
-
|
|
90
|
-
async with
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
await session.initialize()
|
|
94
|
-
conn.send(ResultMessage(result=True).model_dump())
|
|
95
|
-
except Exception as e:
|
|
96
|
-
conn.send(ErrorMessage(error=f"Failed to initialize MCP subprocess: {e}").model_dump())
|
|
97
|
-
|
|
98
|
-
while True:
|
|
99
|
-
if not conn.poll(0.1):
|
|
100
|
-
await asyncio.sleep(0.1)
|
|
101
|
-
continue
|
|
102
|
-
|
|
94
|
+
with tempfile.TemporaryFile(mode="w+t") as _errlog:
|
|
95
|
+
async with stdio_client(self.params, errlog=_errlog) as (read, write):
|
|
96
|
+
async with ClientSession(read, write) as session:
|
|
97
|
+
# Notify to main process that the initialization has been finished and ready to receive requests
|
|
103
98
|
try:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
99
|
+
await session.initialize()
|
|
100
|
+
conn.send(ResultMessage(result=True).model_dump())
|
|
101
|
+
except McpError:
|
|
102
|
+
_errlog.seek(0)
|
|
103
|
+
error = _errlog.read()
|
|
104
|
+
conn.send(
|
|
105
|
+
ErrorMessage(
|
|
106
|
+
error=f"Failed to initialize MCP subprocess. Check the error output below.\n\n{error}"
|
|
107
|
+
).model_dump()
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
while True:
|
|
111
|
+
if not conn.poll(0.1):
|
|
112
|
+
await asyncio.sleep(0.1)
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
raw = conn.recv()
|
|
117
|
+
req = TypeAdapter(RequestMessage).validate_python(raw)
|
|
118
|
+
|
|
119
|
+
if isinstance(req, ListToolsRequest):
|
|
120
|
+
result = await session.list_tools()
|
|
121
|
+
conn.send(ResultMessage(result=result.tools).model_dump())
|
|
122
|
+
|
|
123
|
+
elif isinstance(req, CallToolRequest):
|
|
124
|
+
result = await session.call_tool(req.tool.name, req.arguments)
|
|
125
|
+
contents: list[str] = []
|
|
126
|
+
for item in result.content:
|
|
127
|
+
if isinstance(item, mcp_types.TextContent):
|
|
128
|
+
try:
|
|
129
|
+
content = json.loads(item.text)
|
|
130
|
+
contents.append(json.dumps(content))
|
|
131
|
+
except json.JSONDecodeError:
|
|
132
|
+
contents.append(item.text)
|
|
133
|
+
elif isinstance(item, mcp_types.ImageContent):
|
|
134
|
+
contents.append(item.data)
|
|
135
|
+
elif isinstance(item, mcp_types.EmbeddedResource):
|
|
136
|
+
if isinstance(item.resource, mcp_types.TextResourceContents):
|
|
137
|
+
contents.append(item.resource.text)
|
|
138
|
+
else:
|
|
139
|
+
contents.append(item.resource.blob)
|
|
140
|
+
conn.send(ResultMessage(result=contents).model_dump())
|
|
141
|
+
|
|
142
|
+
elif isinstance(req, ShutdownRequest):
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
conn.send(ErrorMessage(error=str(e)).model_dump())
|
|
135
147
|
|
|
136
148
|
def _send_request(self, msg: RequestMessage):
|
|
137
149
|
self._parent_conn.send(msg.model_dump())
|
ailoy/models/__init__.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Literal, Optional, get_args
|
|
2
|
+
|
|
3
|
+
from pydantic import model_validator
|
|
4
|
+
from pydantic.dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
OpenAIModelId = Literal[
|
|
7
|
+
"o4-mini",
|
|
8
|
+
"o3",
|
|
9
|
+
"o3-pro",
|
|
10
|
+
"o3-mini",
|
|
11
|
+
"gpt-4o",
|
|
12
|
+
"gpt-4o-mini",
|
|
13
|
+
"gpt-4.1",
|
|
14
|
+
"gpt-4.1-mini",
|
|
15
|
+
"gpt-4.1-nano",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
GeminiModelId = Literal[
|
|
19
|
+
"gemini-2.5-flash",
|
|
20
|
+
"gemini-2.5-pro",
|
|
21
|
+
"gemini-2.0-flash",
|
|
22
|
+
"gemini-1.5-flash",
|
|
23
|
+
"gemini-1.5-pro",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
ClaudeModelId = Literal[
|
|
27
|
+
"claude-sonnet-4-20250514",
|
|
28
|
+
"claude-3-7-sonnet-20250219",
|
|
29
|
+
"claude-3-5-sonnet-20241022",
|
|
30
|
+
"claude-3-5-sonnet-20240620",
|
|
31
|
+
"claude-opus-4-20250514",
|
|
32
|
+
"claude-3-opus-20240229",
|
|
33
|
+
"claude-3-5-haiku-20241022",
|
|
34
|
+
"claude-3-haiku-20240307",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
GrokModelId = Literal[
|
|
38
|
+
"grok-4",
|
|
39
|
+
"grok-4-0709",
|
|
40
|
+
"grok-3",
|
|
41
|
+
"grok-3-fast",
|
|
42
|
+
"grok-3-mini",
|
|
43
|
+
"grok-3-mini-fast",
|
|
44
|
+
"grok-2",
|
|
45
|
+
"grok-2-1212",
|
|
46
|
+
"grok-2-vision-1212",
|
|
47
|
+
"grok-2-image-1212",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
APIModelProvider = Literal["openai", "gemini", "claude", "grok"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class APIModel:
|
|
55
|
+
id: OpenAIModelId | GeminiModelId | ClaudeModelId | str
|
|
56
|
+
api_key: str
|
|
57
|
+
provider: Optional[APIModelProvider] = None
|
|
58
|
+
|
|
59
|
+
@model_validator(mode="after")
|
|
60
|
+
def validate_provider(self):
|
|
61
|
+
if self.provider is None:
|
|
62
|
+
if self.id in get_args(OpenAIModelId):
|
|
63
|
+
self.provider = "openai"
|
|
64
|
+
elif self.id in get_args(GeminiModelId):
|
|
65
|
+
self.provider = "gemini"
|
|
66
|
+
elif self.id in get_args(ClaudeModelId):
|
|
67
|
+
self.provider = "claude"
|
|
68
|
+
elif self.id in get_args(GrokModelId):
|
|
69
|
+
self.provider = "grok"
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f'Failed to infer the model provider based on the model id "{self.id}". '
|
|
73
|
+
"Please provide an explicit model provider."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def component_type(self) -> str:
|
|
80
|
+
return self.provider
|
|
81
|
+
|
|
82
|
+
def to_attrs(self):
|
|
83
|
+
return {
|
|
84
|
+
"model": self.id,
|
|
85
|
+
"api_key": self.api_key,
|
|
86
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Literal, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic.dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
LocalModelBackend = Literal["tvm"]
|
|
6
|
+
LocalModelId = Literal[
|
|
7
|
+
"Qwen/Qwen3-0.6B",
|
|
8
|
+
"Qwen/Qwen3-1.7B",
|
|
9
|
+
"Qwen/Qwen3-4B",
|
|
10
|
+
"Qwen/Qwen3-8B",
|
|
11
|
+
"Qwen/Qwen3-14B",
|
|
12
|
+
"Qwen/Qwen3-32B",
|
|
13
|
+
"Qwen/Qwen3-30B-A3B",
|
|
14
|
+
]
|
|
15
|
+
Quantization = Literal["q4f16_1"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class LocalModel:
|
|
20
|
+
id: LocalModelId
|
|
21
|
+
backend: LocalModelBackend = "tvm"
|
|
22
|
+
quantization: Quantization = "q4f16_1"
|
|
23
|
+
device: int = 0
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def default_system_message(self) -> Optional[str]:
|
|
27
|
+
if self.id.startswith("Qwen"):
|
|
28
|
+
return "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def component_type(self) -> str:
|
|
33
|
+
if self.backend == "tvm":
|
|
34
|
+
return "tvm_language_model"
|
|
35
|
+
raise ValueError(f"Unknown local model backend: {self.backend}")
|
|
36
|
+
|
|
37
|
+
def to_attrs(self) -> dict:
|
|
38
|
+
if self.backend == "tvm":
|
|
39
|
+
return {
|
|
40
|
+
"model": self.id,
|
|
41
|
+
"quantization": self.quantization,
|
|
42
|
+
"device": self.device,
|
|
43
|
+
}
|
|
44
|
+
raise ValueError(f"Unknown local model backend: {self.backend}")
|
ailoy/tools.py
CHANGED
|
@@ -5,6 +5,7 @@ import types
|
|
|
5
5
|
from typing import (
|
|
6
6
|
Any,
|
|
7
7
|
Callable,
|
|
8
|
+
Literal,
|
|
8
9
|
Optional,
|
|
9
10
|
Union,
|
|
10
11
|
get_args,
|
|
@@ -41,7 +42,7 @@ class DocstringParsingException(Exception):
|
|
|
41
42
|
pass
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
def _get_json_schema_type(param_type:
|
|
45
|
+
def _get_json_schema_type(param_type: type) -> dict[str, str]:
|
|
45
46
|
type_mapping = {
|
|
46
47
|
int: {"type": "integer"},
|
|
47
48
|
float: {"type": "number"},
|
|
@@ -85,6 +86,20 @@ def _parse_type_hint(hint: str) -> dict:
|
|
|
85
86
|
return_dict["nullable"] = True
|
|
86
87
|
return return_dict
|
|
87
88
|
|
|
89
|
+
elif origin is Literal and len(args) > 0:
|
|
90
|
+
LITERAL_TYPES = (int, float, str, bool, type(None))
|
|
91
|
+
args_types = []
|
|
92
|
+
for arg in args:
|
|
93
|
+
if type(arg) not in LITERAL_TYPES:
|
|
94
|
+
raise TypeHintParsingException("Only the valid python literals can be listed in typing.Literal.")
|
|
95
|
+
arg_type = _get_json_schema_type(type(arg)).get("type")
|
|
96
|
+
if arg_type is not None and arg_type not in args_types:
|
|
97
|
+
args_types.append(arg_type)
|
|
98
|
+
return {
|
|
99
|
+
"type": args_types.pop() if len(args_types) == 1 else list(args_types),
|
|
100
|
+
"enum": list(args),
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
elif origin is list:
|
|
89
104
|
if not args:
|
|
90
105
|
return {"type": "array"}
|
|
@@ -100,13 +115,13 @@ def _parse_type_hint(hint: str) -> dict:
|
|
|
100
115
|
f"The type hint {str(hint).replace('typing.', '')} is a Tuple with a single element, which "
|
|
101
116
|
"we do not automatically convert to JSON schema as it is rarely necessary. If this input can contain "
|
|
102
117
|
"more than one element, we recommend "
|
|
103
|
-
"using a
|
|
118
|
+
"using a list[] type instead, or if it really is a single element, remove the tuple[] wrapper and just "
|
|
104
119
|
"pass the element directly."
|
|
105
120
|
)
|
|
106
121
|
if ... in args:
|
|
107
122
|
raise TypeHintParsingException(
|
|
108
123
|
"Conversion of '...' is not supported in Tuple type hints. "
|
|
109
|
-
"Use
|
|
124
|
+
"Use list[] types for variable-length"
|
|
110
125
|
" inputs instead."
|
|
111
126
|
)
|
|
112
127
|
return {"type": "array", "prefixItems": [_parse_type_hint(t) for t in args]}
|
ailoy/utils/__init__.py
ADDED
|
File without changes
|
ailoy/utils/image.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
|
|
4
|
+
from PIL.Image import Image
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def pillow_image_to_base64(img: Image):
|
|
8
|
+
buffered = io.BytesIO()
|
|
9
|
+
img.save(buffered, format=img.format)
|
|
10
|
+
b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
11
|
+
return f"data:image/{img.format.lower()};base64,{b64}"
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Version: 1.11.0
|
|
2
|
+
Arguments: ['C:\\Program Files\\Python312\\Lib\\site-packages\\delvewheel\\__main__.py', 'repair', '-w', 'wheelhouse', 'C:\\workspace\\bindings\\python\\dist\\ailoy_py-0.0.5-cp312-cp312-win_amd64.whl', '--add-path', 'C:\\workspace\\bindings\\python\\build\\_deps\\tvm-build\\Release', '--exclude', 'vulkan-1.dll']
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ailoy-py
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Python binding for Ailoy runtime APIs
|
|
5
5
|
Author-Email: "Brekkylab Inc." <contact@brekkylab.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -18,6 +18,7 @@ Requires-Dist: anyio>=4.9.0
|
|
|
18
18
|
Requires-Dist: jmespath>=1.0.1
|
|
19
19
|
Requires-Dist: mcp>=1.8.0
|
|
20
20
|
Requires-Dist: numpy>=2.0.2
|
|
21
|
+
Requires-Dist: pillow>=11.2.1
|
|
21
22
|
Requires-Dist: pydantic>=2.11.4
|
|
22
23
|
Requires-Dist: rich>=14.0.0
|
|
23
24
|
Requires-Dist: typer>=0.15.4
|
|
@@ -38,14 +39,14 @@ pip install ailoy-py
|
|
|
38
39
|
## Quickstart
|
|
39
40
|
|
|
40
41
|
```python
|
|
41
|
-
from ailoy import Runtime, Agent
|
|
42
|
+
from ailoy import Runtime, Agent, LocalModel
|
|
42
43
|
|
|
43
44
|
# The runtime must be started to use Ailoy
|
|
44
45
|
rt = Runtime()
|
|
45
46
|
|
|
46
47
|
# Defines an agent
|
|
47
48
|
# During this step, the model parameters are downloaded and the LLM is set up for execution
|
|
48
|
-
with Agent(rt,
|
|
49
|
+
with Agent(rt, LocalModel("Qwen/Qwen3-0.6B")) as agent:
|
|
49
50
|
# This is where the actual LLM call happens
|
|
50
51
|
for resp in agent.query("Please give me a short poem about AI"):
|
|
51
52
|
agent.print(resp)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
ailoy/agent.py,sha256=SqpnpywdXg28UjtBHcU0k446s1DxW-g6jWCNEHQxGAk,27728
|
|
2
|
+
ailoy/ailoy_py.cp312-win_amd64.pyd,sha256=-0SFpqXhB9GM0U-Fh3T3H-rC6jQpYflen0OydCU9crg,12851200
|
|
3
|
+
ailoy/ailoy_py.pyi,sha256=wr_-KGTI-O9usNdzTyszn4mAjnjiV2cJf0aBct2PKrU,1020
|
|
4
|
+
ailoy/mcp.py,sha256=mwxfEZj7Je2PJTwwM9zVQ44AurrA5xY5TWVsAUkk9T8,6971
|
|
5
|
+
ailoy/runtime.py,sha256=Bic6acq6NVuZchyuB51lm-58Xe_blRJbIE3gF9Xk22w,10833
|
|
6
|
+
ailoy/tools.py,sha256=OQZHMGOWovx0uWlYeWN2BoPG_G5rMW5HXCP3yTvtLDk,9081
|
|
7
|
+
ailoy/vector_store.py,sha256=507HG-3W_glmZsKDUcFygdaPg3d7xyHbAFczE3diGwo,7723
|
|
8
|
+
ailoy/__init__.py,sha256=gR2i8fAFUqreAxQmdJ38Fw7QF6SVtlXLhGbRBL-ve_Q,1135
|
|
9
|
+
ailoy/cli/model.py,sha256=t1-uO5vRDUomxIDpuMduPVCOKBR9VHMXqMcz38m_-YI,3091
|
|
10
|
+
ailoy/cli/__main__.py,sha256=JUWtde3gYqKxuCtihgr5g2MAQgj151wv41OOZv2uHsQ,190
|
|
11
|
+
ailoy/models/api_model.py,sha256=q99HKACIutjkBPyVRFbdsrFoD8DYI9bayLM0ZBcwupM,2176
|
|
12
|
+
ailoy/models/local_model.py,sha256=Z7TwScM2V1U99ErqgO2YVcSOc85wEmIie8xTXP7wMBA,1250
|
|
13
|
+
ailoy/models/__init__.py,sha256=AfJlZYoacXf9de0LOttkFNirwS8XFaZcHUg7dA7xQsw,124
|
|
14
|
+
ailoy/presets/tools/calculator.json,sha256=8G5t1PGyM7ZmX97WNfrF-H-x7LR8s_O15ppY_APFnVo,1064
|
|
15
|
+
ailoy/presets/tools/frankfurter.json,sha256=nM26DjkiPgamhL1Re7Gi_fnhUQyegDxcquEFBD0af5g,1185
|
|
16
|
+
ailoy/presets/tools/nytimes.json,sha256=diio4FSJGHvumX05MQ0RgrqZzggpjyX8ekBPZ4NCmPo,944
|
|
17
|
+
ailoy/presets/tools/tmdb.json,sha256=o9dg9bdfFhvgrhVGw5hP3V0EgGvBzm5ZrTtxrb1PerM,7231
|
|
18
|
+
ailoy/utils/image.py,sha256=hMNrwexs_8Dz0QK2CUI8Kyw3_X3v4mWxtskg9SLBLCg,299
|
|
19
|
+
ailoy/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
ailoy_py-0.0.5.dist-info/DELVEWHEEL,sha256=qEWJ3W3yQoEChUttBroevRyphLsxojcqOpS09upToVg,330
|
|
21
|
+
ailoy_py-0.0.5.dist-info/entry_points.txt,sha256=gVG45uDE6kef0wm6SEMYSgZgRNNRhSAeP2n2lPR00dI,50
|
|
22
|
+
ailoy_py-0.0.5.dist-info/METADATA,sha256=WpOovIib4t4AWYNmIBRRixucdz9-wBetyxyDaLjzOk4,2053
|
|
23
|
+
ailoy_py-0.0.5.dist-info/RECORD,,
|
|
24
|
+
ailoy_py-0.0.5.dist-info/WHEEL,sha256=TcMXEVBP2SQds4YZwJ6flDTTNRzCE5owNAganfIqM0g,106
|
|
25
|
+
ailoy_py.libs/msvcp140-0f885b509a685d2bbfa652fed26b5fb3.dll,sha256=D4hbUJpoXSu_plL-0mtfsx2I-9qwqXjGQdHHuKpGCqk,557728
|
|
26
|
+
ailoy_py.libs/tvm_runtime-c447f68e9478167bfe4a5c4da0fdc0b6.dll,sha256=wFKjnNpi8IF6mq-pll4BqmdJe9-gKHxV4GGOEw1CLVE,2531328
|
|
27
|
+
ailoy_py.libs/vcomp140-55aba23cdcd6484fbb06f4155b8ca75a.dll,sha256=VauiPNzWSE-7BvQVW4ynWt_OeogfEK_QxJRXFl5ncWQ,193152
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
Version: 1.10.1
|
|
2
|
-
Arguments: ['C:\\hostedtoolcache\\windows\\Python\\3.12.10\\x64\\Scripts\\delvewheel', 'repair', '-w', 'wheelhouse', 'D:\\a\\ailoy\\ailoy\\bindings\\python\\dist\\ailoy_py-0.0.2-cp312-cp312-win_amd64.whl', '--add-path', 'D:\\a\\ailoy\\ailoy\\bindings\\python\\build\\_deps\\tvm-build\\Release', '--exclude', 'vulkan-1.dll']
|
ailoy_py-0.0.2.dist-info/RECORD
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
ailoy/agent.py,sha256=-dT8cmfnVAt80xjpBC-I58jo7TTTiS397trtAMMx6mg,28419
|
|
2
|
-
ailoy/ailoy_py.cp312-win_amd64.pyd,sha256=BETng93IFLwUhO9FbYjrajurfNQCwBm-IlmBKPJNHI8,11578880
|
|
3
|
-
ailoy/ailoy_py.pyi,sha256=wr_-KGTI-O9usNdzTyszn4mAjnjiV2cJf0aBct2PKrU,1020
|
|
4
|
-
ailoy/mcp.py,sha256=Ta7u8QAsu2dMThFAhelh577aiBX3OpWuptCOofb1kD0,6349
|
|
5
|
-
ailoy/runtime.py,sha256=Bic6acq6NVuZchyuB51lm-58Xe_blRJbIE3gF9Xk22w,10833
|
|
6
|
-
ailoy/tools.py,sha256=5Q7Q59ozeKWXwH5q9WY_9nuI3ALMAorKPgX6LNPWaJg,8412
|
|
7
|
-
ailoy/vector_store.py,sha256=507HG-3W_glmZsKDUcFygdaPg3d7xyHbAFczE3diGwo,7723
|
|
8
|
-
ailoy/__init__.py,sha256=krEldcQGrWWEJc2qWhL9oCoRHxD_Eyakz42gszfo6Ic,998
|
|
9
|
-
ailoy/cli/model.py,sha256=t1-uO5vRDUomxIDpuMduPVCOKBR9VHMXqMcz38m_-YI,3091
|
|
10
|
-
ailoy/cli/__main__.py,sha256=JUWtde3gYqKxuCtihgr5g2MAQgj151wv41OOZv2uHsQ,190
|
|
11
|
-
ailoy/presets/tools/calculator.json,sha256=8G5t1PGyM7ZmX97WNfrF-H-x7LR8s_O15ppY_APFnVo,1064
|
|
12
|
-
ailoy/presets/tools/frankfurter.json,sha256=nM26DjkiPgamhL1Re7Gi_fnhUQyegDxcquEFBD0af5g,1185
|
|
13
|
-
ailoy/presets/tools/nytimes.json,sha256=diio4FSJGHvumX05MQ0RgrqZzggpjyX8ekBPZ4NCmPo,944
|
|
14
|
-
ailoy/presets/tools/tmdb.json,sha256=o9dg9bdfFhvgrhVGw5hP3V0EgGvBzm5ZrTtxrb1PerM,7231
|
|
15
|
-
ailoy_py-0.0.2.dist-info/DELVEWHEEL,sha256=UxMBHG54OPX751rYwjUBgZn5PvqfKWnG2lJdaJtKpS4,340
|
|
16
|
-
ailoy_py-0.0.2.dist-info/entry_points.txt,sha256=gVG45uDE6kef0wm6SEMYSgZgRNNRhSAeP2n2lPR00dI,50
|
|
17
|
-
ailoy_py-0.0.2.dist-info/METADATA,sha256=B5RbxeITquJfdiw9bhA6w02q9OvLkuFH7jMRg6Lxc2A,2010
|
|
18
|
-
ailoy_py-0.0.2.dist-info/RECORD,,
|
|
19
|
-
ailoy_py-0.0.2.dist-info/WHEEL,sha256=jS-OUpQ7pzysZBq4lYO_axuVXwmbi12-xcZ3P0PA_yc,106
|
|
20
|
-
ailoy_py.libs/msvcp140-9867ece6bcf7e4746fa7e6671b0a17bd.dll,sha256=cDrXxV8uCWYH7Gp3qLX1LVNdvnH93wIel8vLvCP3SSo,576128
|
|
21
|
-
ailoy_py.libs/tvm_runtime-fb74f5f1c02e740e0ef70887ee9b4d3c.dll,sha256=xDITlhtd-Vz7-fzP2S3siaCWI4TXCDNe_-meXwqA3vc,2158080
|
|
22
|
-
ailoy_py.libs/vcomp140-f99ecd9a7e9d3df487b10cf7a201d515.dll,sha256=42pcXjKbx6811PqmEKKa7ugmp4EOBnEvD1Tpss_mpyg,192112
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|