coze-coding-utils 0.2.3a3__tar.gz → 0.2.5__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.
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/PKG-INFO +1 -1
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/pyproject.toml +1 -1
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/agent_helper.py +93 -15
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/stream_runner.py +31 -22
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/node_log.py +0 -1
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/converter/request_converter.py +22 -0
- coze_coding_utils-0.2.3a3/src/coze_coding_utils/error/test_classifier.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/.gitignore +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/LICENSE +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/README.md +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/classifier.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/codes.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/exceptions.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/patterns.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/file/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/file/file.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/graph_helper.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/common.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/config.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/err_trace.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/loop_trace.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/parser.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/write_log.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/client.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/server.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/converter/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/converter/response_converter.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/handler.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/request.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/response.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/__init__.py +0 -0
- {coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/context.py +0 -0
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/agent_helper.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
import json
|
|
3
|
-
import
|
|
4
|
-
from
|
|
3
|
+
import requests
|
|
4
|
+
from urllib.parse import urlparse, urlencode, parse_qs, urljoin
|
|
5
|
+
from typing import Any, Dict, List, Tuple, Iterator, Optional
|
|
5
6
|
import time
|
|
6
7
|
from coze_coding_utils.file.file import File, FileOps, infer_file_category
|
|
7
8
|
from coze_coding_utils.error import classify_error
|
|
@@ -31,6 +32,78 @@ from coze_coding_utils.messages.server import (
|
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
_VIDEO_MIME_MAP: Dict[str, str] = {
|
|
36
|
+
"mp4": "video/mp4",
|
|
37
|
+
"avi": "video/x-msvideo",
|
|
38
|
+
"mov": "video/quicktime",
|
|
39
|
+
"mkv": "video/x-matroska",
|
|
40
|
+
"flv": "video/x-flv",
|
|
41
|
+
"wmv": "video/x-ms-wmv",
|
|
42
|
+
"webm": "video/webm",
|
|
43
|
+
"m4v": "video/x-m4v",
|
|
44
|
+
"3gp": "video/3gpp",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# 下游 LLM API 实际支持的视频 MIME 类型白名单
|
|
48
|
+
_SUPPORTED_VIDEO_MIMES = {
|
|
49
|
+
"video/mp4",
|
|
50
|
+
"video/quicktime",
|
|
51
|
+
"video/webm",
|
|
52
|
+
"video/3gpp",
|
|
53
|
+
"video/x-m4v",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _probe_remote_mime(url: str) -> Optional[str]:
|
|
58
|
+
"""HEAD 请求探测远程文件的真实 Content-Type,失败时返回 None"""
|
|
59
|
+
try:
|
|
60
|
+
resp = requests.head(url, timeout=5, allow_redirects=True)
|
|
61
|
+
ct = resp.headers.get("Content-Type", "")
|
|
62
|
+
return ct.split(";")[0].strip() or None
|
|
63
|
+
except Exception:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _ensure_video_content_type(url: str, mime_type: str) -> str:
|
|
68
|
+
"""
|
|
69
|
+
对象存储(TOS/S3)的 presigned URL 可能返回 application/octet-stream。
|
|
70
|
+
通过追加 response-content-type 参数让服务端在响应中覆盖 Content-Type,
|
|
71
|
+
从而使 LLM API fetch URL 时能拿到正确的 video MIME,而不会因 octet-stream 被拒。
|
|
72
|
+
已有签名不受影响(TOS/S3 response-* 参数不计入签名哈希)。
|
|
73
|
+
"""
|
|
74
|
+
parsed = urlparse(url)
|
|
75
|
+
# 已经有正确的 response-content-type,不重复添加
|
|
76
|
+
qs = parse_qs(parsed.query, keep_blank_values=True)
|
|
77
|
+
if qs.get("response-content-type") == [mime_type]:
|
|
78
|
+
return url
|
|
79
|
+
sep = "&" if parsed.query else "?"
|
|
80
|
+
return url + sep + "response-content-type=" + requests.utils.quote(mime_type, safe="")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _build_video_parts(
|
|
84
|
+
file_data: File, file_info: Any, file_ext: str, mime_override: Optional[str] = None
|
|
85
|
+
) -> List[Dict[str, Any]]:
|
|
86
|
+
"""将视频文件转换为 content_parts,不支持的格式退化为文本描述。
|
|
87
|
+
mime_override: 外部已探测好的 MIME,传入时跳过内部探测,避免重复 HEAD 请求。
|
|
88
|
+
"""
|
|
89
|
+
ext = file_ext.lstrip(".").lower()
|
|
90
|
+
mime_type = mime_override or _VIDEO_MIME_MAP.get(ext)
|
|
91
|
+
if not mime_type and file_data.is_remote:
|
|
92
|
+
mime_type = _probe_remote_mime(file_info.url)
|
|
93
|
+
|
|
94
|
+
if mime_type and mime_type in _SUPPORTED_VIDEO_MIMES:
|
|
95
|
+
# 追加 response-content-type 参数,确保对象存储返回正确的 Content-Type
|
|
96
|
+
# 防止 LLM API fetch URL 时因拿到 application/octet-stream 而报错
|
|
97
|
+
video_url = _ensure_video_content_type(file_info.url, mime_type) if file_data.is_remote else file_info.url
|
|
98
|
+
return [
|
|
99
|
+
{"type": "text", "text": f'{file_data.url}'},
|
|
100
|
+
{"type": "video_url", "video_url": {"url": video_url, "media_type": mime_type}},
|
|
101
|
+
]
|
|
102
|
+
else:
|
|
103
|
+
fmt_hint = mime_type or (f".{ext}" if ext else "unknown")
|
|
104
|
+
return [{"type": "text", "text": f"video url: {file_info.url} (format: {fmt_hint})"}]
|
|
105
|
+
|
|
106
|
+
|
|
34
107
|
def to_stream_input(msg: ClientMessage) -> Dict[str, Any]:
|
|
35
108
|
content_parts = []
|
|
36
109
|
if msg and msg.content and msg.content.query and msg.content.query.prompt:
|
|
@@ -43,7 +116,23 @@ def to_stream_input(msg: ClientMessage) -> Dict[str, Any]:
|
|
|
43
116
|
and block.content.upload_file
|
|
44
117
|
):
|
|
45
118
|
file_info = block.content.upload_file
|
|
46
|
-
|
|
119
|
+
# 第一轮:从 URL 后缀识别类型
|
|
120
|
+
file_type, file_ext = infer_file_category(file_info.url)
|
|
121
|
+
# 第二轮:URL 无后缀时用 file_name 兜底
|
|
122
|
+
if file_type == "default" and file_info.file_name:
|
|
123
|
+
file_type, file_ext = infer_file_category(file_info.file_name)
|
|
124
|
+
# 第三轮:两轮扩展名都无法识别时,HEAD 探测真实 Content-Type
|
|
125
|
+
probed_mime: Optional[str] = None
|
|
126
|
+
if file_type == "default" and file_info.url.startswith(("http://", "https://")):
|
|
127
|
+
probed_mime = _probe_remote_mime(file_info.url)
|
|
128
|
+
if probed_mime:
|
|
129
|
+
top = probed_mime.split("/")[0]
|
|
130
|
+
if top == "video":
|
|
131
|
+
file_type = "video"
|
|
132
|
+
elif top == "image":
|
|
133
|
+
file_type = "image"
|
|
134
|
+
elif top == "audio":
|
|
135
|
+
file_type = "audio"
|
|
47
136
|
file_data = File(url=file_info.url, file_type=file_type)
|
|
48
137
|
# check is image
|
|
49
138
|
if file_data.file_type == "image":
|
|
@@ -61,18 +150,7 @@ def to_stream_input(msg: ClientMessage) -> Dict[str, Any]:
|
|
|
61
150
|
)
|
|
62
151
|
# check is video
|
|
63
152
|
elif file_data.file_type == "video":
|
|
64
|
-
content_parts.
|
|
65
|
-
{
|
|
66
|
-
"type": "text",
|
|
67
|
-
"text": f'{file_data.url}'
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
content_parts.append(
|
|
71
|
-
{
|
|
72
|
-
"type": "video_url",
|
|
73
|
-
"video_url": {"url": file_info.url},
|
|
74
|
-
}
|
|
75
|
-
)
|
|
153
|
+
content_parts.extend(_build_video_parts(file_data, file_info, file_ext, probed_mime))
|
|
76
154
|
# check is audio
|
|
77
155
|
elif file_data.file_type == "audio":
|
|
78
156
|
content_parts.append(
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/stream_runner.py
RENAMED
|
@@ -4,7 +4,8 @@ import threading
|
|
|
4
4
|
import contextvars
|
|
5
5
|
import logging
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
-
from
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Dict, Iterator, AsyncIterable, Optional, Literal
|
|
8
9
|
from langchain_core.runnables import RunnableConfig
|
|
9
10
|
from langgraph.graph.state import CompiledStateGraph
|
|
10
11
|
from coze_coding_utils.helper.agent_helper import (
|
|
@@ -31,6 +32,15 @@ logger = logging.getLogger(__name__)
|
|
|
31
32
|
TIMEOUT_SECONDS = 900
|
|
32
33
|
PING_INTERVAL_SECONDS = 30
|
|
33
34
|
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class RunOpt:
|
|
38
|
+
workflow_debug: bool = False
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def stream_mode(self) -> Literal["debug", "updates"]:
|
|
42
|
+
return "debug" if self.workflow_debug else "updates"
|
|
43
|
+
|
|
34
44
|
class WorkflowEventType:
|
|
35
45
|
WORKFLOW_START = "workflow_start"
|
|
36
46
|
WORKFLOW_END = "workflow_end"
|
|
@@ -47,16 +57,16 @@ class WorkflowErrorCode:
|
|
|
47
57
|
|
|
48
58
|
class BaseStreamRunner(ABC):
|
|
49
59
|
@abstractmethod
|
|
50
|
-
def stream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context) -> Iterator[Any]:
|
|
60
|
+
def stream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context, run_opt: Optional[RunOpt] = None) -> Iterator[Any]:
|
|
51
61
|
pass
|
|
52
62
|
|
|
53
63
|
@abstractmethod
|
|
54
|
-
async def astream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context) -> AsyncIterable[Any]:
|
|
64
|
+
async def astream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context, run_opt: Optional[RunOpt] = None) -> AsyncIterable[Any]:
|
|
55
65
|
pass
|
|
56
66
|
|
|
57
67
|
|
|
58
68
|
class AgentStreamRunner(BaseStreamRunner):
|
|
59
|
-
def stream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context) -> Iterator[Any]:
|
|
69
|
+
def stream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context, run_opt: Optional[RunOpt] = None) -> Iterator[Any]:
|
|
60
70
|
client_msg, session_id = to_client_message(payload)
|
|
61
71
|
run_config["recursion_limit"] = 100
|
|
62
72
|
run_config["configurable"] = {"thread_id": session_id}
|
|
@@ -101,7 +111,7 @@ class AgentStreamRunner(BaseStreamRunner):
|
|
|
101
111
|
)
|
|
102
112
|
yield end_msg
|
|
103
113
|
|
|
104
|
-
async def astream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context) -> AsyncIterable[Any]:
|
|
114
|
+
async def astream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context, run_opt: Optional[RunOpt] = None) -> AsyncIterable[Any]:
|
|
105
115
|
client_msg, session_id = to_client_message(payload)
|
|
106
116
|
run_config["recursion_limit"] = 100
|
|
107
117
|
run_config["configurable"] = {"thread_id": session_id}
|
|
@@ -194,9 +204,6 @@ class AgentStreamRunner(BaseStreamRunner):
|
|
|
194
204
|
|
|
195
205
|
|
|
196
206
|
class WorkflowStreamRunner(BaseStreamRunner):
|
|
197
|
-
def __init__(self):
|
|
198
|
-
self._node_start_times: Dict[str, float] = {}
|
|
199
|
-
|
|
200
207
|
def _serialize_data(self, data: Any) -> Any:
|
|
201
208
|
if isinstance(data, dict):
|
|
202
209
|
return {k: self._serialize_data(v) for k, v in data.items()}
|
|
@@ -221,7 +228,9 @@ class WorkflowStreamRunner(BaseStreamRunner):
|
|
|
221
228
|
result.update(kwargs)
|
|
222
229
|
return result
|
|
223
230
|
|
|
224
|
-
def stream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context) -> Iterator[Any]:
|
|
231
|
+
def stream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context, run_opt: Optional[RunOpt] = None) -> Iterator[Any]:
|
|
232
|
+
if run_opt is None:
|
|
233
|
+
run_opt = RunOpt()
|
|
225
234
|
run_config["recursion_limit"] = 100
|
|
226
235
|
if "configurable" not in run_config:
|
|
227
236
|
run_config["configurable"] = {}
|
|
@@ -232,21 +241,19 @@ class WorkflowStreamRunner(BaseStreamRunner):
|
|
|
232
241
|
node_start_times: Dict[str, float] = {}
|
|
233
242
|
final_output = {}
|
|
234
243
|
seq = 0
|
|
235
|
-
is_debug = run_config.get("configurable", {}).get("workflow_debug", False)
|
|
236
|
-
stream_mode = "debug" if is_debug else "updates"
|
|
237
244
|
|
|
238
245
|
try:
|
|
239
246
|
seq += 1
|
|
240
247
|
yield (seq, self._build_event(WorkflowEventType.WORKFLOW_START, ctx))
|
|
241
248
|
|
|
242
|
-
for event in graph.stream(payload, stream_mode=stream_mode, config=run_config, context=ctx):
|
|
249
|
+
for event in graph.stream(payload, stream_mode=run_opt.stream_mode, config=run_config, context=ctx):
|
|
243
250
|
current_time = time.time()
|
|
244
251
|
if current_time - last_ping_time >= PING_INTERVAL_SECONDS:
|
|
245
252
|
seq += 1
|
|
246
253
|
yield (seq, self._build_event(WorkflowEventType.PING, ctx))
|
|
247
254
|
last_ping_time = current_time
|
|
248
255
|
|
|
249
|
-
if not
|
|
256
|
+
if not run_opt.workflow_debug:
|
|
250
257
|
if isinstance(event, dict):
|
|
251
258
|
logger.info(f"Debug event: {event}")
|
|
252
259
|
for node_name, node_output in event.items():
|
|
@@ -311,7 +318,9 @@ class WorkflowStreamRunner(BaseStreamRunner):
|
|
|
311
318
|
seq += 1
|
|
312
319
|
yield (seq, self._build_event(WorkflowEventType.ERROR, ctx, code=str(err.code), error_msg=err.message))
|
|
313
320
|
|
|
314
|
-
async def astream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context) -> AsyncIterable[Any]:
|
|
321
|
+
async def astream(self, payload: Dict[str, Any], graph: CompiledStateGraph, run_config: RunnableConfig, ctx: Context, run_opt: Optional[RunOpt] = None) -> AsyncIterable[Any]:
|
|
322
|
+
if run_opt is None:
|
|
323
|
+
run_opt = RunOpt()
|
|
315
324
|
run_config["recursion_limit"] = 100
|
|
316
325
|
if "configurable" not in run_config:
|
|
317
326
|
run_config["configurable"] = {}
|
|
@@ -323,9 +332,7 @@ class WorkflowStreamRunner(BaseStreamRunner):
|
|
|
323
332
|
start_time = time.time()
|
|
324
333
|
cancelled = threading.Event()
|
|
325
334
|
last_ping_time = [start_time]
|
|
326
|
-
|
|
327
|
-
stream_mode = "debug" if is_debug else "updates"
|
|
328
|
-
logger.info(f"Stream mode: {stream_mode}")
|
|
335
|
+
logger.info(f"Stream mode: {run_opt.stream_mode}")
|
|
329
336
|
seq = [0]
|
|
330
337
|
|
|
331
338
|
def producer():
|
|
@@ -339,7 +346,7 @@ class WorkflowStreamRunner(BaseStreamRunner):
|
|
|
339
346
|
seq[0] += 1
|
|
340
347
|
loop.call_soon_threadsafe(q.put_nowait, (seq[0], self._build_event(WorkflowEventType.WORKFLOW_START, ctx)))
|
|
341
348
|
|
|
342
|
-
for event in graph.stream(payload, stream_mode=stream_mode, config=run_config, context=ctx):
|
|
349
|
+
for event in graph.stream(payload, stream_mode=run_opt.stream_mode, config=run_config, context=ctx):
|
|
343
350
|
if cancelled.is_set():
|
|
344
351
|
logger.info(f"Workflow producer cancelled during iteration for run_id: {ctx.run_id}")
|
|
345
352
|
seq[0] += 1
|
|
@@ -358,7 +365,7 @@ class WorkflowStreamRunner(BaseStreamRunner):
|
|
|
358
365
|
loop.call_soon_threadsafe(q.put_nowait, (seq[0], self._build_event(WorkflowEventType.PING, ctx)))
|
|
359
366
|
last_ping_time[0] = current_time
|
|
360
367
|
|
|
361
|
-
if not
|
|
368
|
+
if not run_opt.workflow_debug:
|
|
362
369
|
if isinstance(event, dict):
|
|
363
370
|
for node_name, node_output in event.items():
|
|
364
371
|
logger.info(f"Node output: {node_name}")
|
|
@@ -474,7 +481,7 @@ async def agent_stream_handler(
|
|
|
474
481
|
t0 = time.time()
|
|
475
482
|
|
|
476
483
|
try:
|
|
477
|
-
async for chunk in stream_sse_func(payload, ctx
|
|
484
|
+
async for chunk in stream_sse_func(payload, ctx):
|
|
478
485
|
yield chunk
|
|
479
486
|
except asyncio.CancelledError:
|
|
480
487
|
logger.info(f"Agent stream cancelled for run_id: {run_id}")
|
|
@@ -517,15 +524,17 @@ async def workflow_stream_handler(
|
|
|
517
524
|
sse_event_func: Callable,
|
|
518
525
|
error_classifier: ErrorClassifier,
|
|
519
526
|
register_task_func: Callable[[str, asyncio.Task], None],
|
|
520
|
-
|
|
527
|
+
run_opt: Optional[RunOpt] = None,
|
|
521
528
|
) -> AsyncGenerator[str, None]:
|
|
529
|
+
if run_opt is None:
|
|
530
|
+
run_opt = RunOpt()
|
|
522
531
|
task = asyncio.current_task()
|
|
523
532
|
if task:
|
|
524
533
|
register_task_func(run_id, task)
|
|
525
534
|
logger.info(f"Registered workflow streaming task for run_id: {run_id}")
|
|
526
535
|
|
|
527
536
|
try:
|
|
528
|
-
async for chunk in stream_sse_func(payload, ctx,
|
|
537
|
+
async for chunk in stream_sse_func(payload, ctx, run_opt):
|
|
529
538
|
yield chunk
|
|
530
539
|
except asyncio.CancelledError:
|
|
531
540
|
logger.info(f"Workflow stream cancelled for run_id: {run_id}")
|
|
@@ -6,6 +6,10 @@ from coze_coding_utils.openai.types.request import (
|
|
|
6
6
|
ChatMessage,
|
|
7
7
|
)
|
|
8
8
|
from coze_coding_utils.file.file import File, FileOps, infer_file_category
|
|
9
|
+
from coze_coding_utils.helper.agent_helper import (
|
|
10
|
+
_VIDEO_MIME_MAP,
|
|
11
|
+
_ensure_video_content_type,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class RequestConverter:
|
|
@@ -110,6 +114,15 @@ class RequestConverter:
|
|
|
110
114
|
video_url_data = part.get("video_url", {})
|
|
111
115
|
url = video_url_data.get("url", "")
|
|
112
116
|
if url:
|
|
117
|
+
_, file_ext = infer_file_category(url)
|
|
118
|
+
ext = file_ext.lstrip(".").lower()
|
|
119
|
+
mime_type = _VIDEO_MIME_MAP.get(ext)
|
|
120
|
+
if mime_type:
|
|
121
|
+
fixed_url = _ensure_video_content_type(url, mime_type)
|
|
122
|
+
return [
|
|
123
|
+
{"type": "text", "text": url},
|
|
124
|
+
{"type": "video_url", "video_url": {"url": fixed_url, "media_type": mime_type}},
|
|
125
|
+
]
|
|
113
126
|
return [
|
|
114
127
|
{"type": "text", "text": url},
|
|
115
128
|
{"type": "video_url", "video_url": {"url": url}},
|
|
@@ -147,6 +160,15 @@ class RequestConverter:
|
|
|
147
160
|
{"type": "image_url", "image_url": {"url": url}},
|
|
148
161
|
]
|
|
149
162
|
elif file_type == "video":
|
|
163
|
+
_, file_ext = infer_file_category(url)
|
|
164
|
+
ext = file_ext.lstrip(".").lower()
|
|
165
|
+
mime_type = _VIDEO_MIME_MAP.get(ext)
|
|
166
|
+
if mime_type:
|
|
167
|
+
fixed_url = _ensure_video_content_type(url, mime_type)
|
|
168
|
+
return [
|
|
169
|
+
{"type": "text", "text": url},
|
|
170
|
+
{"type": "video_url", "video_url": {"url": fixed_url, "media_type": mime_type}},
|
|
171
|
+
]
|
|
150
172
|
return [
|
|
151
173
|
{"type": "text", "text": url},
|
|
152
174
|
{"type": "video_url", "video_url": {"url": url}},
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/classifier.py
RENAMED
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/exceptions.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/patterns.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/file/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/graph_helper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/err_trace.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/loop_trace.py
RENAMED
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/write_log.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/client.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/server.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/handler.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/request.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/response.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.3a3 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/context.py
RENAMED
|
File without changes
|