local-coze 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_coze/__init__.py +110 -0
- local_coze/cli/__init__.py +3 -0
- local_coze/cli/chat.py +126 -0
- local_coze/cli/cli.py +34 -0
- local_coze/cli/constants.py +7 -0
- local_coze/cli/db.py +81 -0
- local_coze/cli/embedding.py +193 -0
- local_coze/cli/image.py +162 -0
- local_coze/cli/knowledge.py +195 -0
- local_coze/cli/search.py +198 -0
- local_coze/cli/utils.py +41 -0
- local_coze/cli/video.py +191 -0
- local_coze/cli/video_edit.py +888 -0
- local_coze/cli/voice.py +351 -0
- local_coze/core/__init__.py +25 -0
- local_coze/core/client.py +253 -0
- local_coze/core/config.py +58 -0
- local_coze/core/exceptions.py +67 -0
- local_coze/database/__init__.py +29 -0
- local_coze/database/client.py +170 -0
- local_coze/database/migration.py +342 -0
- local_coze/embedding/__init__.py +31 -0
- local_coze/embedding/client.py +350 -0
- local_coze/embedding/models.py +130 -0
- local_coze/image/__init__.py +19 -0
- local_coze/image/client.py +110 -0
- local_coze/image/models.py +163 -0
- local_coze/knowledge/__init__.py +19 -0
- local_coze/knowledge/client.py +148 -0
- local_coze/knowledge/models.py +45 -0
- local_coze/llm/__init__.py +25 -0
- local_coze/llm/client.py +317 -0
- local_coze/llm/models.py +48 -0
- local_coze/memory/__init__.py +14 -0
- local_coze/memory/client.py +176 -0
- local_coze/s3/__init__.py +12 -0
- local_coze/s3/client.py +580 -0
- local_coze/s3/models.py +18 -0
- local_coze/search/__init__.py +19 -0
- local_coze/search/client.py +183 -0
- local_coze/search/models.py +57 -0
- local_coze/video/__init__.py +17 -0
- local_coze/video/client.py +347 -0
- local_coze/video/models.py +39 -0
- local_coze/video_edit/__init__.py +23 -0
- local_coze/video_edit/examples.py +340 -0
- local_coze/video_edit/frame_extractor.py +176 -0
- local_coze/video_edit/models.py +362 -0
- local_coze/video_edit/video_edit.py +631 -0
- local_coze/voice/__init__.py +17 -0
- local_coze/voice/asr.py +82 -0
- local_coze/voice/models.py +86 -0
- local_coze/voice/tts.py +94 -0
- local_coze-0.0.1.dist-info/METADATA +636 -0
- local_coze-0.0.1.dist-info/RECORD +58 -0
- local_coze-0.0.1.dist-info/WHEEL +4 -0
- local_coze-0.0.1.dist-info/entry_points.txt +3 -0
- local_coze-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional, List, Dict
|
|
3
|
+
from cozeloop.decorator import observe
|
|
4
|
+
from coze_coding_utils.runtime_ctx.context import Context
|
|
5
|
+
|
|
6
|
+
from ..core.client import BaseClient
|
|
7
|
+
from ..core.config import Config
|
|
8
|
+
from ..core.exceptions import APIError, ValidationError
|
|
9
|
+
from .models import (
|
|
10
|
+
EmbeddingConfig,
|
|
11
|
+
EmbeddingInputItem,
|
|
12
|
+
EmbeddingInputImageURL,
|
|
13
|
+
EmbeddingInputVideoURL,
|
|
14
|
+
EmbeddingRequest,
|
|
15
|
+
EmbeddingResponse,
|
|
16
|
+
EmbeddingData,
|
|
17
|
+
EmbeddingUsage,
|
|
18
|
+
MultiEmbeddingConfig,
|
|
19
|
+
SparseEmbeddingConfig,
|
|
20
|
+
SparseEmbeddingItem,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EmbeddingClient(BaseClient):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
config: Optional[Config] = None,
|
|
28
|
+
ctx: Optional[Context] = None,
|
|
29
|
+
custom_headers: Optional[Dict[str, str]] = None,
|
|
30
|
+
verbose: bool = False,
|
|
31
|
+
):
|
|
32
|
+
super().__init__(config, ctx, custom_headers, verbose)
|
|
33
|
+
self.base_url = self.config.base_url
|
|
34
|
+
self.model = EmbeddingConfig.DEFAULT_MODEL
|
|
35
|
+
|
|
36
|
+
def _validate_inputs(
|
|
37
|
+
self,
|
|
38
|
+
texts: Optional[List[str]],
|
|
39
|
+
image_urls: Optional[List[str]],
|
|
40
|
+
video_urls: Optional[List[str]] = None
|
|
41
|
+
) -> None:
|
|
42
|
+
has_texts = texts is not None and len(texts) > 0
|
|
43
|
+
has_images = image_urls is not None and len(image_urls) > 0
|
|
44
|
+
has_videos = video_urls is not None and len(video_urls) > 0
|
|
45
|
+
|
|
46
|
+
if not has_texts and not has_images and not has_videos:
|
|
47
|
+
raise ValidationError(
|
|
48
|
+
"At least one of texts, image_urls, or video_urls must be provided",
|
|
49
|
+
field="input"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
total_inputs = 0
|
|
53
|
+
if texts:
|
|
54
|
+
total_inputs += len(texts)
|
|
55
|
+
if image_urls:
|
|
56
|
+
total_inputs += len(image_urls)
|
|
57
|
+
if video_urls:
|
|
58
|
+
total_inputs += len(video_urls)
|
|
59
|
+
|
|
60
|
+
if total_inputs > EmbeddingConfig.MAX_BATCH_SIZE:
|
|
61
|
+
raise ValidationError(
|
|
62
|
+
f"Total inputs exceed maximum batch size of {EmbeddingConfig.MAX_BATCH_SIZE}",
|
|
63
|
+
field="input",
|
|
64
|
+
value=total_inputs
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _build_input_items(
|
|
68
|
+
self,
|
|
69
|
+
texts: Optional[List[str]],
|
|
70
|
+
image_urls: Optional[List[str]],
|
|
71
|
+
video_urls: Optional[List[str]] = None
|
|
72
|
+
) -> List[EmbeddingInputItem]:
|
|
73
|
+
items = []
|
|
74
|
+
if texts:
|
|
75
|
+
for text in texts:
|
|
76
|
+
if text and text.strip():
|
|
77
|
+
items.append(EmbeddingInputItem(type="text", text=text))
|
|
78
|
+
if image_urls:
|
|
79
|
+
for url in image_urls:
|
|
80
|
+
if url and url.strip():
|
|
81
|
+
items.append(EmbeddingInputItem(
|
|
82
|
+
type="image_url",
|
|
83
|
+
image_url=EmbeddingInputImageURL(url=url)
|
|
84
|
+
))
|
|
85
|
+
if video_urls:
|
|
86
|
+
for url in video_urls:
|
|
87
|
+
if url and url.strip():
|
|
88
|
+
items.append(EmbeddingInputItem(
|
|
89
|
+
type="video_url",
|
|
90
|
+
video_url=EmbeddingInputVideoURL(url=url)
|
|
91
|
+
))
|
|
92
|
+
return items
|
|
93
|
+
|
|
94
|
+
@observe
|
|
95
|
+
def embed(
|
|
96
|
+
self,
|
|
97
|
+
texts: Optional[List[str]] = None,
|
|
98
|
+
image_urls: Optional[List[str]] = None,
|
|
99
|
+
video_urls: Optional[List[str]] = None,
|
|
100
|
+
model: Optional[str] = None,
|
|
101
|
+
dimensions: Optional[int] = None,
|
|
102
|
+
encoding_format: Optional[str] = None,
|
|
103
|
+
instructions: Optional[str] = None,
|
|
104
|
+
multi_embedding: bool = False,
|
|
105
|
+
sparse_embedding: bool = False
|
|
106
|
+
) -> EmbeddingResponse:
|
|
107
|
+
self._validate_inputs(texts, image_urls, video_urls)
|
|
108
|
+
|
|
109
|
+
input_items = self._build_input_items(texts, image_urls, video_urls)
|
|
110
|
+
if not input_items:
|
|
111
|
+
raise ValidationError("No valid input provided", field="input")
|
|
112
|
+
|
|
113
|
+
request = EmbeddingRequest(
|
|
114
|
+
model=model or self.model,
|
|
115
|
+
input=input_items,
|
|
116
|
+
dimensions=dimensions,
|
|
117
|
+
encoding_format=encoding_format,
|
|
118
|
+
instructions=instructions,
|
|
119
|
+
multi_embedding=MultiEmbeddingConfig(type="enabled") if multi_embedding else None,
|
|
120
|
+
sparse_embedding=SparseEmbeddingConfig(type="enabled") if sparse_embedding else None
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
data = self._request(
|
|
124
|
+
method="POST",
|
|
125
|
+
url=f"{self.base_url}/api/v3/embeddings/multimodal",
|
|
126
|
+
json=request.to_api_request()
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if "error" in data and data["error"]:
|
|
130
|
+
raise APIError(
|
|
131
|
+
f"API returned error: {data['error'].get('message', 'Unknown error')}",
|
|
132
|
+
code=data['error'].get('code')
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return EmbeddingResponse(**data)
|
|
136
|
+
|
|
137
|
+
@observe
|
|
138
|
+
def embed_text(
|
|
139
|
+
self,
|
|
140
|
+
text: str,
|
|
141
|
+
model: Optional[str] = None,
|
|
142
|
+
dimensions: Optional[int] = None,
|
|
143
|
+
instructions: Optional[str] = None
|
|
144
|
+
) -> List[float]:
|
|
145
|
+
response = self.embed(
|
|
146
|
+
texts=[text],
|
|
147
|
+
model=model,
|
|
148
|
+
dimensions=dimensions,
|
|
149
|
+
instructions=instructions
|
|
150
|
+
)
|
|
151
|
+
if response.embedding:
|
|
152
|
+
return response.embedding
|
|
153
|
+
raise APIError("No embedding returned")
|
|
154
|
+
|
|
155
|
+
@observe
|
|
156
|
+
def embed_texts(
|
|
157
|
+
self,
|
|
158
|
+
texts: List[str],
|
|
159
|
+
model: Optional[str] = None,
|
|
160
|
+
dimensions: Optional[int] = None,
|
|
161
|
+
instructions: Optional[str] = None
|
|
162
|
+
) -> List[float]:
|
|
163
|
+
response = self.embed(
|
|
164
|
+
texts=texts,
|
|
165
|
+
model=model,
|
|
166
|
+
dimensions=dimensions,
|
|
167
|
+
instructions=instructions
|
|
168
|
+
)
|
|
169
|
+
if response.embedding:
|
|
170
|
+
return response.embedding
|
|
171
|
+
raise APIError("No embedding returned")
|
|
172
|
+
|
|
173
|
+
@observe
|
|
174
|
+
def embed_image(
|
|
175
|
+
self,
|
|
176
|
+
image_url: str,
|
|
177
|
+
model: Optional[str] = None,
|
|
178
|
+
dimensions: Optional[int] = None
|
|
179
|
+
) -> List[float]:
|
|
180
|
+
response = self.embed(
|
|
181
|
+
image_urls=[image_url],
|
|
182
|
+
model=model,
|
|
183
|
+
dimensions=dimensions
|
|
184
|
+
)
|
|
185
|
+
if response.embedding:
|
|
186
|
+
return response.embedding
|
|
187
|
+
raise APIError("No embedding returned")
|
|
188
|
+
|
|
189
|
+
@observe
|
|
190
|
+
def embed_images(
|
|
191
|
+
self,
|
|
192
|
+
image_urls: List[str],
|
|
193
|
+
model: Optional[str] = None,
|
|
194
|
+
dimensions: Optional[int] = None
|
|
195
|
+
) -> List[float]:
|
|
196
|
+
response = self.embed(
|
|
197
|
+
image_urls=image_urls,
|
|
198
|
+
model=model,
|
|
199
|
+
dimensions=dimensions
|
|
200
|
+
)
|
|
201
|
+
if response.embedding:
|
|
202
|
+
return response.embedding
|
|
203
|
+
raise APIError("No embedding returned")
|
|
204
|
+
|
|
205
|
+
@observe
|
|
206
|
+
def embed_video(
|
|
207
|
+
self,
|
|
208
|
+
video_url: str,
|
|
209
|
+
model: Optional[str] = None,
|
|
210
|
+
dimensions: Optional[int] = None
|
|
211
|
+
) -> List[float]:
|
|
212
|
+
response = self.embed(
|
|
213
|
+
video_urls=[video_url],
|
|
214
|
+
model=model,
|
|
215
|
+
dimensions=dimensions
|
|
216
|
+
)
|
|
217
|
+
if response.embedding:
|
|
218
|
+
return response.embedding
|
|
219
|
+
raise APIError("No embedding returned")
|
|
220
|
+
|
|
221
|
+
@observe
|
|
222
|
+
def embed_videos(
|
|
223
|
+
self,
|
|
224
|
+
video_urls: List[str],
|
|
225
|
+
model: Optional[str] = None,
|
|
226
|
+
dimensions: Optional[int] = None
|
|
227
|
+
) -> List[float]:
|
|
228
|
+
response = self.embed(
|
|
229
|
+
video_urls=video_urls,
|
|
230
|
+
model=model,
|
|
231
|
+
dimensions=dimensions
|
|
232
|
+
)
|
|
233
|
+
if response.embedding:
|
|
234
|
+
return response.embedding
|
|
235
|
+
raise APIError("No embedding returned")
|
|
236
|
+
|
|
237
|
+
@observe
|
|
238
|
+
def embed_multimodal(
|
|
239
|
+
self,
|
|
240
|
+
texts: Optional[List[str]] = None,
|
|
241
|
+
image_urls: Optional[List[str]] = None,
|
|
242
|
+
video_urls: Optional[List[str]] = None,
|
|
243
|
+
model: Optional[str] = None,
|
|
244
|
+
dimensions: Optional[int] = None,
|
|
245
|
+
instructions: Optional[str] = None,
|
|
246
|
+
multi_embedding: bool = False,
|
|
247
|
+
sparse_embedding: bool = False
|
|
248
|
+
) -> EmbeddingResponse:
|
|
249
|
+
return self.embed(
|
|
250
|
+
texts=texts,
|
|
251
|
+
image_urls=image_urls,
|
|
252
|
+
video_urls=video_urls,
|
|
253
|
+
model=model,
|
|
254
|
+
dimensions=dimensions,
|
|
255
|
+
instructions=instructions,
|
|
256
|
+
multi_embedding=multi_embedding,
|
|
257
|
+
sparse_embedding=sparse_embedding
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@observe
|
|
261
|
+
def embed_with_multi_vectors(
|
|
262
|
+
self,
|
|
263
|
+
texts: Optional[List[str]] = None,
|
|
264
|
+
image_urls: Optional[List[str]] = None,
|
|
265
|
+
video_urls: Optional[List[str]] = None,
|
|
266
|
+
model: Optional[str] = None,
|
|
267
|
+
dimensions: Optional[int] = None,
|
|
268
|
+
instructions: Optional[str] = None
|
|
269
|
+
) -> Optional[List[List[float]]]:
|
|
270
|
+
response = self.embed(
|
|
271
|
+
texts=texts,
|
|
272
|
+
image_urls=image_urls,
|
|
273
|
+
video_urls=video_urls,
|
|
274
|
+
model=model,
|
|
275
|
+
dimensions=dimensions,
|
|
276
|
+
instructions=instructions,
|
|
277
|
+
multi_embedding=True
|
|
278
|
+
)
|
|
279
|
+
return response.multi_embeddings
|
|
280
|
+
|
|
281
|
+
@observe
|
|
282
|
+
def embed_with_sparse(
|
|
283
|
+
self,
|
|
284
|
+
texts: Optional[List[str]] = None,
|
|
285
|
+
image_urls: Optional[List[str]] = None,
|
|
286
|
+
video_urls: Optional[List[str]] = None,
|
|
287
|
+
model: Optional[str] = None,
|
|
288
|
+
dimensions: Optional[int] = None,
|
|
289
|
+
instructions: Optional[str] = None
|
|
290
|
+
) -> Optional[List[SparseEmbeddingItem]]:
|
|
291
|
+
response = self.embed(
|
|
292
|
+
texts=texts,
|
|
293
|
+
image_urls=image_urls,
|
|
294
|
+
video_urls=video_urls,
|
|
295
|
+
model=model,
|
|
296
|
+
dimensions=dimensions,
|
|
297
|
+
instructions=instructions,
|
|
298
|
+
sparse_embedding=True
|
|
299
|
+
)
|
|
300
|
+
return response.sparse_embeddings
|
|
301
|
+
|
|
302
|
+
async def embed_async(
|
|
303
|
+
self,
|
|
304
|
+
texts: Optional[List[str]] = None,
|
|
305
|
+
image_urls: Optional[List[str]] = None,
|
|
306
|
+
video_urls: Optional[List[str]] = None,
|
|
307
|
+
model: Optional[str] = None,
|
|
308
|
+
dimensions: Optional[int] = None,
|
|
309
|
+
encoding_format: Optional[str] = None,
|
|
310
|
+
instructions: Optional[str] = None,
|
|
311
|
+
multi_embedding: bool = False,
|
|
312
|
+
sparse_embedding: bool = False
|
|
313
|
+
) -> EmbeddingResponse:
|
|
314
|
+
loop = asyncio.get_event_loop()
|
|
315
|
+
return await loop.run_in_executor(
|
|
316
|
+
None,
|
|
317
|
+
lambda: self.embed(
|
|
318
|
+
texts=texts,
|
|
319
|
+
image_urls=image_urls,
|
|
320
|
+
video_urls=video_urls,
|
|
321
|
+
model=model,
|
|
322
|
+
dimensions=dimensions,
|
|
323
|
+
encoding_format=encoding_format,
|
|
324
|
+
instructions=instructions,
|
|
325
|
+
multi_embedding=multi_embedding,
|
|
326
|
+
sparse_embedding=sparse_embedding
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
async def batch_embed(
|
|
331
|
+
self,
|
|
332
|
+
text_batches: List[List[str]],
|
|
333
|
+
model: Optional[str] = None,
|
|
334
|
+
dimensions: Optional[int] = None,
|
|
335
|
+
instructions: Optional[str] = None,
|
|
336
|
+
max_concurrent: int = 5
|
|
337
|
+
) -> List[EmbeddingResponse]:
|
|
338
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
|
339
|
+
|
|
340
|
+
async def _embed_with_semaphore(texts: List[str]) -> EmbeddingResponse:
|
|
341
|
+
async with semaphore:
|
|
342
|
+
return await self.embed_async(
|
|
343
|
+
texts=texts,
|
|
344
|
+
model=model,
|
|
345
|
+
dimensions=dimensions,
|
|
346
|
+
instructions=instructions
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
tasks = [_embed_with_semaphore(batch) for batch in text_batches]
|
|
350
|
+
return await asyncio.gather(*tasks, return_exceptions=False)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from typing import Optional, List, Literal
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EmbeddingConfig:
|
|
6
|
+
DEFAULT_MODEL = "doubao-embedding-vision-251215"
|
|
7
|
+
DEFAULT_ENCODING_FORMAT = "float"
|
|
8
|
+
MAX_BATCH_SIZE = 100
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EmbeddingInputImageURL(BaseModel):
|
|
12
|
+
url: str = Field(..., description="Image URL")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EmbeddingInputVideoURL(BaseModel):
|
|
16
|
+
url: str = Field(..., description="Video URL")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EmbeddingInputItem(BaseModel):
|
|
20
|
+
type: Literal["text", "image_url", "video_url"] = Field(..., description="Input type")
|
|
21
|
+
text: Optional[str] = Field(default=None, description="Text content for embedding")
|
|
22
|
+
image_url: Optional[EmbeddingInputImageURL] = Field(default=None, description="Image URL for embedding")
|
|
23
|
+
video_url: Optional[EmbeddingInputVideoURL] = Field(default=None, description="Video URL for embedding")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MultiEmbeddingConfig(BaseModel):
|
|
27
|
+
type: Literal["enabled", "disabled"] = Field(default="disabled", description="Multi embedding mode")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SparseEmbeddingConfig(BaseModel):
|
|
31
|
+
type: Literal["enabled", "disabled"] = Field(default="disabled", description="Sparse embedding mode")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SparseEmbeddingItem(BaseModel):
|
|
35
|
+
index: int = Field(..., description="Token index")
|
|
36
|
+
value: float = Field(..., description="Token weight value")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PromptTokensDetails(BaseModel):
|
|
40
|
+
image_tokens: int = Field(default=0, description="Number of image tokens")
|
|
41
|
+
text_tokens: int = Field(default=0, description="Number of text tokens")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class EmbeddingUsage(BaseModel):
|
|
45
|
+
prompt_tokens: int = Field(default=0, description="Number of prompt tokens")
|
|
46
|
+
prompt_tokens_details: Optional[PromptTokensDetails] = Field(default=None, description="Token details breakdown")
|
|
47
|
+
total_tokens: int = Field(default=0, description="Total number of tokens")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class EmbeddingData(BaseModel):
|
|
51
|
+
object: str = Field(default="embedding", description="Object type")
|
|
52
|
+
embedding: Optional[List[float]] = Field(default=None, description="Embedding vector")
|
|
53
|
+
multi_embedding: Optional[List[List[float]]] = Field(default=None, description="Multi embedding vectors")
|
|
54
|
+
sparse_embedding: Optional[List[SparseEmbeddingItem]] = Field(default=None, description="Sparse embedding")
|
|
55
|
+
index: int = Field(default=0, description="Index of the embedding in the batch")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class EmbeddingRequest(BaseModel):
|
|
59
|
+
model: str = Field(default=EmbeddingConfig.DEFAULT_MODEL, description="Model ID for embedding")
|
|
60
|
+
input: List[EmbeddingInputItem] = Field(..., description="List of inputs to embed")
|
|
61
|
+
dimensions: Optional[int] = Field(default=None, description="Output embedding dimensions")
|
|
62
|
+
encoding_format: Optional[Literal["float", "base64"]] = Field(default=None, description="Encoding format")
|
|
63
|
+
instructions: Optional[str] = Field(default=None, description="Instructions for embedding")
|
|
64
|
+
multi_embedding: Optional[MultiEmbeddingConfig] = Field(default=None, description="Multi embedding config")
|
|
65
|
+
sparse_embedding: Optional[SparseEmbeddingConfig] = Field(default=None, description="Sparse embedding config")
|
|
66
|
+
|
|
67
|
+
def to_api_request(self) -> dict:
|
|
68
|
+
request_data = {
|
|
69
|
+
"model": self.model,
|
|
70
|
+
"input": []
|
|
71
|
+
}
|
|
72
|
+
for item in self.input:
|
|
73
|
+
item_dict = {"type": item.type}
|
|
74
|
+
if item.text is not None:
|
|
75
|
+
item_dict["text"] = item.text
|
|
76
|
+
if item.image_url is not None:
|
|
77
|
+
item_dict["image_url"] = {"url": item.image_url.url}
|
|
78
|
+
if item.video_url is not None:
|
|
79
|
+
item_dict["video_url"] = {"url": item.video_url.url}
|
|
80
|
+
request_data["input"].append(item_dict)
|
|
81
|
+
|
|
82
|
+
if self.dimensions is not None:
|
|
83
|
+
request_data["dimensions"] = self.dimensions
|
|
84
|
+
if self.encoding_format is not None:
|
|
85
|
+
request_data["encoding_format"] = self.encoding_format
|
|
86
|
+
if self.instructions is not None:
|
|
87
|
+
request_data["instructions"] = self.instructions
|
|
88
|
+
if self.multi_embedding is not None:
|
|
89
|
+
request_data["multi_embedding"] = {"type": self.multi_embedding.type}
|
|
90
|
+
if self.sparse_embedding is not None:
|
|
91
|
+
request_data["sparse_embedding"] = {"type": self.sparse_embedding.type}
|
|
92
|
+
return request_data
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class EmbeddingResponse(BaseModel):
|
|
96
|
+
object: str = Field(default="list", description="Response object type")
|
|
97
|
+
data: Optional[EmbeddingData] = Field(default=None, description="Embedding result")
|
|
98
|
+
model: str = Field(default="", description="Model used for embedding")
|
|
99
|
+
usage: Optional[EmbeddingUsage] = Field(default=None, description="Token usage information")
|
|
100
|
+
id: Optional[str] = Field(default=None, description="Response ID")
|
|
101
|
+
created: Optional[int] = Field(default=None, description="Creation timestamp")
|
|
102
|
+
error: Optional[dict] = Field(default=None, description="Error information if any")
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def success(self) -> bool:
|
|
106
|
+
return self.error is None and self.data is not None
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def embedding(self) -> Optional[List[float]]:
|
|
110
|
+
if self.data and self.data.embedding:
|
|
111
|
+
return self.data.embedding
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def multi_embeddings(self) -> Optional[List[List[float]]]:
|
|
116
|
+
if self.data and self.data.multi_embedding:
|
|
117
|
+
return self.data.multi_embedding
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def sparse_embeddings(self) -> Optional[List[SparseEmbeddingItem]]:
|
|
122
|
+
if self.data and self.data.sparse_embedding:
|
|
123
|
+
return self.data.sparse_embedding
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def error_message(self) -> Optional[str]:
|
|
128
|
+
if self.error:
|
|
129
|
+
return self.error.get("message", "Unknown error")
|
|
130
|
+
return None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .client import ImageGenerationClient
|
|
2
|
+
from .models import (
|
|
3
|
+
ImageConfig,
|
|
4
|
+
ImageSize,
|
|
5
|
+
ImageGenerationRequest,
|
|
6
|
+
ImageGenerationResponse,
|
|
7
|
+
ImageData,
|
|
8
|
+
UsageInfo
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ImageGenerationClient",
|
|
13
|
+
"ImageConfig",
|
|
14
|
+
"ImageSize",
|
|
15
|
+
"ImageGenerationRequest",
|
|
16
|
+
"ImageGenerationResponse",
|
|
17
|
+
"ImageData",
|
|
18
|
+
"UsageInfo",
|
|
19
|
+
]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from coze_coding_utils.runtime_ctx.context import Context
|
|
5
|
+
from cozeloop.decorator import observe
|
|
6
|
+
|
|
7
|
+
from ..core.client import BaseClient
|
|
8
|
+
from ..core.config import Config
|
|
9
|
+
from ..core.exceptions import APIError
|
|
10
|
+
from .models import ImageConfig, ImageGenerationRequest, ImageGenerationResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ImageGenerationClient(BaseClient):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
config: Optional[Config] = None,
|
|
17
|
+
ctx: Optional[Context] = None,
|
|
18
|
+
custom_headers: Optional[Dict[str, str]] = None,
|
|
19
|
+
verbose: bool = False,
|
|
20
|
+
):
|
|
21
|
+
super().__init__(config, ctx, custom_headers, verbose)
|
|
22
|
+
self.base_url = self.config.base_url
|
|
23
|
+
self.model = ImageConfig.DEFAULT_MODEL
|
|
24
|
+
|
|
25
|
+
def extract_urls(self, response: ImageGenerationResponse) -> List[str]:
|
|
26
|
+
urls = []
|
|
27
|
+
for item in response.data:
|
|
28
|
+
if item.error:
|
|
29
|
+
raise APIError(
|
|
30
|
+
f"图片生成失败: {item.error.get('message', 'Unknown error')}",
|
|
31
|
+
code=item.error.get("code"),
|
|
32
|
+
)
|
|
33
|
+
if item.url:
|
|
34
|
+
urls.append(item.url)
|
|
35
|
+
elif item.b64_json:
|
|
36
|
+
urls.append(f"data:image/png;base64,{item.b64_json}")
|
|
37
|
+
return urls
|
|
38
|
+
|
|
39
|
+
@observe
|
|
40
|
+
def generate(
|
|
41
|
+
self,
|
|
42
|
+
prompt: str,
|
|
43
|
+
size: Optional[str] = None,
|
|
44
|
+
watermark: Optional[bool] = None,
|
|
45
|
+
image: Optional[Union[str, List[str]]] = None,
|
|
46
|
+
response_format: Optional[str] = None,
|
|
47
|
+
optimize_prompt_mode: Optional[str] = None,
|
|
48
|
+
sequential_image_generation: Optional[str] = None,
|
|
49
|
+
sequential_image_generation_max_images: Optional[int] = None,
|
|
50
|
+
) -> ImageGenerationResponse:
|
|
51
|
+
request_params = {"prompt": prompt}
|
|
52
|
+
if size is not None:
|
|
53
|
+
request_params["size"] = size
|
|
54
|
+
if watermark is not None:
|
|
55
|
+
request_params["watermark"] = watermark
|
|
56
|
+
if image is not None:
|
|
57
|
+
request_params["image"] = image
|
|
58
|
+
if response_format is not None:
|
|
59
|
+
request_params["response_format"] = response_format
|
|
60
|
+
if optimize_prompt_mode is not None:
|
|
61
|
+
request_params["optimize_prompt_mode"] = optimize_prompt_mode
|
|
62
|
+
if sequential_image_generation is not None:
|
|
63
|
+
request_params["sequential_image_generation"] = sequential_image_generation
|
|
64
|
+
if sequential_image_generation_max_images is not None:
|
|
65
|
+
request_params["sequential_image_generation_max_images"] = (
|
|
66
|
+
sequential_image_generation_max_images
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
request = ImageGenerationRequest(**request_params)
|
|
70
|
+
|
|
71
|
+
data = self._request(
|
|
72
|
+
method="POST",
|
|
73
|
+
url=f"{self.base_url}/api/v3/images/generations",
|
|
74
|
+
json=request.to_api_request(self.model),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if "error" in data and data["error"]:
|
|
78
|
+
raise APIError(
|
|
79
|
+
f"API 返回错误: {data['error'].get('message', 'Unknown error')}",
|
|
80
|
+
code=data["error"].get("code"),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
parsed_response = ImageGenerationResponse(**data)
|
|
84
|
+
|
|
85
|
+
return parsed_response
|
|
86
|
+
|
|
87
|
+
async def generate_async(
|
|
88
|
+
self,
|
|
89
|
+
prompt: str,
|
|
90
|
+
size: Optional[str] = None,
|
|
91
|
+
watermark: Optional[bool] = None,
|
|
92
|
+
image: Optional[Union[str, List[str]]] = None,
|
|
93
|
+
response_format: Optional[str] = None,
|
|
94
|
+
optimize_prompt_mode: Optional[str] = None,
|
|
95
|
+
sequential_image_generation: Optional[str] = None,
|
|
96
|
+
sequential_image_generation_max_images: Optional[int] = None,
|
|
97
|
+
) -> ImageGenerationResponse:
|
|
98
|
+
loop = asyncio.get_event_loop()
|
|
99
|
+
return await loop.run_in_executor(
|
|
100
|
+
None,
|
|
101
|
+
self.generate,
|
|
102
|
+
prompt,
|
|
103
|
+
size,
|
|
104
|
+
watermark,
|
|
105
|
+
image,
|
|
106
|
+
response_format,
|
|
107
|
+
optimize_prompt_mode,
|
|
108
|
+
sequential_image_generation,
|
|
109
|
+
sequential_image_generation_max_images,
|
|
110
|
+
)
|