装闭 RenoPit 源码解析(08):多模态AI调用、重试与文本降级

1785882663954

Prompt 构建完成后,RenoPit 还要面对不同模型、不同兼容接口以及图片能力差异。开源仓库 fthux/RenoPit 将这些差异集中在 llm_service.py,业务层只调用 analyze_design() 等统一函数。

一、统一创建 OpenAI 兼容客户端

_get_openai_client() 使用三个环境配置创建 AsyncOpenAI:API Key、Base URL 和 180 秒请求超时。只要服务实现 OpenAI Chat Completions 兼容协议,RenoPit 就可以通过同一客户端调用。

设计分析、合同分析、增项预测和交叉核查最终都进入两个底层函数:

  • _call_llm():发送图片和文本;
  • _call_llm_text():只发送系统 Prompt 与用户文本。

这种划分让多模态能力判断集中在设计分析路径,文档类任务始终使用纯文本调用。

二、图片如何进入多模态消息

_call_llm() 遍历 Base64 图片,识别可能存在的 Data URL 前缀,默认 MIME 类型为 image/jpeg,然后构造 OpenAI 的 image_url 内容:

content_parts.append({
    "type": "image_url",
    "image_url": {
        "url": f"data:{mime_type};base64,{img_data}",
        "detail": "high",
    },
})

全部图片之后再追加一个 text 内容块。最终请求包含一条 system 消息和一条 user 消息,其中 user 的 content 是图片块与文本块组成的数组。

模型名称完全来自 LLM_MODEL_NAME,最大输出设置为 65536 Token,温度为 0.3,让结构化结果保持相对稳定。

三、联网工具只对特定端点启用

ENABLE_WEB_SEARCH 为真时,代码还会检查 Base URL 是否属于 OpenAI API。只有满足条件才附加 web_search_preview 工具。

Gemini、DeepSeek、Ollama 等兼容端点不会收到该参数,因为它们可能能处理 Chat Completions,却不支持同样的工具定义。此时联网要求仍保留在系统 Prompt 中,API 请求本身保持普通聊天格式。

四、重试封装如何工作

多模态和纯文本各有一个指数退避封装。当前常量为:

MAX_RETRIES = 2
BASE_DELAY = 2
TIMEOUT_SECONDS = 180

每次调用都由 asyncio.wait_for() 限制在 180 秒内。空字符串也会被视为失败。第一次失败后等待两秒再进行第二次尝试;两次都失败时抛出带模型名称和最后错误信息的 RuntimeError

多模态重试对 ModelNotMultimodalError 做了特殊处理:这种错误不会继续重复请求,而是立即交给上层决定是否走文本降级。

五、如何识别模型不支持图片

部分纯文本模型收到 image_url 后会返回包含 unknown variant image_url 的 400 错误。_call_llm() 捕获异常并把它转换成 ModelNotMultimodalError

analyze_design() 先判断是否有图片和文本,再决定执行路线:

flowchart TD A[analyze_design] --> B{是否有图片} B -->|是| C[多模态调用] C -->|成功| D[返回 LLM 文本] C -->|不支持图片| E{是否还有文本} C -->|其他调用失败| E B -->|否| E E -->|有| F[纯文本调用] E -->|无| G[抛出 RuntimeError] F --> D

如果用户同时上传了图片和合同,即使当前模型不支持视觉输入,系统仍能使用提取出的合同文本和用户说明完成分析。只有“存在图片、模型不支持图片、又没有任何文本”时才无法降级。

六、同步 Celery 任务如何调用异步函数

LLM 服务全部使用 async def,但 Celery 的 run_analysis_task 和分析引擎是同步函数。run_analysis_sync() 通常通过 asyncio.run() 执行 analyze_design();如果当前线程已经存在运行中的事件循环,则使用线程池创建新的异步执行环境。

文档分析、增项预测和交叉核查也采用同样的同步包装。因此异步网络客户端被限制在服务层,Celery 编排代码仍然保持同步调用形式。

七、LLM 服务调用链小结

RenoPit 的 LLM 层完成四件事:根据配置创建兼容客户端,把图片和文本组装成请求,为超时及空响应提供重试,并在模型缺少视觉能力时切换到纯文本。上层分析引擎拿到的始终是一段待解析的模型输出文本。

相关实现可以在 fthux/RenoPitllm_service.py 中逐行对照。下一篇将分析这段原始文本如何经过 AnalysisEngine、JSON 修复和数据库事务,最终成为可查询的结构化装修闭坑结果。

posted on 2026-08-14 09:52  fthux  阅读(0)  评论(0)    收藏  举报