coze-coding-dev-sdk 0.4.2__tar.gz → 0.4.4__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.
Files changed (44) hide show
  1. {coze_coding_dev_sdk-0.4.2/coze_coding_dev_sdk.egg-info → coze_coding_dev_sdk-0.4.4}/PKG-INFO +1 -1
  2. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/__init__.py +1 -1
  3. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/chat.py +13 -1
  4. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/image.py +25 -7
  5. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/search.py +12 -2
  6. coze_coding_dev_sdk-0.4.4/coze_coding_dev_sdk/cli/utils.py +41 -0
  7. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/video.py +25 -12
  8. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/voice.py +33 -13
  9. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/core/__init__.py +6 -0
  10. coze_coding_dev_sdk-0.4.4/coze_coding_dev_sdk/core/client.py +188 -0
  11. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/core/config.py +6 -0
  12. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/image/client.py +3 -14
  13. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/llm/client.py +16 -2
  14. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/search/client.py +2 -1
  15. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/video/client.py +3 -2
  16. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/voice/asr.py +26 -25
  17. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/voice/tts.py +25 -22
  18. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4/coze_coding_dev_sdk.egg-info}/PKG-INFO +1 -1
  19. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk.egg-info/SOURCES.txt +1 -0
  20. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/pyproject.toml +1 -1
  21. coze_coding_dev_sdk-0.4.2/coze_coding_dev_sdk/core/client.py +0 -97
  22. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/CHANGELOG.md +0 -0
  23. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/LICENSE +0 -0
  24. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/MANIFEST.in +0 -0
  25. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/README.md +0 -0
  26. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/__init__.py +0 -0
  27. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/cli.py +0 -0
  28. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/cli/constants.py +0 -0
  29. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/core/exceptions.py +0 -0
  30. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/image/__init__.py +0 -0
  31. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/image/models.py +0 -0
  32. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/llm/__init__.py +0 -0
  33. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/llm/models.py +0 -0
  34. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/search/__init__.py +0 -0
  35. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/search/models.py +0 -0
  36. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/video/__init__.py +0 -0
  37. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/video/models.py +0 -0
  38. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/voice/__init__.py +0 -0
  39. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk/voice/models.py +0 -0
  40. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk.egg-info/dependency_links.txt +0 -0
  41. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk.egg-info/entry_points.txt +0 -0
  42. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk.egg-info/requires.txt +0 -0
  43. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/coze_coding_dev_sdk.egg-info/top_level.txt +0 -0
  44. {coze_coding_dev_sdk-0.4.2 → coze_coding_dev_sdk-0.4.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coze-coding-dev-sdk
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Coze Coding Dev SDK - 优雅的多功能 AI SDK,支持图片生成、视频生成、语音合成、语音识别、大语言模型和联网搜索。包含命令行工具 coze-coding-ai,支持 Context 上下文追踪
5
5
  Author-email: Coze Coding Integration Team <support@coze.com>
6
6
  Maintainer-email: Coze Coding Integration Team <support@coze.com>
@@ -44,7 +44,7 @@ from .video import (
44
44
  VideoGenerationTask
45
45
  )
46
46
 
47
- __version__ = "0.2.0"
47
+ __version__ = "0.4.4"
48
48
 
49
49
  __all__ = [
50
50
  "Config",
@@ -22,17 +22,29 @@ console = Console()
22
22
  "--output", "-o", type=click.Path(), help="Output file path (JSON format)"
23
23
  )
24
24
  @click.option("--stream", is_flag=True, help="Stream the response in real-time")
25
+ @click.option(
26
+ "--header",
27
+ "-H",
28
+ multiple=True,
29
+ help="自定义 HTTP 请求头 (格式: 'Key: Value' 或 'Key=Value',可多次使用)",
30
+ )
31
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求日志")
25
32
  def chat(
26
33
  prompt: str,
27
34
  system: Optional[str],
28
35
  thinking: bool,
29
36
  output: Optional[str],
30
37
  stream: bool,
38
+ header: tuple,
39
+ verbose: bool,
31
40
  ):
32
41
  """Chat with AI using natural language."""
33
42
  try:
43
+ from .utils import parse_headers
44
+
34
45
  config = Config()
35
- client = LLMClient(config)
46
+ custom_headers = parse_headers(header)
47
+ client = LLMClient(config, custom_headers=custom_headers, verbose=verbose)
36
48
 
37
49
  messages = []
38
50
  if system:
@@ -4,7 +4,7 @@ import time
4
4
  from typing import Optional
5
5
 
6
6
  import click
7
- from coze_coding_utils.runtime_ctx.context import Context
7
+ from coze_coding_utils.runtime_ctx.context import new_context
8
8
  from rich.console import Console
9
9
 
10
10
  from ..core.config import Config
@@ -56,22 +56,40 @@ def validate_and_normalize_size(size: str) -> str:
56
56
  help="Multiple reference image URLs or paths (can be used multiple times)",
57
57
  )
58
58
  @click.option("--mock", is_flag=True, help="使用 mock 模式(测试运行)")
59
+ @click.option(
60
+ "--header",
61
+ "-H",
62
+ multiple=True,
63
+ help="自定义 HTTP 请求头 (格式: 'Key: Value' 或 'Key=Value',可多次使用)",
64
+ )
65
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求日志")
59
66
  def image(
60
- prompt: str, output: str, size: str, image: Optional[str], images: tuple, mock: bool
67
+ prompt: str,
68
+ output: str,
69
+ size: str,
70
+ image: Optional[str],
71
+ images: tuple,
72
+ mock: bool,
73
+ header: tuple,
74
+ verbose: bool,
61
75
  ):
62
76
  """Generate image using AI."""
63
77
  try:
78
+ from .utils import parse_headers
79
+
64
80
  config = Config()
65
81
 
66
82
  ctx = None
83
+ custom_headers = parse_headers(header)
84
+
67
85
  if mock:
68
- ctx = Context(
69
- request_id=f"mock-req-{int(time.time())}",
70
- headers={RUN_MODE_HEADER: RUN_MODE_TEST},
71
- )
86
+ ctx = new_context(method="image.generate", headers=custom_headers)
87
+ custom_headers[RUN_MODE_HEADER] = RUN_MODE_TEST
72
88
  console.print("[yellow]🧪 Mock 模式已启用(测试运行)[/yellow]")
73
89
 
74
- client = ImageGenerationClient(config, ctx=ctx)
90
+ client = ImageGenerationClient(
91
+ config, ctx=ctx, custom_headers=custom_headers, verbose=verbose
92
+ )
75
93
 
76
94
  reference_images = None
77
95
  if image:
@@ -121,7 +121,14 @@ def save_to_json(response, output_path: str):
121
121
  @click.option("--time-range", help="发文时间范围,如: 1d, 1w, 1m (仅 web 类型)")
122
122
  @click.option("--output", "-o", type=click.Path(), help="输出 JSON 文件路径")
123
123
  @click.option("--format", "-f", type=click.Choice(["table", "json", "simple"]), default="table", help="输出格式")
124
- def search(query, type, count, summary, need_content, need_url, sites, block_hosts, time_range, output, format):
124
+ @click.option(
125
+ "--header",
126
+ "-H",
127
+ multiple=True,
128
+ help="自定义 HTTP 请求头 (格式: 'Key: Value' 或 'Key=Value',可多次使用)",
129
+ )
130
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求日志")
131
+ def search(query, type, count, summary, need_content, need_url, sites, block_hosts, time_range, output, format, header, verbose):
125
132
  """联网搜索
126
133
 
127
134
  支持三种搜索类型:
@@ -147,8 +154,11 @@ def search(query, type, count, summary, need_content, need_url, sites, block_hos
147
154
  coze-coding-ai search "新闻" --time-range "1d" --need-url
148
155
  """
149
156
  try:
157
+ from .utils import parse_headers
158
+
150
159
  config = Config()
151
- client = SearchClient(config)
160
+ custom_headers = parse_headers(header)
161
+ client = SearchClient(config, custom_headers=custom_headers, verbose=verbose)
152
162
 
153
163
  if type == "image":
154
164
  console.print(f"[bold magenta]正在搜索图片:[/bold magenta] {query}")
@@ -0,0 +1,41 @@
1
+ from typing import Dict, Optional, Tuple
2
+
3
+
4
+ def parse_headers(header_strings: Tuple[str, ...]) -> Optional[Dict[str, str]]:
5
+ """
6
+ 解析命令行传入的 header 字符串
7
+
8
+ 支持格式:
9
+ - "Key: Value"
10
+ - "Key=Value"
11
+
12
+ 示例:
13
+ --header "X-Custom: value1" --header "X-Test=value2"
14
+
15
+ Args:
16
+ header_strings: 命令行传入的 header 字符串元组
17
+
18
+ Returns:
19
+ Dict[str, str] or None: 解析后的 headers 字典,如果没有则返回 None
20
+
21
+ Raises:
22
+ ValueError: 如果 header 格式不正确
23
+ """
24
+ if not header_strings:
25
+ return None
26
+
27
+ headers = {}
28
+ for header_str in header_strings:
29
+ if ": " in header_str:
30
+ key, value = header_str.split(": ", 1)
31
+ elif "=" in header_str:
32
+ key, value = header_str.split("=", 1)
33
+ else:
34
+ raise ValueError(
35
+ f"Invalid header format: '{header_str}'. "
36
+ f"Use 'Key: Value' or 'Key=Value'"
37
+ )
38
+
39
+ headers[key.strip()] = value.strip()
40
+
41
+ return headers if headers else None
@@ -4,7 +4,7 @@ import time
4
4
  from typing import Optional
5
5
 
6
6
  import click
7
- from coze_coding_utils.runtime_ctx.context import Context
7
+ from coze_coding_utils.runtime_ctx.context import new_context
8
8
  from rich.console import Console
9
9
  from rich.progress import Progress, SpinnerColumn, TextColumn
10
10
  from rich.table import Table
@@ -54,6 +54,13 @@ def parse_resolution(size: Optional[str]) -> tuple[Optional[str], Optional[str]]
54
54
  @click.option("--max-polls", type=int, default=60, help="Maximum poll attempts")
55
55
  @click.option("--output", "-o", type=click.Path(), help="Output file path (JSON)")
56
56
  @click.option("--mock", is_flag=True, help="使用 mock 模式(测试运行)")
57
+ @click.option(
58
+ "--header",
59
+ "-H",
60
+ multiple=True,
61
+ help="自定义 HTTP 请求头 (格式: 'Key: Value' 或 'Key=Value',可多次使用)",
62
+ )
63
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求日志")
57
64
  def video(
58
65
  prompt: Optional[str],
59
66
  image_url: Optional[str],
@@ -68,20 +75,24 @@ def video(
68
75
  max_polls: int,
69
76
  output: Optional[str],
70
77
  mock: bool,
78
+ header: tuple,
79
+ verbose: bool,
71
80
  ):
72
81
  """Generate video using AI."""
73
82
  try:
83
+ from .utils import parse_headers
84
+
74
85
  config = Config()
75
86
 
76
87
  ctx = None
88
+ custom_headers = parse_headers(header)
89
+
77
90
  if mock:
78
- ctx = Context(
79
- request_id=f"mock-req-{int(time.time())}",
80
- headers={RUN_MODE_HEADER: RUN_MODE_TEST},
81
- )
91
+ ctx = new_context(method="video.generate", headers=custom_headers)
92
+ custom_headers[RUN_MODE_HEADER] = RUN_MODE_TEST
82
93
  console.print("[yellow]🧪 Mock 模式已启用(测试运行)[/yellow]")
83
94
 
84
- client = VideoGenerationClient(config, ctx=ctx)
95
+ client = VideoGenerationClient(config, ctx=ctx, custom_headers=custom_headers, verbose=verbose)
85
96
 
86
97
  image_urls = None
87
98
  if image_url:
@@ -203,20 +214,22 @@ def video(
203
214
  @click.argument("task_id")
204
215
  @click.option("--output", "-o", type=click.Path(), help="Output file path (JSON)")
205
216
  @click.option("--mock", is_flag=True, help="使用 mock 模式(测试运行)")
206
- def video_status(task_id: str, output: Optional[str], mock: bool):
217
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求日志")
218
+ def video_status(task_id: str, output: Optional[str], mock: bool, verbose: bool):
207
219
  """Check video generation task status."""
208
220
  try:
209
221
  config = Config()
210
222
 
211
223
  ctx = None
212
224
  if mock:
213
- ctx = Context(
214
- request_id=f"mock-req-{int(time.time())}",
215
- headers={RUN_MODE_HEADER: RUN_MODE_TEST},
216
- )
225
+ ctx = new_context(method="video.status")
217
226
  console.print("[yellow]🧪 Mock 模式已启用(测试运行)[/yellow]")
218
227
 
219
- client = VideoGenerationClient(config, ctx=ctx)
228
+ custom_headers = {}
229
+ if mock:
230
+ custom_headers[RUN_MODE_HEADER] = RUN_MODE_TEST
231
+
232
+ client = VideoGenerationClient(config, ctx=ctx, custom_headers=custom_headers, verbose=verbose)
220
233
 
221
234
  task_result = client._get_task_status(task_id)
222
235
  result = task_result.model_dump()
@@ -5,7 +5,7 @@ import time
5
5
  from typing import Optional
6
6
 
7
7
  import click
8
- from coze_coding_utils.runtime_ctx.context import Context
8
+ from coze_coding_utils.runtime_ctx.context import new_context
9
9
  from rich.console import Console
10
10
  from rich.panel import Panel
11
11
  from rich.progress import Progress, SpinnerColumn, TextColumn
@@ -63,6 +63,13 @@ COMMON_SPEAKERS = {
63
63
  @click.option("--loudness-rate", type=int, default=0, help="音量 (-50 到 100)")
64
64
  @click.option("--ssml", is_flag=True, help="使用 SSML 格式")
65
65
  @click.option("--mock", is_flag=True, help="使用 mock 模式(测试运行)")
66
+ @click.option(
67
+ "--header",
68
+ "-H",
69
+ multiple=True,
70
+ help="自定义 HTTP 请求头 (格式: 'Key: Value' 或 'Key=Value',可多次使用)",
71
+ )
72
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求日志")
66
73
  def tts(
67
74
  text,
68
75
  output,
@@ -74,6 +81,8 @@ def tts(
74
81
  loudness_rate,
75
82
  ssml,
76
83
  mock,
84
+ header,
85
+ verbose,
77
86
  ):
78
87
  """语音合成 (Text-to-Speech)
79
88
 
@@ -111,17 +120,19 @@ def tts(
111
120
  coze-coding-ai tts "儿童故事" -o story.mp3 -s zh_female_xueayi_saturn_bigtts --speech-rate 20
112
121
  """
113
122
  try:
123
+ from .utils import parse_headers
124
+
114
125
  config = Config()
115
126
 
116
127
  ctx = None
128
+ custom_headers = parse_headers(header)
129
+
117
130
  if mock:
118
- ctx = Context(
119
- request_id=f"mock-req-{int(time.time())}",
120
- headers={RUN_MODE_HEADER: RUN_MODE_TEST},
121
- )
131
+ ctx = new_context(method="tts.generate", headers=custom_headers)
132
+ custom_headers[RUN_MODE_HEADER] = RUN_MODE_TEST
122
133
  console.print("[yellow]🧪 Mock 模式已启用(测试运行)[/yellow]")
123
134
 
124
- client = TTSClient(config, ctx=ctx)
135
+ client = TTSClient(config, ctx=ctx, custom_headers=custom_headers, verbose=verbose)
125
136
 
126
137
  with Progress(
127
138
  SpinnerColumn(),
@@ -216,7 +227,14 @@ def tts(
216
227
  )
217
228
  @click.option("--base64", is_flag=True, help="将本地文件转为 base64 上传")
218
229
  @click.option("--mock", is_flag=True, help="使用 mock 模式(测试运行)")
219
- def asr(audio, uid, output, format, base64, mock):
230
+ @click.option(
231
+ "--header",
232
+ "-H",
233
+ multiple=True,
234
+ help="自定义 HTTP 请求头 (格式: 'Key: Value' 或 'Key=Value',可多次使用)",
235
+ )
236
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求日志")
237
+ def asr(audio, uid, output, format, base64, mock, header, verbose):
220
238
  """语音识别 (Automatic Speech Recognition)
221
239
 
222
240
  将语音音频转换为文本。
@@ -231,20 +249,22 @@ def asr(audio, uid, output, format, base64, mock):
231
249
  coze-coding-ai asr https://example.com/audio.mp3
232
250
  coze-coding-ai asr ./audio.mp3 -o result.txt
233
251
  coze-coding-ai asr ./audio.mp3 -f json
234
- coze-coding-ai asr ./audio.mp3 --base64
252
+ coze-coding-ai asr audio.mp3 --base64 --output result.txt
235
253
  """
236
254
  try:
255
+ from .utils import parse_headers
256
+
237
257
  config = Config()
238
258
 
239
259
  ctx = None
260
+ custom_headers = parse_headers(header)
261
+
240
262
  if mock:
241
- ctx = Context(
242
- request_id=f"mock-req-{int(time.time())}",
243
- headers={RUN_MODE_HEADER: RUN_MODE_TEST},
244
- )
263
+ ctx = new_context(method="asr.recognize", headers=custom_headers)
264
+ custom_headers[RUN_MODE_HEADER] = RUN_MODE_TEST
245
265
  console.print("[yellow]🧪 Mock 模式已启用(测试运行)[/yellow]")
246
266
 
247
- client = ASRClient(config, ctx=ctx)
267
+ client = ASRClient(config, ctx=ctx, custom_headers=custom_headers, verbose=verbose)
248
268
 
249
269
  audio_url = None
250
270
  audio_base64 = None
@@ -8,6 +8,11 @@ from .exceptions import (
8
8
  ValidationError
9
9
  )
10
10
 
11
+ try:
12
+ from .. import __version__
13
+ except ImportError:
14
+ __version__ = "0.0.0"
15
+
11
16
  __all__ = [
12
17
  "BaseClient",
13
18
  "Config",
@@ -16,4 +21,5 @@ __all__ = [
16
21
  "APIError",
17
22
  "NetworkError",
18
23
  "ValidationError",
24
+ "__version__",
19
25
  ]
@@ -0,0 +1,188 @@
1
+ import json
2
+ import time
3
+ from typing import Dict, Optional
4
+
5
+ import requests
6
+ from coze_coding_utils.runtime_ctx.context import Context, default_headers
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.syntax import Syntax
10
+
11
+ from .config import Config
12
+ from .exceptions import APIError, NetworkError
13
+
14
+ console = Console()
15
+
16
+
17
+ class BaseClient:
18
+ def __init__(
19
+ self,
20
+ config: Optional[Config] = None,
21
+ ctx: Optional[Context] = None,
22
+ custom_headers: Optional[Dict[str, str]] = None,
23
+ verbose: bool = False,
24
+ ):
25
+ if config is None:
26
+ config = Config()
27
+ self.config = config
28
+ self.ctx = ctx
29
+ self.custom_headers = custom_headers or {}
30
+ self.verbose = verbose
31
+
32
+ def _request(
33
+ self, method: str, url: str, headers: Optional[Dict[str, str]] = None, **kwargs
34
+ ) -> dict:
35
+ request_headers = {}
36
+
37
+ if self.ctx is not None:
38
+ ctx_headers = default_headers(self.ctx)
39
+ request_headers.update(ctx_headers)
40
+
41
+ if self.custom_headers:
42
+ request_headers.update(self.custom_headers)
43
+
44
+ config_headers = self.config.get_headers(headers)
45
+ request_headers.update(config_headers)
46
+
47
+ response = self._make_request(
48
+ method=method, url=url, headers=request_headers, **kwargs
49
+ )
50
+ return self._handle_response(response)
51
+
52
+ def _sanitize_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
53
+ sanitized = headers.copy()
54
+ if "Authorization" in sanitized:
55
+ token = sanitized["Authorization"]
56
+ if token.startswith("Bearer "):
57
+ token = token[7:]
58
+ if len(token) > 16:
59
+ sanitized["Authorization"] = f"Bearer {token[:8]}...{token[-4:]}"
60
+ else:
61
+ sanitized["Authorization"] = "Bearer ****"
62
+ return sanitized
63
+
64
+ def _log_request(self, method: str, url: str, **kwargs):
65
+ if not self.verbose:
66
+ return
67
+
68
+ parts = []
69
+ parts.append(f"[bold cyan]{method}[/bold cyan] {url}\n")
70
+
71
+ headers = kwargs.get("headers", {})
72
+ sanitized_headers = self._sanitize_headers(headers)
73
+ if sanitized_headers:
74
+ parts.append("[bold]Headers:[/bold]")
75
+ for key, value in sanitized_headers.items():
76
+ parts.append(f" {key}: {value}")
77
+ parts.append("")
78
+
79
+ if "json" in kwargs and kwargs["json"]:
80
+ parts.append("[bold]Body:[/bold]")
81
+ try:
82
+ json_str = json.dumps(kwargs["json"], ensure_ascii=False, indent=2)
83
+ parts.append("")
84
+ except Exception:
85
+ json_str = str(kwargs["json"])
86
+ parts.append("")
87
+
88
+ content = "\n".join(parts)
89
+ console.print(
90
+ Panel(
91
+ content,
92
+ title="[bold green]HTTP Request[/bold green]",
93
+ border_style="green",
94
+ )
95
+ )
96
+
97
+ if "json" in kwargs and kwargs["json"]:
98
+ try:
99
+ json_str = json.dumps(kwargs["json"], ensure_ascii=False, indent=2)
100
+ console.print(
101
+ Syntax(json_str, "json", theme="monokai", line_numbers=False)
102
+ )
103
+ console.print()
104
+ except Exception:
105
+ pass
106
+
107
+ def _log_response(self, response: requests.Response):
108
+ if not self.verbose:
109
+ return
110
+
111
+ parts = []
112
+ parts.append(f"[bold]Status:[/bold] {response.status_code} {response.reason}\n")
113
+ parts.append("[bold]Body:[/bold]")
114
+
115
+ content = "\n".join(parts)
116
+ console.print(
117
+ Panel(
118
+ content,
119
+ title="[bold blue]HTTP Response[/bold blue]",
120
+ border_style="blue",
121
+ )
122
+ )
123
+
124
+ try:
125
+ response_data = response.json()
126
+ json_str = json.dumps(response_data, ensure_ascii=False, indent=2)
127
+ if len(json_str) > 2000:
128
+ json_str = json_str[:2000] + "\n... (truncated)"
129
+ console.print(Syntax(json_str, "json", theme="monokai", line_numbers=False))
130
+ except Exception:
131
+ body_text = response.text[:500]
132
+ if len(response.text) > 500:
133
+ body_text += "... (truncated)"
134
+ console.print(f" {body_text}")
135
+
136
+ console.print()
137
+
138
+ def _make_request(self, method: str, url: str, **kwargs) -> requests.Response:
139
+ last_error = None
140
+
141
+ for attempt in range(self.config.retry_times):
142
+ try:
143
+ if attempt == 0:
144
+ self._log_request(method, url, **kwargs)
145
+
146
+ response = requests.request(
147
+ method=method, url=url, timeout=self.config.timeout, **kwargs
148
+ )
149
+
150
+ if attempt == 0:
151
+ self._log_response(response)
152
+
153
+ return response
154
+
155
+ except requests.exceptions.RequestException as e:
156
+ last_error = NetworkError(str(e), e)
157
+ if attempt < self.config.retry_times - 1:
158
+ time.sleep(self.config.retry_delay * (attempt + 1))
159
+ continue
160
+
161
+ raise last_error
162
+
163
+ def _handle_response(self, response: requests.Response) -> dict:
164
+ try:
165
+ data = response.json()
166
+ except Exception as e:
167
+ raise APIError(
168
+ f"响应解析失败: {str(e)}, 响应内容: {response.text[:200]}",
169
+ status_code=response.status_code,
170
+ )
171
+
172
+ try:
173
+ response.raise_for_status()
174
+ except requests.exceptions.HTTPError as e:
175
+ error_msg = f"HTTP 错误: {str(e)}"
176
+ if data:
177
+ error_msg += f", 响应数据: {data}"
178
+ raise APIError(
179
+ error_msg, status_code=response.status_code, response_data=data
180
+ )
181
+
182
+ return data
183
+
184
+ def __enter__(self):
185
+ return self
186
+
187
+ def __exit__(self, exc_type, exc_val, exc_tb):
188
+ pass
@@ -42,9 +42,15 @@ class Config:
42
42
  raise ConfigurationError("Base URL 未配置")
43
43
 
44
44
  def get_headers(self, ctx_headers: Optional[dict] = None) -> dict:
45
+ try:
46
+ from .. import __version__
47
+ except ImportError:
48
+ __version__ = "0.0.0"
49
+
45
50
  headers = {}
46
51
  if ctx_headers:
47
52
  headers.update(ctx_headers)
48
53
  headers.setdefault("Content-Type", "application/json")
49
54
  headers.setdefault("Authorization", f"Bearer {self.api_key}")
55
+ headers.setdefault("X-Client-Sdk", f"coze-coding-dev-sdk-python/{__version__}")
50
56
  return headers
@@ -16,12 +16,13 @@ class ImageGenerationClient(BaseClient):
16
16
  config: Optional[Config] = None,
17
17
  ctx: Optional[Context] = None,
18
18
  custom_headers: Optional[Dict[str, str]] = None,
19
+ verbose: bool = False,
19
20
  ):
20
- super().__init__(config, ctx, custom_headers)
21
+ super().__init__(config, ctx, custom_headers, verbose)
21
22
  self.base_url = self.config.base_url
22
23
  self.model = ImageConfig.DEFAULT_MODEL
23
24
 
24
- def _extract_urls(self, response: ImageGenerationResponse) -> List[str]:
25
+ def extract_urls(self, response: ImageGenerationResponse) -> List[str]:
25
26
  urls = []
26
27
  for item in response.data:
27
28
  if item.error:
@@ -107,15 +108,3 @@ class ImageGenerationClient(BaseClient):
107
108
  sequential_image_generation,
108
109
  sequential_image_generation_max_images,
109
110
  )
110
-
111
- async def batch_generate(
112
- self, requests: List[Dict[str, Any]], max_concurrent: int = 5
113
- ) -> List[ImageGenerationResponse]:
114
- semaphore = asyncio.Semaphore(max_concurrent)
115
-
116
- async def _generate_with_semaphore(request_params: Dict[str, Any]):
117
- async with semaphore:
118
- return await self.generate_async(**request_params)
119
-
120
- tasks = [_generate_with_semaphore(req) for req in requests]
121
- return await asyncio.gather(*tasks, return_exceptions=False)
@@ -16,11 +16,19 @@ from .models import LLMConfig
16
16
 
17
17
 
18
18
  class LLMClient:
19
- def __init__(self, config: Optional[Config] = None, ctx: Optional[Context] = None):
19
+ def __init__(
20
+ self,
21
+ config: Optional[Config] = None,
22
+ ctx: Optional[Context] = None,
23
+ custom_headers: Optional[Dict[str, str]] = None,
24
+ verbose: bool = False,
25
+ ):
20
26
  if config is None:
21
27
  config = Config()
22
28
  self.config = config
23
29
  self.ctx = ctx
30
+ self.custom_headers = custom_headers or {}
31
+ self.verbose = verbose
24
32
  self.base_url = self.config.base_model_url
25
33
  self.api_key = self.config.api_key
26
34
 
@@ -39,12 +47,18 @@ class LLMClient:
39
47
  if llm_config.caching:
40
48
  extra_body["caching"] = {"type": llm_config.caching}
41
49
 
42
- headers = self.config.get_headers(extra_headers)
50
+ headers = {}
43
51
 
44
52
  if self.ctx is not None:
45
53
  ctx_headers = default_headers(self.ctx)
46
54
  headers.update(ctx_headers)
47
55
 
56
+ if self.custom_headers:
57
+ headers.update(self.custom_headers)
58
+
59
+ config_headers = self.config.get_headers(extra_headers)
60
+ headers.update(config_headers)
61
+
48
62
  llm = ChatOpenAI(
49
63
  model=llm_config.model,
50
64
  api_key=self.api_key,
@@ -79,8 +79,9 @@ class SearchClient(BaseClient):
79
79
  config: Optional[Config] = None,
80
80
  ctx: Optional[Context] = None,
81
81
  custom_headers: Optional[Dict[str, str]] = None,
82
+ verbose: bool = False,
82
83
  ):
83
- super().__init__(config, ctx, custom_headers)
84
+ super().__init__(config, ctx, custom_headers, verbose)
84
85
  self.base_url = self.config.base_url
85
86
 
86
87
  @observe(name="web_search")
@@ -20,9 +20,10 @@ class VideoGenerationClient(BaseClient):
20
20
  self,
21
21
  config: Optional[Config] = None,
22
22
  ctx: Optional[Context] = None,
23
- custom_headers: Optional[Dict[str, str]] = None
23
+ custom_headers: Optional[Dict[str, str]] = None,
24
+ verbose: bool = False
24
25
  ):
25
- super().__init__(config, ctx, custom_headers)
26
+ super().__init__(config, ctx, custom_headers, verbose)
26
27
  self.base_url = self.config.base_url
27
28
 
28
29
  @observe(name="video_generation_create_task")
@@ -1,6 +1,7 @@
1
- from typing import Optional, Tuple, Dict
2
- from cozeloop.decorator import observe
1
+ from typing import Dict, Optional, Tuple
2
+
3
3
  from coze_coding_utils.runtime_ctx.context import Context
4
+ from cozeloop.decorator import observe
4
5
 
5
6
  from ..core.client import BaseClient
6
7
  from ..core.config import Config
@@ -10,50 +11,50 @@ from .models import ASRRequest, ASRResponse
10
11
 
11
12
  class ASRClient(BaseClient):
12
13
  def __init__(
13
- self,
14
- config: Optional[Config] = None,
14
+ self,
15
+ config: Optional[Config] = None,
15
16
  ctx: Optional[Context] = None,
16
- custom_headers: Optional[Dict[str, str]] = None
17
+ custom_headers: Optional[Dict[str, str]] = None,
18
+ verbose: bool = False,
17
19
  ):
18
- super().__init__(config, ctx, custom_headers)
20
+ super().__init__(config, ctx, custom_headers, verbose)
19
21
  self.base_url = self.config.base_url
22
+
20
23
  @observe
21
24
  def recognize(
22
25
  self,
23
26
  uid: Optional[str] = None,
24
27
  url: Optional[str] = None,
25
- base64_data: Optional[str] = None
28
+ base64_data: Optional[str] = None,
26
29
  ) -> Tuple[str, dict]:
27
30
  if not (url or base64_data):
28
- raise ValidationError("必须提供 url 或 base64_data 其中之一", field="url/base64_data")
29
-
30
- request = ASRRequest(
31
- uid=uid,
32
- url=url,
33
- base64_data=base64_data
34
- )
35
-
31
+ raise ValidationError(
32
+ "必须提供 url 或 base64_data 其中之一", field="url/base64_data"
33
+ )
34
+
35
+ request = ASRRequest(uid=uid, url=url, base64_data=base64_data)
36
+
36
37
  headers = self.config.get_headers()
37
38
  response = self._make_request(
38
39
  method="POST",
39
40
  url=f"{self.base_url}/api/v3/auc/bigmodel/recognize/flash",
40
41
  json=request.to_api_request(),
41
- headers=headers
42
+ headers=headers,
42
43
  )
43
-
44
- status_code = response.headers.get('X-Api-Status-Code', '0')
45
- message = response.headers.get('X-Api-Message', '')
46
-
47
- if status_code != '20000000':
44
+
45
+ status_code = response.headers.get("X-Api-Status-Code", "0")
46
+ message = response.headers.get("X-Api-Message", "")
47
+
48
+ if status_code != "20000000":
48
49
  raise APIError(
49
50
  f"识别失败: {message}",
50
51
  code=status_code,
51
- status_code=response.status_code
52
+ status_code=response.status_code,
52
53
  )
53
-
54
+
54
55
  data = self._handle_response(response)
55
-
56
+
56
57
  result = data.get("result", {})
57
58
  text = result.get("text", "")
58
-
59
+
59
60
  return text, data
@@ -1,8 +1,9 @@
1
- import json
2
1
  import base64
3
- from typing import Optional, Tuple, Dict
4
- from cozeloop.decorator import observe
2
+ import json
3
+ from typing import Dict, Optional, Tuple
4
+
5
5
  from coze_coding_utils.runtime_ctx.context import Context
6
+ from cozeloop.decorator import observe
6
7
 
7
8
  from ..core.client import BaseClient
8
9
  from ..core.config import Config
@@ -12,13 +13,15 @@ from .models import TTSConfig, TTSRequest
12
13
 
13
14
  class TTSClient(BaseClient):
14
15
  def __init__(
15
- self,
16
- config: Optional[Config] = None,
16
+ self,
17
+ config: Optional[Config] = None,
17
18
  ctx: Optional[Context] = None,
18
- custom_headers: Optional[Dict[str, str]] = None
19
+ custom_headers: Optional[Dict[str, str]] = None,
20
+ verbose: bool = False,
19
21
  ):
20
- super().__init__(config, ctx, custom_headers)
22
+ super().__init__(config, ctx, custom_headers, verbose)
21
23
  self.base_url = self.config.base_url
24
+
22
25
  @observe
23
26
  def synthesize(
24
27
  self,
@@ -33,7 +36,7 @@ class TTSClient(BaseClient):
33
36
  ) -> Tuple[str, int]:
34
37
  if not (text or ssml):
35
38
  raise ValidationError("必须提供 text 或 ssml 其中之一", field="text/ssml")
36
-
39
+
37
40
  request = TTSRequest(
38
41
  uid=uid,
39
42
  text=text,
@@ -42,49 +45,49 @@ class TTSClient(BaseClient):
42
45
  audio_format=audio_format,
43
46
  sample_rate=sample_rate,
44
47
  speech_rate=speech_rate,
45
- loudness_rate=loudness_rate
48
+ loudness_rate=loudness_rate,
46
49
  )
47
-
50
+
48
51
  headers = self.config.get_headers({"Connection": "keep-alive"})
49
52
  response = self._make_request(
50
53
  method="POST",
51
54
  url=f"{self.base_url}/api/v3/tts/unidirectional",
52
55
  json=request.to_api_request(),
53
56
  headers=headers,
54
- stream=True
57
+ stream=True,
55
58
  )
56
-
59
+
57
60
  try:
58
61
  audio_uri = None
59
62
  audio_data = bytearray()
60
63
  total_audio_size = 0
61
-
64
+
62
65
  for chunk in response.iter_lines(decode_unicode=False):
63
66
  if not chunk:
64
67
  continue
65
-
68
+
66
69
  chunk_str = chunk.decode("utf-8").replace("data:", "")
67
70
  data = json.loads(chunk_str)
68
-
71
+
69
72
  if data.get("code", 0) == 0 and "data" in data and data["data"]:
70
73
  chunk_audio = base64.b64decode(data["data"])
71
74
  audio_size = len(chunk_audio)
72
75
  total_audio_size += audio_size
73
76
  audio_data.extend(chunk_audio)
74
-
77
+
75
78
  elif data.get("code", 0) == 20000000:
76
- if 'url' in data and data['url']:
77
- audio_uri = data['url']
79
+ if "url" in data and data["url"]:
80
+ audio_uri = data["url"]
78
81
  break
79
-
82
+
80
83
  elif data.get("code", 0) > 0:
81
84
  raise APIError(
82
85
  f"合成音频失败: {data.get('message', '')}",
83
- code=str(data.get('code', 0))
86
+ code=str(data.get("code", 0)),
84
87
  )
85
-
88
+
86
89
  return audio_uri or "", total_audio_size
87
-
90
+
88
91
  except json.JSONDecodeError as e:
89
92
  raise APIError(f"响应解析失败: {str(e)}")
90
93
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coze-coding-dev-sdk
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Coze Coding Dev SDK - 优雅的多功能 AI SDK,支持图片生成、视频生成、语音合成、语音识别、大语言模型和联网搜索。包含命令行工具 coze-coding-ai,支持 Context 上下文追踪
5
5
  Author-email: Coze Coding Integration Team <support@coze.com>
6
6
  Maintainer-email: Coze Coding Integration Team <support@coze.com>
@@ -16,6 +16,7 @@ coze_coding_dev_sdk/cli/cli.py
16
16
  coze_coding_dev_sdk/cli/constants.py
17
17
  coze_coding_dev_sdk/cli/image.py
18
18
  coze_coding_dev_sdk/cli/search.py
19
+ coze_coding_dev_sdk/cli/utils.py
19
20
  coze_coding_dev_sdk/cli/video.py
20
21
  coze_coding_dev_sdk/cli/voice.py
21
22
  coze_coding_dev_sdk/core/__init__.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "coze-coding-dev-sdk"
7
- version = "0.4.2"
7
+ version = "0.4.4"
8
8
  description = "Coze Coding Dev SDK - 优雅的多功能 AI SDK,支持图片生成、视频生成、语音合成、语音识别、大语言模型和联网搜索。包含命令行工具 coze-coding-ai,支持 Context 上下文追踪"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,97 +0,0 @@
1
- import time
2
- from typing import Optional, Dict
3
- import requests
4
- from coze_coding_utils.runtime_ctx.context import Context, default_headers
5
-
6
- from .config import Config
7
- from .exceptions import APIError, NetworkError
8
-
9
-
10
- class BaseClient:
11
- def __init__(
12
- self,
13
- config: Optional[Config] = None,
14
- ctx: Optional[Context] = None,
15
- custom_headers: Optional[Dict[str, str]] = None
16
- ):
17
- if config is None:
18
- config = Config()
19
- self.config = config
20
- self.ctx = ctx
21
- self.custom_headers = custom_headers or {}
22
-
23
- def _request(
24
- self,
25
- method: str,
26
- url: str,
27
- headers: Optional[Dict[str, str]] = None,
28
- **kwargs
29
- ) -> dict:
30
- request_headers = self.config.get_headers(headers)
31
-
32
- if self.ctx is not None:
33
- ctx_headers = default_headers(self.ctx)
34
- request_headers.update(ctx_headers)
35
-
36
- response = self._make_request(
37
- method=method,
38
- url=url,
39
- headers=request_headers,
40
- **kwargs
41
- )
42
- return self._handle_response(response)
43
-
44
- def _make_request(
45
- self,
46
- method: str,
47
- url: str,
48
- **kwargs
49
- ) -> requests.Response:
50
- last_error = None
51
-
52
- for attempt in range(self.config.retry_times):
53
- try:
54
- response = requests.request(
55
- method=method,
56
- url=url,
57
- timeout=self.config.timeout,
58
- **kwargs
59
- )
60
- return response
61
-
62
- except requests.exceptions.RequestException as e:
63
- last_error = NetworkError(str(e), e)
64
- if attempt < self.config.retry_times - 1:
65
- time.sleep(self.config.retry_delay * (attempt + 1))
66
- continue
67
-
68
- raise last_error
69
-
70
- def _handle_response(self, response: requests.Response) -> dict:
71
- try:
72
- data = response.json()
73
- except Exception as e:
74
- raise APIError(
75
- f"响应解析失败: {str(e)}, 响应内容: {response.text[:200]}",
76
- status_code=response.status_code
77
- )
78
-
79
- try:
80
- response.raise_for_status()
81
- except requests.exceptions.HTTPError as e:
82
- error_msg = f"HTTP 错误: {str(e)}"
83
- if data:
84
- error_msg += f", 响应数据: {data}"
85
- raise APIError(
86
- error_msg,
87
- status_code=response.status_code,
88
- response_data=data
89
- )
90
-
91
- return data
92
-
93
- def __enter__(self):
94
- return self
95
-
96
- def __exit__(self, exc_type, exc_val, exc_tb):
97
- pass