Initial commit: V1

This commit is contained in:
theliu
2026-04-25 14:10:09 +08:00
parent 76b5751518
commit 3fe9b00de7
9 changed files with 305 additions and 279 deletions
+34 -44
View File
@@ -1,43 +1,44 @@
"""
text_ai.py - LLM 文本生成
用于场景划分等 AI 推理任务
支持多 LLM 提供商切换
text_ai.py - LLM text generation client.
Supports multiple providers defined in config.py.
"""
from openai import OpenAI
from config import LLM_PROVIDERS, DEFAULT_LLM, LLM_API_KEY, LLM_API_BASE, LLM_MODEL
from config import LLM_PROVIDERS
def text_ai(in_put: str, system_prompt: str = "You are a helpful assistant.",
provider: str = None) -> str:
"""
调用 LLM 生成文本
"""Call LLM to generate text.
Args:
in_put: 用户输入内容
system_prompt: 系统提示词
provider: LLM 提供商名称(对应 LLM_PROVIDERS 的 key),None 则用默认
in_put: user message
system_prompt: system prompt
provider: provider name (key in LLM_PROVIDERS), None = first in dict
Returns:
AI 生成的文本
generated text
"""
if provider and provider in LLM_PROVIDERS:
cfg = LLM_PROVIDERS[provider]
api_key = cfg["api_key"]
api_base = cfg["api_base"]
model = cfg["model"]
else:
api_key = LLM_API_KEY
api_base = LLM_API_BASE
model = LLM_MODEL
# Default to first provider in dict
cfg = next(iter(LLM_PROVIDERS.values()))
provider = next(iter(LLM_PROVIDERS))
client = OpenAI(
api_key=api_key,
base_url=api_base,
)
api_key = cfg["api_key"]
api_base = cfg["api_base"]
model = cfg["model"]
# ModelScope 的 Qwen3 系列和 GLM 系列默认开启 thinking,需要关掉
# 注意:MiniMax 系列不是 Qwen/GLM,不需要也不能传 enable_thinking
if not api_key:
raise ValueError(
f"API key not configured for '{provider}'. "
f"Edit config.py and fill in the api_key field."
)
client = OpenAI(api_key=api_key, base_url=api_base)
# ModelScope Qwen3/GLM default to thinking mode, disable it
extra_body = {}
is_modelscope = "modelscope" in api_base.lower()
is_qwen = "qwen" in model.lower()
@@ -49,38 +50,31 @@ def text_ai(in_put: str, system_prompt: str = "You are a helpful assistant.",
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": in_put}
{"role": "user", "content": in_put},
],
max_tokens=16384,
stream=False,
extra_body=extra_body if extra_body else None,
extra_body=extra_body or None,
)
# 防御:choices 为空或 None
if not response.choices:
# 尝试从 response 对象提取有用信息
resp_dict = response.model_dump() if hasattr(response, "model_dump") else {}
error_msg = resp_dict.get("error", {})
if isinstance(error_msg, dict):
err_text = error_msg.get("message", str(error_msg))
else:
err_text = str(resp_dict)
err_text = error_msg.get("message", str(error_msg)) if isinstance(error_msg, dict) else str(resp_dict)
raise ValueError(
f"模型 '{model}' 返回了空的 choices\n"
f"响应内容: {err_text}\n"
f"可能是模型暂时不可用或请求被拒绝。"
f"Model '{model}' returned empty choices.\n"
f"Response: {err_text}\n"
f"Model may be unavailable or request was rejected."
)
msg = response.choices[0].message
content = msg.content
# 检测输出是否被截断
finish = response.choices[0].finish_reason
if finish == "length":
print(f"[WARN] LLM output truncated (finish_reason=length), max_tokens may be too small")
if response.choices[0].finish_reason == "length":
print(f"[WARN] LLM output truncated (finish_reason=length)")
if content is None:
# fallback:尝试多种字段名(不同 API 叫法不同)
# Fallback: try alternate field names
for attr in ("thinking_content", "reasoning_content", "text", "output"):
fallback = getattr(msg, attr, None)
if fallback:
@@ -88,7 +82,6 @@ def text_ai(in_put: str, system_prompt: str = "You are a helpful assistant.",
break
if content is None:
# 最后一搏:尝试把 message 对象当 dict 看
try:
msg_dict = msg.model_dump() if hasattr(msg, "model_dump") else vars(msg)
for v in msg_dict.values():
@@ -99,11 +92,8 @@ def text_ai(in_put: str, system_prompt: str = "You are a helpful assistant.",
pass
if content is None:
finish = response.choices[0].finish_reason
raise ValueError(
f"模型 '{model}' 返回内容为空(content=None),"
f"finish_reason={finish}\n"
f"如果使用 MiniMax 系列,请改用 Qwen3.5-35B (ModelScope 免费) 或其他 Qwen 模型。"
f"Model '{model}' returned None content (finish_reason={response.choices[0].finish_reason})."
)
return content