coze-coding-utils 0.2.4__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.4 → coze_coding_utils-0.2.5}/PKG-INFO +1 -1
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/pyproject.toml +1 -1
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/agent_helper.py +93 -15
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/converter/request_converter.py +22 -0
- coze_coding_utils-0.2.4/src/coze_coding_utils/error/test_classifier.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/.gitignore +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/LICENSE +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/README.md +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/classifier.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/codes.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/exceptions.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/patterns.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/file/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/file/file.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/graph_helper.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/stream_runner.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/common.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/config.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/err_trace.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/loop_trace.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/node_log.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/parser.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/log/write_log.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/client.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/server.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/converter/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/converter/response_converter.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/handler.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/request.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/response.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/__init__.py +0 -0
- {coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/context.py +0 -0
{coze_coding_utils-0.2.4 → 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(
|
|
@@ -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
|
|
File without changes
|
{coze_coding_utils-0.2.4 → 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.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/error/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/graph_helper.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/helper/stream_runner.py
RENAMED
|
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
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/client.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/messages/server.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/request.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/openai/types/response.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/__init__.py
RENAMED
|
File without changes
|
{coze_coding_utils-0.2.4 → coze_coding_utils-0.2.5}/src/coze_coding_utils/runtime_ctx/context.py
RENAMED
|
File without changes
|