26 Feb, 2026

1 commit

  • - **统一并增强 thinking/reasoning 处理的请求参数逻辑(`shopping_agent.py`)**
      - 新增基础工具函数用于判断不同 provider 类型:
        - `_normalize_base_url(base_url)`:标准化 `openai_api_base_url`(去空格与尾部 `/`),避免字符串比较不一致。
        - `_is_openai_official_base_url(base_url)`:通过 hostname 判断是否为官方 `api.openai.com`,用于选择 Responses API。
        - `_is_dashscope_base_url(base_url)`:通过 hostname 中是否包含 `dashscope` 来识别 DashScope 兼容模式。
      - 在 `ShoppingAgent.__init__` 中基于 `base_url` 与 `openai_use_reasoning` 做**分支处理**,确保不同 provider 下的思考模式启用方式正确且互不干扰:
        - **OpenAI 官方(含未显式配置 base_url 的默认情况)**:
          - 当 `openai_use_reasoning=True` 且 `base_url` 为空或指向 `api.openai.com` 时:
            - 启用 `llm_kwargs["use_responses_api"] = True`,切换到 OpenAI Responses API。
            - 设置 `llm_kwargs["model_kwargs"] = {"reasoning": {"effort": <配置>, "summary": "none"}}`,与官方 reasoning 参数保持一致。
        - **DashScope OpenAI 兼容接口**:
          - 当 `openai_use_reasoning=True` 且 `base_url` 解析为 DashScope 域名时:
            - 在请求体中合并注入 `extra_body={"enable_thinking": True}`(保留已有 `extra_body` 字段),按照 DashScope 官方建议开启 Qwen3/QwQ 的思考模式。
            - 不启用 Responses API,依然使用标准 `/chat/completions` 接口,符合 DashScope 兼容模式要求。
        - **其他第三方 OpenAI 兼容服务**:
          - 当 `openai_use_reasoning=True` 且 `base_url` 既非 OpenAI 官方也非 DashScope 时:
            - 不再强行附加任何 provider 特定的 thinking 参数,仅记录一条 info 级别日志说明「请求了 reasoning,但当前 base_url 不识别为 OpenAI 或 DashScope,故跳过 provider-specific 参数」,避免潜在 4xx 报错或不可预期行为。
      - 根据 provider 选择不同的 LLM 类:
        - 对于 **OpenAI 官方 endpoint**(包括默认未设置 `base_url` 时)仍使用原始 `ChatOpenAI`。
        - 对于 **任何非 OpenAI 官方的 base_url**(包含 DashScope 和其他兼容实现),统一使用扩展后的 `ChatOpenAIWithReasoningContent`,保证即使未来有更多兼容服务返回 `reasoning_content` 字段,也能统一注入 `additional_kwargs`。
    
    - **扩展 LLM 响应中的 thinking/reasoning 提取与日志记录(`shopping_agent.py`)**
      - 新增 `_coerce_reasoning_text(value)` 辅助函数,对多种结构的 reasoning 返回进行**鲁棒的文本提取**:
        - 支持 `str`、`dict`、`list` 等多种结构:
          - 对 `dict` 优先尝试聚合 `content` / `summary` / `text` / `reasoning_content` 字段;
          - 对 `list` 递归调用自身并按行拼接;
          - 当无法结构化提取时,兜底使用 `json.dumps(..., ensure_ascii=False)` 或 `str(value)`,避免因结构变更导致完全丢失思考信息。
      - 在 `_extract_thinking(msg)` 中统一使用 `_coerce_reasoning_text`,大幅提高对不同模型/接口返回格式的兼容性:
        - 优先从 `msg.additional_kwargs["reasoning_content"]` 中提取(DashScope/Qwen 官方推荐字段)并返回。
        - 其次从 `msg.additional_kwargs["reasoning"]` 中提取(OpenAI Responses API reasoning 对象)。
        - 再次遍历 `msg.content` 为 list 的情况,识别 `type` 为 `"reasoning" / "reasoning_content" / "thinking"` 的内容块,并通过 `_coerce_reasoning_text` 提取文本。
        - 最后对于纯字符串 content,通过 `_RE_THINK_INNER` 正则匹配 `<think>...</think>` 包裹的思考片段,只返回标签内正文。
      - 在 `_message_for_log` 中增加 `include_thinking` 开关:
        - 当 `include_thinking=True` 时:
          - 调用 `_extract_thinking(msg)` 获取思考内容,并做长度截断(超过 `_LOG_CONTENT_MAX` 时尾部追加 `[truncated, total N chars]` 标记),然后写入 `out["thinking"]` 字段。
        - 日志中区分「正式回复」与「thinking」两个字段,便于后续排查与分析。
      - 调整 `_message_for_log` 对「是否需要走 `_extract_formal_reply`」的判断逻辑:
        - 如 `msg.additional_kwargs` 中存在 `reasoning` 或 `reasoning_content` 字段,则通过 `_extract_formal_reply` 去掉 thinking,只保留正式回复文本;
        - 否则直接使用 `_extract_message_text` 减少不必要处理。
      - 在 LangGraph 的 `agent_node` 中,将 LLM 响应的日志调用更新为:
        - `response_log = _message_for_log(response, include_thinking=True)`
        - 确保每条 `LLM_RESPONSE` 日志都尽量带有 `"thinking"` 字段(在模型真实返回思考内容的前提下),方便线上观测与调试。
    
    - **为兼容模式增加 `reasoning_content` 注入支持(`shopping_agent.py`)**
      - 新增 `ChatOpenAIWithReasoningContent` 子类,继承自 `ChatOpenAI`,重写 `_create_chat_result`:
        - `super()._create_chat_result(response, generation_info)` 后,通过原始 `response`(`dict` 或包含 `model_dump()` 的对象)提取 `choices[i].message.reasoning_content`。
        - 对于每个 choice,如存在 `reasoning_content` 字段,则将其写入对应 `AIMessage` 的 `additional_kwargs["reasoning_content"]` 中。
        - 该注入逻辑是幂等的、仅在字段存在时生效,对不返回 reasoning_content 的模型/服务没有副作用。
      - 该子类专门用于 DashScope 及未来可能返回 `reasoning_content` 的其他兼容 provider,将 provider 特定逻辑集中在一处,方便维护。
    
    - **补充与修正与 thinking 相关的正则和提取逻辑(`shopping_agent.py`)**
      - 在原有 `_RE_THINK_TAGS` 基础上新增加 `_RE_THINK_INNER`:
        - `_RE_THINK_TAGS`:用于从完整回复中移除 `<think>...</think>` 块,供 `_extract_formal_reply` 使用。
        - `_RE_THINK_INNER`:用于仅提取 `<think>...</think>` 标签内部正文,供 `_extract_thinking` 使用,避免日志中重复包含标签本身。
    
    - **更新配置注释以匹配新的 reasoning 行为(`config.py`)**
      - 改写 `openai_use_reasoning` 的注释,使其准确描述在不同 provider 下的启用方式:
        - OpenAI 官方 endpoint(含 `api.openai.com` base_url):通过 Responses API 的 `reasoning` 参数启用思考模式。
        - DashScope 兼容 endpoint:通过 `extra_body.enable_thinking=True` 开启思考模式,由模型返回 `reasoning_content`。
      - 将 `openai_use_reasoning` 默认值设置为 `True`,以便在满足条件时自动启用 reasoning,同时由上游配置控制具体是否生效。
    tangwang
     

21 Feb, 2026

2 commits


18 Feb, 2026

1 commit


12 Feb, 2026

3 commits