nous-genai 0.1.0__tar.gz → 0.1.2__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.
- {nous_genai-0.1.0/nous_genai.egg-info → nous_genai-0.1.2}/PKG-INFO +6 -1
- {nous_genai-0.1.0 → nous_genai-0.1.2}/README.md +5 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/_internal/capability_rules.py +0 -2
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/cli.py +174 -20
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/providers/tuzi.py +234 -299
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/tuzi_web.py +2 -2
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/types.py +2 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2/nous_genai.egg-info}/PKG-INFO +6 -1
- {nous_genai-0.1.0 → nous_genai-0.1.2}/pyproject.toml +1 -1
- nous_genai-0.1.2/tests/test_tuzi_models.py +723 -0
- nous_genai-0.1.0/tests/test_tuzi_models.py +0 -332
- {nous_genai-0.1.0 → nous_genai-0.1.2}/LICENSE +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/__init__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/__init__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/__main__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/_internal/__init__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/_internal/config.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/_internal/errors.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/_internal/http.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/_internal/json_schema.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/client.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/mcp_cli.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/mcp_server.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/providers/__init__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/providers/aliyun.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/providers/anthropic.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/providers/gemini.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/providers/openai.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/providers/volcengine.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/__init__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/catalog.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/mappings.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/mode_overrides.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/__init__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/aliyun.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/anthropic.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/google.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/openai.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/tuzi_anthropic.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/tuzi_google.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/tuzi_openai.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/reference/model_catalog_data/volcengine.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/tools/__init__.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/genai/tools/output_parser.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous/py.typed +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous_genai.egg-info/SOURCES.txt +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous_genai.egg-info/dependency_links.txt +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous_genai.egg-info/entry_points.txt +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous_genai.egg-info/requires.txt +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/nous_genai.egg-info/top_level.txt +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/setup.cfg +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_capability_flags.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_capability_rules.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_cli_google_download_auth.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_cli_probe_parsing.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_cli_prompt_path.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_cli_subcommands.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_cli_timeout_hint.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_cli_video_wait_default.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_client_protected_url_artifacts.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_client_timeout.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_http_error_mapping.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_http_security.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_mcp_artifact_limits.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_mcp_cli_url_resolution.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_mcp_input_policy.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_mcp_model_keyword_filter.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_mcp_server_transports.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_mcp_token_rules.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_model_catalog_validation.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_model_discovery.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_modes_inference.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_openai_responses_streaming.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_output_json_schema.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_output_parser_tool.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_reasoning_mapping.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_review_report_fixes.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_sse_parser.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_tool_calling.py +0 -0
- {nous_genai-0.1.0 → nous_genai-0.1.2}/tests/test_tuzi_gemini_markdown_image.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nous-genai
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Single-endpoint GenAI SDK (multi-provider, multimodal)
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/gravtice/nous-genai
|
|
@@ -168,6 +168,11 @@ If you need to write to file, see `examples/demo.py` (`_write_binary()`), or reu
|
|
|
168
168
|
uv run genai --model openai:gpt-4o-mini --prompt "Hello"
|
|
169
169
|
uv run genai model available --all
|
|
170
170
|
|
|
171
|
+
# Tuzi Chirp music
|
|
172
|
+
uv run genai --model tuzi-web:chirp-v3-5 --prompt "Lo-fi hiphop beat, 30s" --no-wait
|
|
173
|
+
# ...later
|
|
174
|
+
uv run genai --model tuzi-web:chirp-v3-5 --job-id "<job_id>" --output-path demo_suno.mp3 --timeout-ms 600000
|
|
175
|
+
|
|
171
176
|
# MCP Server
|
|
172
177
|
uv run genai-mcp-server # Streamable HTTP: /mcp, SSE: /sse
|
|
173
178
|
uv run genai-mcp-cli tools # Debug CLI
|
|
@@ -151,6 +151,11 @@ If you need to write to file, see `examples/demo.py` (`_write_binary()`), or reu
|
|
|
151
151
|
uv run genai --model openai:gpt-4o-mini --prompt "Hello"
|
|
152
152
|
uv run genai model available --all
|
|
153
153
|
|
|
154
|
+
# Tuzi Chirp music
|
|
155
|
+
uv run genai --model tuzi-web:chirp-v3-5 --prompt "Lo-fi hiphop beat, 30s" --no-wait
|
|
156
|
+
# ...later
|
|
157
|
+
uv run genai --model tuzi-web:chirp-v3-5 --job-id "<job_id>" --output-path demo_suno.mp3 --timeout-ms 600000
|
|
158
|
+
|
|
154
159
|
# MCP Server
|
|
155
160
|
uv run genai-mcp-server # Streamable HTTP: /mcp, SSE: /sse
|
|
156
161
|
uv run genai-mcp-cli tools # Debug CLI
|
|
@@ -63,7 +63,6 @@ _TTS_PREFIX: Final[str] = "tts-"
|
|
|
63
63
|
_TTS_SUFFIX: Final[str] = "-tts"
|
|
64
64
|
_VOICE_SUFFIXES: Final[tuple[str, ...]] = ("-voice", "_voice")
|
|
65
65
|
_ADVANCED_VOICE_MODEL: Final[str] = "advanced-voice"
|
|
66
|
-
_SUNO_PREFIX: Final[str] = "suno-"
|
|
67
66
|
_CHIRP_PREFIX: Final[str] = "chirp-"
|
|
68
67
|
|
|
69
68
|
_WHISPER_PREFIX: Final[str] = "whisper-"
|
|
@@ -223,7 +222,6 @@ def is_tts_model(model_id: str) -> bool:
|
|
|
223
222
|
mid_l = _norm(model_id)
|
|
224
223
|
return (
|
|
225
224
|
mid_l.startswith(_TTS_PREFIX)
|
|
226
|
-
or mid_l.startswith(_SUNO_PREFIX)
|
|
227
225
|
or mid_l.startswith(_CHIRP_PREFIX)
|
|
228
226
|
or mid_l.endswith(_TTS_SUFFIX)
|
|
229
227
|
or mid_l.endswith(_VOICE_SUFFIXES)
|
|
@@ -109,6 +109,15 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
109
109
|
parser.add_argument("--video-path", help="Input video file path")
|
|
110
110
|
parser.add_argument("--output-path", help="Write output to file (text/json/binary)")
|
|
111
111
|
parser.add_argument("--ouput-path", dest="output_path", help=argparse.SUPPRESS)
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
"--job-id",
|
|
114
|
+
help="Resume/poll a provider job id (tuzi-web only for now); ignores --prompt/--*-path",
|
|
115
|
+
)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"--no-wait",
|
|
118
|
+
action="store_true",
|
|
119
|
+
help="Do not wait for job completion (returns job_id if supported)",
|
|
120
|
+
)
|
|
112
121
|
parser.add_argument(
|
|
113
122
|
"--timeout-ms",
|
|
114
123
|
type=int,
|
|
@@ -184,7 +193,25 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
184
193
|
except BrokenPipeError:
|
|
185
194
|
return
|
|
186
195
|
|
|
196
|
+
client = Client()
|
|
187
197
|
provider, model_id = _split_model(args.model)
|
|
198
|
+
_apply_protocol_override(client, provider=provider, protocol=args.protocol)
|
|
199
|
+
|
|
200
|
+
cap = client.capabilities(args.model)
|
|
201
|
+
output = _infer_output_spec(provider=provider, model_id=model_id, cap=cap)
|
|
202
|
+
|
|
203
|
+
if args.job_id:
|
|
204
|
+
_run_job(
|
|
205
|
+
client,
|
|
206
|
+
provider=provider,
|
|
207
|
+
model_id=model_id,
|
|
208
|
+
job_id=str(args.job_id),
|
|
209
|
+
output=output,
|
|
210
|
+
output_path=args.output_path,
|
|
211
|
+
timeout_ms=timeout_ms,
|
|
212
|
+
)
|
|
213
|
+
return
|
|
214
|
+
|
|
188
215
|
prompt = args.prompt
|
|
189
216
|
if prompt is None and args.prompt_path:
|
|
190
217
|
try:
|
|
@@ -192,11 +219,6 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
192
219
|
prompt = f.read()
|
|
193
220
|
except OSError as e:
|
|
194
221
|
raise SystemExit(f"cannot read --prompt-path: {e}") from None
|
|
195
|
-
client = Client()
|
|
196
|
-
_apply_protocol_override(client, provider=provider, protocol=args.protocol)
|
|
197
|
-
|
|
198
|
-
cap = client.capabilities(args.model)
|
|
199
|
-
output = _infer_output_spec(provider=provider, model_id=model_id, cap=cap)
|
|
200
222
|
|
|
201
223
|
parts = _build_input_parts(
|
|
202
224
|
prompt=prompt,
|
|
@@ -212,7 +234,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
212
234
|
model=args.model,
|
|
213
235
|
input=[Message(role="user", content=parts)],
|
|
214
236
|
output=output,
|
|
215
|
-
wait=
|
|
237
|
+
wait=not bool(getattr(args, "no_wait", False)),
|
|
216
238
|
)
|
|
217
239
|
if timeout_ms is not None:
|
|
218
240
|
req = replace(req, params=replace(req.params, timeout_ms=timeout_ms))
|
|
@@ -229,21 +251,37 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
229
251
|
if resp.job and resp.job.job_id:
|
|
230
252
|
print(resp.job.job_id)
|
|
231
253
|
if resp.status == "running":
|
|
232
|
-
|
|
233
|
-
if
|
|
234
|
-
|
|
235
|
-
|
|
254
|
+
status_note = ""
|
|
255
|
+
if resp.job.last_status:
|
|
256
|
+
status_note += f" upstream_status={resp.job.last_status}"
|
|
257
|
+
if resp.job.last_detail:
|
|
258
|
+
d = resp.job.last_detail
|
|
259
|
+
if len(d) > 200:
|
|
260
|
+
d = d[:200] + "..."
|
|
261
|
+
status_note += f" fail_reason={d}"
|
|
262
|
+
if not req.wait:
|
|
263
|
+
print(
|
|
264
|
+
"[INFO] 已提交任务(未等待完成);已返回 job_id。"
|
|
265
|
+
f"可用 --job-id {resp.job.job_id} 继续轮询/下载。",
|
|
266
|
+
file=sys.stderr,
|
|
267
|
+
)
|
|
268
|
+
else:
|
|
269
|
+
effective_timeout_ms = timeout_ms
|
|
270
|
+
if effective_timeout_ms is None:
|
|
271
|
+
effective_timeout_ms = getattr(
|
|
272
|
+
client, "_default_timeout_ms", None
|
|
273
|
+
)
|
|
274
|
+
timeout_note = (
|
|
275
|
+
f"{effective_timeout_ms}ms"
|
|
276
|
+
if isinstance(effective_timeout_ms, int)
|
|
277
|
+
else "timeout"
|
|
278
|
+
)
|
|
279
|
+
print(
|
|
280
|
+
f"[INFO] 任务仍在运行(等待 {elapsed_s:.1f}s,可能已超时 {timeout_note});已返回 job_id。"
|
|
281
|
+
f"可用 --job-id {resp.job.job_id} 继续轮询/下载,或增大 --timeout-ms 重试。"
|
|
282
|
+
f"{status_note}",
|
|
283
|
+
file=sys.stderr,
|
|
236
284
|
)
|
|
237
|
-
timeout_note = (
|
|
238
|
-
f"{effective_timeout_ms}ms"
|
|
239
|
-
if isinstance(effective_timeout_ms, int)
|
|
240
|
-
else "timeout"
|
|
241
|
-
)
|
|
242
|
-
print(
|
|
243
|
-
f"[INFO] 任务仍在运行(等待 {elapsed_s:.1f}s,可能已超时 {timeout_note});已返回 job_id。"
|
|
244
|
-
"可增大 --timeout-ms 或设置 NOUS_GENAI_TIMEOUT_MS 后重试。",
|
|
245
|
-
file=sys.stderr,
|
|
246
|
-
)
|
|
247
285
|
if args.output_path:
|
|
248
286
|
print(
|
|
249
287
|
f"[INFO] 未写入输出文件:{args.output_path}",
|
|
@@ -275,6 +313,122 @@ _DEFAULT_VIDEO_URL = (
|
|
|
275
313
|
)
|
|
276
314
|
|
|
277
315
|
|
|
316
|
+
def _run_job(
|
|
317
|
+
client: Client,
|
|
318
|
+
*,
|
|
319
|
+
provider: str,
|
|
320
|
+
model_id: str,
|
|
321
|
+
job_id: str,
|
|
322
|
+
output: OutputSpec,
|
|
323
|
+
output_path: str | None,
|
|
324
|
+
timeout_ms: int | None,
|
|
325
|
+
) -> None:
|
|
326
|
+
provider = provider.strip().lower()
|
|
327
|
+
if provider != "tuzi-web":
|
|
328
|
+
raise SystemExit("--job-id only supported for provider=tuzi-web for now")
|
|
329
|
+
|
|
330
|
+
job_id = job_id.strip()
|
|
331
|
+
if not job_id:
|
|
332
|
+
raise SystemExit("--job-id must be non-empty")
|
|
333
|
+
|
|
334
|
+
adapter = client._adapter(provider)
|
|
335
|
+
from .providers import TuziAdapter
|
|
336
|
+
|
|
337
|
+
if not isinstance(adapter, TuziAdapter):
|
|
338
|
+
raise SystemExit("tuzi-web adapter not configured")
|
|
339
|
+
|
|
340
|
+
effective_timeout_ms = timeout_ms
|
|
341
|
+
if effective_timeout_ms is None:
|
|
342
|
+
effective_timeout_ms = getattr(client, "_default_timeout_ms", None)
|
|
343
|
+
if effective_timeout_ms is None:
|
|
344
|
+
effective_timeout_ms = 120_000
|
|
345
|
+
|
|
346
|
+
modalities = set(output.modalities)
|
|
347
|
+
mid_l = model_id.lower().strip()
|
|
348
|
+
is_chirp_music = (
|
|
349
|
+
modalities == {"audio"} and mid_l.startswith("chirp-") and mid_l != "chirp-v3"
|
|
350
|
+
)
|
|
351
|
+
if not is_chirp_music:
|
|
352
|
+
raise SystemExit("--job-id only supports tuzi-web chirp-* audio tasks for now")
|
|
353
|
+
|
|
354
|
+
def fn():
|
|
355
|
+
try:
|
|
356
|
+
host = adapter._base_host()
|
|
357
|
+
probe = adapter._suno_feed(
|
|
358
|
+
host=host,
|
|
359
|
+
ids=job_id,
|
|
360
|
+
timeout_ms=min(10_000, int(effective_timeout_ms)),
|
|
361
|
+
)
|
|
362
|
+
clips = probe.get("clips")
|
|
363
|
+
clip_found = bool(
|
|
364
|
+
isinstance(clips, list)
|
|
365
|
+
and any(
|
|
366
|
+
isinstance(c, dict)
|
|
367
|
+
and isinstance(c.get("id"), str)
|
|
368
|
+
and c.get("id") == job_id
|
|
369
|
+
for c in clips
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
if clip_found:
|
|
373
|
+
return adapter._suno_wait_feed_audio(
|
|
374
|
+
clip_id=job_id,
|
|
375
|
+
model_id=model_id,
|
|
376
|
+
timeout_ms=effective_timeout_ms,
|
|
377
|
+
wait=True,
|
|
378
|
+
)
|
|
379
|
+
except Exception:
|
|
380
|
+
pass
|
|
381
|
+
return adapter._suno_wait_fetch_audio(
|
|
382
|
+
task_id=job_id,
|
|
383
|
+
model_id=model_id,
|
|
384
|
+
timeout_ms=effective_timeout_ms,
|
|
385
|
+
wait=True,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
show_progress = sys.stderr.isatty()
|
|
389
|
+
resp, elapsed_s = _run_with_spinner(fn, enabled=show_progress, label="等待任务完成")
|
|
390
|
+
|
|
391
|
+
if resp.status != "completed":
|
|
392
|
+
if resp.job and resp.job.job_id:
|
|
393
|
+
print(resp.job.job_id)
|
|
394
|
+
if resp.status == "running":
|
|
395
|
+
status_note = ""
|
|
396
|
+
if resp.job.last_status:
|
|
397
|
+
status_note += f" upstream_status={resp.job.last_status}"
|
|
398
|
+
if resp.job.last_detail:
|
|
399
|
+
d = resp.job.last_detail
|
|
400
|
+
if len(d) > 200:
|
|
401
|
+
d = d[:200] + "..."
|
|
402
|
+
status_note += f" fail_reason={d}"
|
|
403
|
+
timeout_note = (
|
|
404
|
+
f"{effective_timeout_ms}ms"
|
|
405
|
+
if isinstance(effective_timeout_ms, int)
|
|
406
|
+
else "timeout"
|
|
407
|
+
)
|
|
408
|
+
print(
|
|
409
|
+
f"[INFO] 任务仍在运行(等待 {elapsed_s:.1f}s,可能已超时 {timeout_note});已返回 job_id。"
|
|
410
|
+
f"可稍后重试 --job-id。{status_note}",
|
|
411
|
+
file=sys.stderr,
|
|
412
|
+
)
|
|
413
|
+
if output_path:
|
|
414
|
+
print(f"[INFO] 未写入输出文件:{output_path}", file=sys.stderr)
|
|
415
|
+
else:
|
|
416
|
+
raise SystemExit(f"[FAIL]: request status={resp.status}")
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
if not resp.output:
|
|
420
|
+
raise SystemExit("[FAIL]: missing output")
|
|
421
|
+
_write_response(
|
|
422
|
+
resp.output[0].content,
|
|
423
|
+
output=output,
|
|
424
|
+
output_path=output_path,
|
|
425
|
+
timeout_ms=timeout_ms,
|
|
426
|
+
download_auth=_download_auth(client, provider=provider),
|
|
427
|
+
)
|
|
428
|
+
if show_progress:
|
|
429
|
+
print(f"[INFO] 完成,用时 {elapsed_s:.1f}s", file=sys.stderr)
|
|
430
|
+
|
|
431
|
+
|
|
278
432
|
def _run_probe(args: argparse.Namespace, *, timeout_ms: int | None) -> int:
|
|
279
433
|
from .client import _normalize_provider
|
|
280
434
|
from .reference import get_sdk_supported_models_for_provider
|