ai-microcore 5.0.0.dev7__tar.gz → 5.0.1__tar.gz
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.
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/PKG-INFO +51 -13
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/README.md +50 -12
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/__init__.py +2 -2
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/_llm_functions.py +14 -4
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/ai_func/__init__.py +2 -1
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/file_storage.py +17 -14
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/images.py +1 -1
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm/google_genai.py +5 -3
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm/openai.py +64 -16
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/LICENSE +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/_env.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/_prepare_llm_args.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/ai_func/ai-func.json.j2 +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/ai_func/ai-func.pythonic.j2 +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/ai_func/ai-func.tag.j2 +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/ai_modules.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/configuration.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/embedding_db/__init__.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/embedding_db/chromadb.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/embedding_db/qdrant.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/file_cache.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/interactive_setup.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/json_parsing.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm/__init__.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm/anthropic.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm/local_llm.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm/local_transformers.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm/shared.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/llm_backends.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/lm_client.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/logging.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/mcp.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/message_types.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/metrics.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/presets.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/python.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/templating/__init__.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/templating/jinja2.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/text2speech/elevenlabs.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/tokenizing.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/types.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/ui.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/utils.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/wrappers/__init__.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/wrappers/llm_response_wrapper.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/microcore/wrappers/prompt_wrapper.py +0 -0
- {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.1}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-microcore
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.1
|
|
4
4
|
Summary: # Minimalistic Foundation for AI Applications
|
|
5
5
|
Keywords: llm,large language models,ai,similarity search,ai search,gpt,openai,framework,adapter,anthropic,google gemini,google vertex ai
|
|
6
6
|
Author-email: Vitalii Stepanenko <mail@vitaliy.in>
|
|
@@ -116,21 +116,14 @@ For the full list of available configuration options, you may also check [`micro
|
|
|
116
116
|
|
|
117
117
|
### Installing vendor-specific packages
|
|
118
118
|
For models working not via OpenAI API, you may need to install additional packages:
|
|
119
|
-
#### Anthropic Claude
|
|
119
|
+
#### Anthropic Claude
|
|
120
120
|
```bash
|
|
121
121
|
pip install anthropic
|
|
122
122
|
```
|
|
123
|
-
#### Google Gemini via AI Studio
|
|
123
|
+
#### Google Gemini via AI Studio or Vertex AI
|
|
124
124
|
```bash
|
|
125
|
-
pip install google-
|
|
125
|
+
pip install google-genai
|
|
126
126
|
```
|
|
127
|
-
#### Google Gemini via Vertex AI
|
|
128
|
-
```bash
|
|
129
|
-
pip install vertexai
|
|
130
|
-
```
|
|
131
|
-
📌Additionally for working through [Vertex AI](https://cloud.google.com/vertex-ai) you need to
|
|
132
|
-
[install the Google Cloud CLI](https://cloud.google.com/sdk/docs/install)
|
|
133
|
-
and [configure the authorization](https://cloud.google.com/sdk/docs/authorizing).
|
|
134
127
|
|
|
135
128
|
#### Local language models via Hugging Face Transformers
|
|
136
129
|
|
|
@@ -207,7 +200,7 @@ ai_response = llm('What is your model name?')
|
|
|
207
200
|
# - For chat completion models elements are treated as separate messages
|
|
208
201
|
# - For completion LLMs elements are treated as text lines
|
|
209
202
|
llm(['1+2', '='])
|
|
210
|
-
llm('1+2=', model='gpt-
|
|
203
|
+
llm('1+2=', model='gpt-5.2')
|
|
211
204
|
|
|
212
205
|
# To specify a message role, you can use dictionary or classes
|
|
213
206
|
llm(dict(role='system', content='1+2='))
|
|
@@ -314,8 +307,53 @@ Text generation using HF/Transformers model locally (example with Qwen 3 0.6B).
|
|
|
314
307
|
#### [Other examples](https://github.com/llm-microcore/microcore/tree/main/examples)
|
|
315
308
|
|
|
316
309
|
## Python functions as AI tools
|
|
310
|
+
*Usage Example*:
|
|
311
|
+
```python
|
|
312
|
+
from microcore.ai_func import ai_func
|
|
313
|
+
|
|
314
|
+
@ai_func
|
|
315
|
+
def search_products(
|
|
316
|
+
query: str,
|
|
317
|
+
category: str = "all",
|
|
318
|
+
max_results: int = 10,
|
|
319
|
+
in_stock_only: bool = False
|
|
320
|
+
):
|
|
321
|
+
"""
|
|
322
|
+
Search for products in the catalog.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
query: Search terms to find matching products
|
|
326
|
+
category: Product category to filter by (e.g., "electronics", "clothing")
|
|
327
|
+
max_results: Maximum number of results to return
|
|
328
|
+
in_stock_only: If True, only return products currently in stock
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
List of matching products with name, price, and availability
|
|
332
|
+
"""
|
|
333
|
+
# Implementation would go here
|
|
334
|
+
pass
|
|
335
|
+
```
|
|
336
|
+
*Output*:
|
|
337
|
+
```
|
|
338
|
+
# Search for products in the catalog.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
query: Search terms to find matching products
|
|
342
|
+
category: Product category to filter by (e.g., "electronics", "clothing")
|
|
343
|
+
max_results: Maximum number of results to return
|
|
344
|
+
in_stock_only: If True, only return products currently in stock
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
List of matching products with name, price, and availability
|
|
348
|
+
{
|
|
349
|
+
"call": "search_products",
|
|
350
|
+
"query": <str>,
|
|
351
|
+
"category": <str> (default = "all"),
|
|
352
|
+
"max_results": <int> (default = 10),
|
|
353
|
+
"in_stock_only": <bool> (default = False)
|
|
354
|
+
}
|
|
317
355
|
|
|
318
|
-
|
|
356
|
+
```
|
|
319
357
|
|
|
320
358
|
## 🤖 AI Modules
|
|
321
359
|
**This is an experimental feature.**
|
|
@@ -81,21 +81,14 @@ For the full list of available configuration options, you may also check [`micro
|
|
|
81
81
|
|
|
82
82
|
### Installing vendor-specific packages
|
|
83
83
|
For models working not via OpenAI API, you may need to install additional packages:
|
|
84
|
-
#### Anthropic Claude
|
|
84
|
+
#### Anthropic Claude
|
|
85
85
|
```bash
|
|
86
86
|
pip install anthropic
|
|
87
87
|
```
|
|
88
|
-
#### Google Gemini via AI Studio
|
|
88
|
+
#### Google Gemini via AI Studio or Vertex AI
|
|
89
89
|
```bash
|
|
90
|
-
pip install google-
|
|
90
|
+
pip install google-genai
|
|
91
91
|
```
|
|
92
|
-
#### Google Gemini via Vertex AI
|
|
93
|
-
```bash
|
|
94
|
-
pip install vertexai
|
|
95
|
-
```
|
|
96
|
-
📌Additionally for working through [Vertex AI](https://cloud.google.com/vertex-ai) you need to
|
|
97
|
-
[install the Google Cloud CLI](https://cloud.google.com/sdk/docs/install)
|
|
98
|
-
and [configure the authorization](https://cloud.google.com/sdk/docs/authorizing).
|
|
99
92
|
|
|
100
93
|
#### Local language models via Hugging Face Transformers
|
|
101
94
|
|
|
@@ -172,7 +165,7 @@ ai_response = llm('What is your model name?')
|
|
|
172
165
|
# - For chat completion models elements are treated as separate messages
|
|
173
166
|
# - For completion LLMs elements are treated as text lines
|
|
174
167
|
llm(['1+2', '='])
|
|
175
|
-
llm('1+2=', model='gpt-
|
|
168
|
+
llm('1+2=', model='gpt-5.2')
|
|
176
169
|
|
|
177
170
|
# To specify a message role, you can use dictionary or classes
|
|
178
171
|
llm(dict(role='system', content='1+2='))
|
|
@@ -279,8 +272,53 @@ Text generation using HF/Transformers model locally (example with Qwen 3 0.6B).
|
|
|
279
272
|
#### [Other examples](https://github.com/llm-microcore/microcore/tree/main/examples)
|
|
280
273
|
|
|
281
274
|
## Python functions as AI tools
|
|
275
|
+
*Usage Example*:
|
|
276
|
+
```python
|
|
277
|
+
from microcore.ai_func import ai_func
|
|
278
|
+
|
|
279
|
+
@ai_func
|
|
280
|
+
def search_products(
|
|
281
|
+
query: str,
|
|
282
|
+
category: str = "all",
|
|
283
|
+
max_results: int = 10,
|
|
284
|
+
in_stock_only: bool = False
|
|
285
|
+
):
|
|
286
|
+
"""
|
|
287
|
+
Search for products in the catalog.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
query: Search terms to find matching products
|
|
291
|
+
category: Product category to filter by (e.g., "electronics", "clothing")
|
|
292
|
+
max_results: Maximum number of results to return
|
|
293
|
+
in_stock_only: If True, only return products currently in stock
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
List of matching products with name, price, and availability
|
|
297
|
+
"""
|
|
298
|
+
# Implementation would go here
|
|
299
|
+
pass
|
|
300
|
+
```
|
|
301
|
+
*Output*:
|
|
302
|
+
```
|
|
303
|
+
# Search for products in the catalog.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
query: Search terms to find matching products
|
|
307
|
+
category: Product category to filter by (e.g., "electronics", "clothing")
|
|
308
|
+
max_results: Maximum number of results to return
|
|
309
|
+
in_stock_only: If True, only return products currently in stock
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
List of matching products with name, price, and availability
|
|
313
|
+
{
|
|
314
|
+
"call": "search_products",
|
|
315
|
+
"query": <str>,
|
|
316
|
+
"category": <str> (default = "all"),
|
|
317
|
+
"max_results": <int> (default = 10),
|
|
318
|
+
"in_stock_only": <bool> (default = False)
|
|
319
|
+
}
|
|
282
320
|
|
|
283
|
-
|
|
321
|
+
```
|
|
284
322
|
|
|
285
323
|
## 🤖 AI Modules
|
|
286
324
|
**This is an experimental feature.**
|
|
@@ -79,7 +79,7 @@ def model_names() -> list[str]:
|
|
|
79
79
|
Return a list of available model names from the default LLM client.
|
|
80
80
|
"""
|
|
81
81
|
if env().default_client is None:
|
|
82
|
-
raise ValueError("No default LLM client
|
|
82
|
+
raise ValueError("No default LLM client is configured.")
|
|
83
83
|
return env().default_client.model_names()
|
|
84
84
|
|
|
85
85
|
|
|
@@ -231,4 +231,4 @@ __all__ = [
|
|
|
231
231
|
# "wrappers",
|
|
232
232
|
]
|
|
233
233
|
|
|
234
|
-
__version__ = "5.0.
|
|
234
|
+
__version__ = "5.0.1"
|
|
@@ -267,9 +267,7 @@ def llm(
|
|
|
267
267
|
save_cache(cache_name, response)
|
|
268
268
|
[h(response) for h in env().llm_after_handlers]
|
|
269
269
|
if tries > 0:
|
|
270
|
-
|
|
271
|
-
retry_params["retries"] = tries - 1
|
|
272
|
-
setattr(response, "_retry_callback", lambda: llm(prompt, **retry_params))
|
|
270
|
+
setattr(response, "_retry_callback", lambda: llm(prompt, retries=tries - 1, **kwargs))
|
|
273
271
|
if parse_json:
|
|
274
272
|
parsing_params = parse_json if isinstance(parse_json, dict) else {}
|
|
275
273
|
return response.parse_json(**parsing_params)
|
|
@@ -294,6 +292,13 @@ async def allm(
|
|
|
294
292
|
parse_json (bool|dict):
|
|
295
293
|
If True, parses response as JSON,
|
|
296
294
|
alternatively non-empty dict can be used as parse_json arguments.
|
|
295
|
+
Default is False (no parsing).
|
|
296
|
+
file_cache (bool | str):
|
|
297
|
+
If True or non-empty string, enables file caching of LLM responses.
|
|
298
|
+
If string, it will be used as cache prefix.
|
|
299
|
+
When enabled, identical requests with identical parameters
|
|
300
|
+
will return cached responses instead of making new API calls.
|
|
301
|
+
Default is False (no caching).
|
|
297
302
|
**kwargs: Parameters supported by the LLM API.
|
|
298
303
|
|
|
299
304
|
See parameters supported by the OpenAI:
|
|
@@ -374,8 +379,13 @@ async def allm(
|
|
|
374
379
|
if file_cache:
|
|
375
380
|
delete_cache(cache_name)
|
|
376
381
|
return await allm(
|
|
377
|
-
prompt,
|
|
382
|
+
prompt,
|
|
383
|
+
retries=tries - 1,
|
|
384
|
+
parse_json=parse_json,
|
|
385
|
+
file_cache=file_cache,
|
|
386
|
+
**kwargs
|
|
378
387
|
)
|
|
388
|
+
raise e
|
|
379
389
|
return response
|
|
380
390
|
|
|
381
391
|
|
|
@@ -80,7 +80,7 @@ def func_metadata(func, name=None) -> Dict[str, Any]:
|
|
|
80
80
|
|
|
81
81
|
# Add descriptions from parsed docstring to parameters
|
|
82
82
|
for param in parsed_docstring.params:
|
|
83
|
-
if param.arg_name in metadata.get("args",
|
|
83
|
+
if param.arg_name in metadata.get("args", {}):
|
|
84
84
|
metadata["args"][param.arg_name]["docstr"] = param.description
|
|
85
85
|
|
|
86
86
|
return metadata
|
|
@@ -190,6 +190,7 @@ def extract_tag_tool_params(
|
|
|
190
190
|
if len(tags) > 1:
|
|
191
191
|
if raise_errors:
|
|
192
192
|
raise ValueError("Response contains multiple tags when only one expected")
|
|
193
|
+
logging.warning("Response contains multiple tags, but only the first one will be used.")
|
|
193
194
|
tag, attrs, content = tags[0]
|
|
194
195
|
return tag, [content], attrs
|
|
195
196
|
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
|
-
File storage
|
|
2
|
+
File storage functionality.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Provides a Storage class for file operations within a configured storage directory.
|
|
6
|
+
Supports automatic file numbering, backups, JSON serialization, encoding detection,
|
|
7
|
+
file copying, directory listing, etc.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
from microcore import storage
|
|
11
|
+
storage.write("data.txt", "content")
|
|
12
|
+
content = storage.read("data.txt")
|
|
3
13
|
"""
|
|
4
14
|
|
|
5
15
|
import fnmatch
|
|
@@ -15,6 +25,7 @@ from ._env import config
|
|
|
15
25
|
from .utils import file_link, list_files
|
|
16
26
|
|
|
17
27
|
_missing = object()
|
|
28
|
+
"""Sentinel value to distinguish between None and 'not provided'."""
|
|
18
29
|
|
|
19
30
|
|
|
20
31
|
@dataclass
|
|
@@ -224,8 +235,6 @@ class Storage:
|
|
|
224
235
|
if isinstance(content, bytes):
|
|
225
236
|
if encoding is not None:
|
|
226
237
|
logging.warning("Encoding is ignored when writing bytes content")
|
|
227
|
-
if append:
|
|
228
|
-
raise ValueError("Cannot append bytes content")
|
|
229
238
|
|
|
230
239
|
if rewrite_existing is None:
|
|
231
240
|
rewrite_existing = True
|
|
@@ -268,18 +277,12 @@ class Storage:
|
|
|
268
277
|
if file_name != fn_incremented:
|
|
269
278
|
os.rename(self.path / file_name, self.path / fn_incremented)
|
|
270
279
|
(self.path / file_name).parent.mkdir(parents=True, exist_ok=True)
|
|
271
|
-
if
|
|
272
|
-
with (self.path / file_name).open(
|
|
273
|
-
|
|
274
|
-
encoding=encoding if not isinstance(content, bytes) else None,
|
|
275
|
-
) as file:
|
|
276
|
-
file.write(content)
|
|
280
|
+
if isinstance(content, bytes):
|
|
281
|
+
with (self.path / file_name).open(mode="ab" if append else "wb") as f:
|
|
282
|
+
f.write(content)
|
|
277
283
|
else:
|
|
278
|
-
if
|
|
279
|
-
|
|
280
|
-
file.write(content)
|
|
281
|
-
else:
|
|
282
|
-
(self.path / file_name).write_text(content, encoding=encoding)
|
|
284
|
+
with (self.path / file_name).open(mode="a" if append else "w", encoding=encoding) as f:
|
|
285
|
+
f.write(content)
|
|
283
286
|
return file_name
|
|
284
287
|
|
|
285
288
|
def clean(self, path: str | Path):
|
|
@@ -163,8 +163,9 @@ class AsyncGoogleClient(BaseAsyncAIClient):
|
|
|
163
163
|
def __init__(self, client: GoogleClient):
|
|
164
164
|
self.sync_client = client
|
|
165
165
|
|
|
166
|
-
async def load_models(self) -> dict:
|
|
167
|
-
|
|
166
|
+
async def load_models(self, **kwargs) -> dict:
|
|
167
|
+
models = await self.sync_client.genai_client.aio.models.list(**kwargs)
|
|
168
|
+
return {model.name: model for model in models}
|
|
168
169
|
|
|
169
170
|
async def generate(
|
|
170
171
|
self,
|
|
@@ -221,6 +222,7 @@ class _GenerationContext:
|
|
|
221
222
|
model_name = kwargs.pop("model", client.config.MODEL)
|
|
222
223
|
callbacks = prepare_callbacks(client.config, kwargs, set_stream=False)
|
|
223
224
|
is_image = is_image_model(model_name)
|
|
225
|
+
stream = kwargs.pop("stream", False) or (callbacks and not is_image)
|
|
224
226
|
return _GenerationContext(
|
|
225
227
|
model_name=model_name,
|
|
226
228
|
save=kwargs.pop("save", True),
|
|
@@ -230,7 +232,7 @@ class _GenerationContext:
|
|
|
230
232
|
genai_client=client.genai_client,
|
|
231
233
|
config=client.config,
|
|
232
234
|
is_image_model=is_image,
|
|
233
|
-
stream=
|
|
235
|
+
stream=stream,
|
|
234
236
|
)
|
|
235
237
|
|
|
236
238
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import base64
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
import openai
|
|
5
6
|
from openai.types import CompletionChoice, ImagesResponse
|
|
6
7
|
|
|
7
8
|
from ..lm_client import BaseAIChatClient, BaseAsyncAIClient
|
|
8
|
-
from ..message_types import TMsgContentPart
|
|
9
|
+
from ..message_types import TMsgContentPart, TMsgContent
|
|
9
10
|
from ..configuration import Config
|
|
10
11
|
from ..llm_backends import ApiPlatform
|
|
11
12
|
from .._prepare_llm_args import prepare_prompt
|
|
@@ -42,8 +43,7 @@ class AsyncOpenAIClient(BaseAsyncAIClient):
|
|
|
42
43
|
config = self.sync_client.config
|
|
43
44
|
args, options = _prepare_llm_arguments(config, kwargs)
|
|
44
45
|
if is_image_model(args["model"]):
|
|
45
|
-
|
|
46
|
-
return _generate_image(
|
|
46
|
+
return await _generate_image_async(
|
|
47
47
|
prompt,
|
|
48
48
|
args,
|
|
49
49
|
self.oai_client,
|
|
@@ -133,6 +133,18 @@ class OpenAIClient(BaseAIChatClient):
|
|
|
133
133
|
return image_to_oai(img)
|
|
134
134
|
return content_part
|
|
135
135
|
|
|
136
|
+
def _convert_message_content(self, message_content: TMsgContent) -> Any:
|
|
137
|
+
"""
|
|
138
|
+
Convert the message content into a format suitable for the LLM inference chat API.
|
|
139
|
+
"""
|
|
140
|
+
if isinstance(message_content, str):
|
|
141
|
+
# Prevent conversion of string content into dict(type=text, text=...)
|
|
142
|
+
# because Azure OpenAI fails with Error 400
|
|
143
|
+
# when passing "azure_search" data source like following:
|
|
144
|
+
# llm(..., extra_body={"data_sources"=[{"type": "azure_search",...}]})
|
|
145
|
+
return message_content
|
|
146
|
+
return super()._convert_message_content(message_content)
|
|
147
|
+
|
|
136
148
|
def load_models(self, **kwargs) -> dict:
|
|
137
149
|
models_iter = self.oai_client.models.list(**kwargs)
|
|
138
150
|
return {model.id: model for model in models_iter}
|
|
@@ -294,12 +306,8 @@ def _oai_image_response_to_images(response: ImagesResponse) -> list[Image]:
|
|
|
294
306
|
return images
|
|
295
307
|
|
|
296
308
|
|
|
297
|
-
def
|
|
298
|
-
prompt
|
|
299
|
-
args,
|
|
300
|
-
connection: openai.OpenAI | openai.AsyncOpenAI,
|
|
301
|
-
options
|
|
302
|
-
) -> ImageGenerationResponse | None:
|
|
309
|
+
def _prepare_image_generation(prompt, args):
|
|
310
|
+
"""Prepare prompt and images for image generation (shared logic)."""
|
|
303
311
|
def convert_input_image(image: ImageInterface):
|
|
304
312
|
if isinstance(image, FileImage):
|
|
305
313
|
return open(image.file, "rb")
|
|
@@ -333,6 +341,32 @@ def _generate_image(
|
|
|
333
341
|
if save and args.get("response_format", "b64_json") != "b64_json":
|
|
334
342
|
raise ValueError("Only 'b64_json' response format is supported.")
|
|
335
343
|
|
|
344
|
+
return prompt, images, save
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _image_generation_response(
|
|
348
|
+
response: ImagesResponse,
|
|
349
|
+
save: bool,
|
|
350
|
+
options: dict,
|
|
351
|
+
) -> ImageGenerationResponse | None:
|
|
352
|
+
check_for_errors(response)
|
|
353
|
+
images = _oai_image_response_to_images(response)
|
|
354
|
+
response_attrs = response.__dict__.copy()
|
|
355
|
+
result = make_image_generation_response(images, save, response_attrs)
|
|
356
|
+
for cb in options["callbacks"]:
|
|
357
|
+
cb(result)
|
|
358
|
+
return result
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _generate_image(
|
|
362
|
+
prompt,
|
|
363
|
+
args,
|
|
364
|
+
connection: openai.OpenAI,
|
|
365
|
+
options
|
|
366
|
+
) -> ImageGenerationResponse | None:
|
|
367
|
+
"""Synchronous version of image generation."""
|
|
368
|
+
prompt, images, save = _prepare_image_generation(prompt, args)
|
|
369
|
+
|
|
336
370
|
if not images:
|
|
337
371
|
response: ImagesResponse = connection.images.generate(prompt=prompt, **args)
|
|
338
372
|
else:
|
|
@@ -341,10 +375,24 @@ def _generate_image(
|
|
|
341
375
|
prompt=prompt,
|
|
342
376
|
**args
|
|
343
377
|
)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
378
|
+
return _image_generation_response(response, save, options)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
async def _generate_image_async(
|
|
382
|
+
prompt,
|
|
383
|
+
args,
|
|
384
|
+
connection: openai.AsyncOpenAI,
|
|
385
|
+
options
|
|
386
|
+
) -> ImageGenerationResponse | None:
|
|
387
|
+
"""Asynchronous version of image generation."""
|
|
388
|
+
prompt, images, save = _prepare_image_generation(prompt, args)
|
|
389
|
+
|
|
390
|
+
if not images:
|
|
391
|
+
response: ImagesResponse = await connection.images.generate(prompt=prompt, **args)
|
|
392
|
+
else:
|
|
393
|
+
response: ImagesResponse = await connection.images.edit(
|
|
394
|
+
image=images,
|
|
395
|
+
prompt=prompt,
|
|
396
|
+
**args
|
|
397
|
+
)
|
|
398
|
+
return _image_generation_response(response, save, options)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|