19 Mar, 2026
1 commit
18 Mar, 2026
2 commits
01 Mar, 2026
1 commit
26 Feb, 2026
2 commits
-
Made-with: Cursor
-
- **统一并增强 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,同时由上游配置控制具体是否生效。
23 Feb, 2026
1 commit
22 Feb, 2026
1 commit
-
一、命名与引用统一 ----------------- - 全文将 [SEARCH_REF:ref_id] 统一为 [SEARCH_RESULTS_REF:ref_id]。 - README_prompts.md: 文档中两处 SEARCH_REF 改为 SEARCH_RESULTS_REF。 - app/agents/shopping_agent.py: 系统提示与注释中的 SEARCH_REF、make_search_products_tool 改为 SEARCH_RESULTS_REF、search_products。 - app/search_registry.py: 注释与 docstring 中的 SEARCH_REF 改为 SEARCH_RESULTS_REF。 - app.py: SEARCH_REF_PATTERN 重命名为 SEARCH_RESULTS_REF_PATTERN,正则与注释同步。 - app/tools/search_tools.py: 工具返回与注释中的 SEARCH_REF 改为 SEARCH_RESULTS_REF。 二、search_registry:ref_id 改为会话内自增 ----------------------------------------- - 移除 new_ref_id()(原 uuid 短码);ref_id 改为按 session 自增:sr_1, sr_2, ... - SearchResultRegistry 新增 _session_counter 与 next_ref_id(session_id)。 - register 逻辑不变;clear_session 时同时清除 _session_counter。 三、search_tools:搜索逻辑统一、去除重复 --------------------------------------- - 新增 _call_search_api(query, size):仅调用搜索 API,返回 (raw_results, total_hits) 或 None。 - 新增 _raw_to_product_items(raw_results, labels=None):将 API 原始结果转为 list[ProductItem];有 labels 时按 Relevant/Partially Relevant 过滤并打标,否则全部打 Partially Relevant。 - 新增 search_products_impl(query, limit, *, assess_quality=True, session_id=None, registry=None):唯一搜索实现;返回 (ref_id_or_none, products, assessed_count)。assess_quality 且提供 session_id/registry 时执行 LLM 评估并写入 registry,否则仅调 API 并返回产品列表。 - make_search_products_tool 内 search_products 改为调用 search_products_impl(..., assess_quality=True, session_id, registry),根据返回值拼工具说明文案;使用 registry.next_ref_id(session_id)。 - 新增 search_products_api_only(query, limit=12):薄封装,调用 search_products_impl(..., assess_quality=False),返回 list[ProductItem],供前端「找相似」侧栏使用(仅 API,不做 LLM 评估)。 四、app.py:前端性能与交互优化 ----------------------------- 1) 图片缓存 - 模块级 _IMAGE_CACHE (OrderedDict),最多 100 条;key 为 image_url 或 "local:{path}"。 - _load_product_image:先查缓存,命中则 move_to_end 并返回;未命中则请求/读本地后写入缓存并做 LRU 淘汰。 2) 减少全量 rerun(fragment) - render_referenced_products_in_input、render_bottom_actions_bar 加 @st.fragment,删除引用 / 点 Ask·Compare 时只重跑对应片段。 - 侧栏内容用 @st.fragment 的 _sidebar_fragment() 包裹,在 with st.sidebar 内调用,Clear Chat 时只重跑侧栏。 3) 「找相似」:先 loading,再仅调搜索 API - 点击 Similar products 时只设置 side_panel.payload = { query, loading: True } 并 rerun,侧栏先显示「加载中…」。 - main() 中若 side_panel.mode=="similar" 且 payload.loading,则调用 search_products_api_only(payload["query"], 12),将结果写入 payload.products、loading=False,再 rerun。 - render_side_drawer:payload.loading 显示加载中;payload.products 存在则用该列表渲染卡片;否则保留基于 ref_id + registry 的兼容逻辑。 - 删除 _run_similar_search(原完整 search tool + LLM 评估),改为上述流程。 4) 长对话渲染 - 仅渲染最近 50 条消息(MAX_MESSAGES=50),msg_index 用 start_idx + i 保持 widget key 稳定;超过 50 条时顶部显示「仅显示最近 50 条,共 N 条消息」。 Co-authored-by: Cursor <cursoragent@cursor.com>
21 Feb, 2026
4 commits
-
=== 修改概要 === 1. 商品卡片 hover 操作:Similar products + 勾选 2. 底部悬浮 Ask / Compare 操作条 3. 右侧抽屉(Modal 式覆盖层):Similar 搜索结果 / Compare 占位 4. 引用商品对话:输入区展示已引用商品(可删)、发送时注入后端格式前缀 5. 抽屉关闭:HTML 按钮 + JS 即时隐藏 + replaceState,不刷背景页 6. 多消息重复 [SEARCH_REF] 导致控件 key 冲突:按消息+块维度加唯一前缀 --- 1. 会话状态 (initialize_session) --- - selected_products: dict, key -> 商品摘要(ref_id, spu_id, sku_id, title, price, tags, specifications),用于勾选与 Ask/Compare - side_panel: { visible, mode, payload },mode 为 "similar" | "compare",payload 为 ref_id/query 或已选商品列表 - referenced_products: list[dict],当前输入区「已引用商品」,点击 Ask 后填入,发送后清空 --- 2. 商品卡片 (display_product_card_from_item) --- - 新增参数 widget_prefix(默认 ""),与 _product_key 组成 key_suffix,用于 st.button / st.checkbox 的 key,避免跨消息重复 - 卡片底部操作条(product-card-actions):Similar products 按钮、Select 勾选框 - Similar 点击:_run_similar_search(product.title) 调用 make_search_products_tool,解析返回的 [SEARCH_REF:ref_id],打开 side_panel mode=similar - 勾选:更新 selected_products[pkey],pkey = _product_key(ref_id, index, product) --- 3. 底部操作条 (render_bottom_actions_bar) --- - 仅当 len(selected_products) > 0 时展示 - Ask:将 selected 写入 referenced_products,rerun - Compare:打开 side_panel mode=compare,payload=已选列表,rerun --- 4. 右侧抽屉 (render_side_drawer) --- - 在 main() 中优先渲染(紧跟 initialize_session 与 query_params 处理),保证 fixed 定位相对视口 - 结构:backdrop (fixed 全屏) + panel (fixed 右侧,top:56px 避顶栏),z-index 999998/999999 - similar 模式:用 payload.ref_id 从 global_registry 取 SearchResult,渲染最多 12 条商品卡片(纯 HTML,无 Streamlit 控件) - compare 模式:列出已选商品标题/价格 +「对比功能暂未实现」提示 - 关闭:panel 内 <button id="side-drawer-close-btn">✕ 关闭</button>;通过 streamlit.components.v1.html 注入 JS: - 点击按钮或 backdrop 时,将 backdrop/panel 设为 display:none(即时隐藏,不刷新) - history.replaceState 设置 ?close_side_panel=1(同页,不导航) - main() 顶部若检测到 close_side_panel,清除 side_panel 状态并 st.query_params.clear(),下次自然 rerun 时不再渲染抽屉 --- 5. 引用商品对话 --- - _product_to_info:增加 sku_id(同 spu_id)、tags、specifications,供后端前缀使用 - _build_reference_prefix(products):生成「引用 x 款商品:」+ 每款一行「sku_id=...; title=...; price=...; tags=...; specifications=...」 - render_referenced_products_in_input:在输入框上方展示 referenced_products,每项可点 ✕ 移除 - 发送逻辑:若有 referenced_products,agent_query = _build_reference_prefix(referenced_products) + "\n\n" + raw_user_query;消息列表存 raw_user_query;发送后 referenced_products = [] --- 6. 多消息重复 key 修复 (StreamlitDuplicateElementKey) --- - 同一条 [SEARCH_REF:xxx] 在不同消息中会重复渲染,导致 similar_/select_ 的 key(仅 ref_id+index+spu_id)重复 - 方案:为每个「消息+ref 块」引入唯一 widget_prefix - display_message(message, msg_index=0);main 中 for msg_idx, message in enumerate(messages): display_message(message, msg_index=msg_idx) - render_message_with_refs(..., msg_index=0);对每个 ref 段 widget_prefix = f"m{msg_index}_r{i}"(i 为 parts 下标) - render_search_result_block(result, widget_prefix="");将 widget_prefix 传入 display_product_card_from_item - display_product_card_from_item(..., widget_prefix="");key_suffix = f"{widget_prefix}_{pkey}" if widget_prefix else pkey;按钮/勾选框 key 使用 key_suffix --- 7. 其他 --- - Clear Chat 时一并清空 selected_products、referenced_products、side_panel - CSS:product-card-wrapper/actions、side-drawer-*、bottom-actions-bar、#side-drawer-close-btn 等 - 依赖:streamlit.components.v1 用于注入抽屉关闭脚本 Co-authored-by: Cursor <cursoragent@cursor.com>
20 Feb, 2026
4 commits
-
## 搜索工具与质量评估 - _assess_search_quality 仅返回 (labels, quality_summary):去掉 verdict(优质/一般/较差)及依赖逻辑;prompt 要求 LLM 输出 labels + quality_summary(1–2 句:结果主要包含什么、是否基本满足意图、匹配度)。 - 工具返回格式统一为:【搜索完成】query='...' + 结果引用 [SEARCH_REF:ref_id] + 搜索结果质量情况(评估总条数、Highly Relevant / Partially Relevant 条数)+ results list(top10 标题)。 - 精简 prompt 与日志:评估输入仅保留序号+标题;删除 verdict_hint、逐条 SEARCH_RESULT_ITEM/SEARCH_RESULT_PRODUCT 日志,保留单行注册日志。 ## 三级标签改为英文 - 完美匹配 → Highly Relevant;部分匹配 → Partially Relevant;不相关 → Not Relevant。 - 全量替换:search_tools(prompt、valid、统计与过滤)、search_registry(ProductItem.match_label 默认及注释、SearchResult 注释)、app.py(卡片 label_style、结果块头部与筛选逻辑)。 ## Registry 与 UI - SearchResult 移除 quality_verdict 字段;quality_summary 由 _assess_search_quality 的 LLM 返回写入。 - 结果块头部不再展示 verdict 图标/文案,改为展示 query + Highly/Partially Relevant 件数 + quality_summary(若有)。 ## Agent - 系统提示词调整:角色与原则、价值提供与信息收集、search_products 与 [SEARCH_REF:xxx] 使用说明。 Co-authored-by: Cursor <cursoragent@cursor.com>
-
- Search image_url: parse results[].image_url, add _normalize_image_url() to convert protocol-less URLs (////host/path) to https://host/path; fix double slash (use https:// + url.lstrip("/") so normalized URL has single //). - Logging: log full LLM request/response (LLM_REQUEST, LLM_RESPONSE), full tool call results (TOOL_CALL_RESULT); for search tool log SEARCH_RESULT summary and per-item SEARCH_RESULT_ITEM (image_url_raw) and SEARCH_RESULT_PRODUCT (image_url_normalized). - Streamlit: replace deprecated use_container_width=True with width="stretch" for st.image and st.button. Co-authored-by: Cursor <cursoragent@cursor.com> -
## 搜索结果管理与人机回复引用 - 新增 app/search_registry.py:SearchResultRegistry + SearchResult/ProductItem 数据结构,按 session 存储每次搜索的 query、质量评估与商品列表。 - 搜索工具改为工厂 make_search_products_tool(session_id, registry):每次搜索后由 LLM 对 top20 打标(完美匹配/部分匹配/不相关),产出整体 verdict(优质/一般/较差),仅将「完美+部分」写入 registry 并返回摘要 + [SEARCH_REF:ref_id];不再向 Agent 返回完整商品列表。 - 废除 extract_products_from_response:最终回复中通过内联 [SEARCH_REF:xxx] 引用「搜索结果块」,UI 用 SEARCH_REF_PATTERN 解析后从 registry 取对应 SearchResult 渲染 query 标题 + 商品卡片,避免 LLM 复述商品列表,节省 token 并减少错误。 ## 系统提示与行为约束 - 系统提示词通用化(不绑定时尚品类),明确四步:理解意图 → 规划 2~4 个 query → 执行搜索并评估 → 撰写回复。 - 要求同一条回复中并行发起 2~4 次 search_products(不同 query),利用 LangGraph ToolNode 的并行执行缩短等待;禁止串行「搜一个看一个再搜下一个」。 - 轮次上限:最多两轮搜索(两轮 = 两次「Agent 发 tool_calls → Tools 执行 → 返回」);若已有优质/一般结果则直接写回复,仅当全部较差时允许第二轮(最多再 1~2 个 query)。图逻辑增加 n_tool_rounds 状态与 agent_final 节点,两轮后强制进入「仅回复、不调工具」的 agent_final,避免无限重搜。 ## 前端与工具导出 - app.py:render_message_with_refs(content, session_id) 按 [SEARCH_REF:xxx] 切分并渲染;render_search_result_block 展示 query + 质量 + 商品卡片;display_product_card_from_item 支持 image_url/本地图/占位;Clear Chat 时 clear_session(registry)。 - app/tools/__init__.py:改为导出 make_search_products_tool、web_search,不再导出已移除的 search_products 顶层名。 Co-authored-by: Cursor <cursoragent@cursor.com>
18 Feb, 2026
1 commit
-
2. add web sch tools
12 Feb, 2026
4 commits