Commit 50fcfb9d6eb522e6656ddc551146b17d7eb4b23b
1 parent
7e985858
up
Showing
7 changed files
with
40 additions
and
17 deletions
Show diff stats
README_prompts.md
| ... | ... | @@ -71,3 +71,16 @@ graphRAG在商品搜索中如何使用?我想将他用于,对商品的模糊 |
| 71 | 71 | 2. 一个勾选框,点击后为勾选状态。可以勾选多个商品。 |
| 72 | 72 | 下方也悬浮两个菜单,一个ask,一个compare。 |
| 73 | 73 | 如果是点击了ask,那么,将引用这两个商品进行继续对话,如果点击了compare,那么,也是从右侧拉出一个页面,覆盖到上面,对这两个商品进行对比,页面内容为空,提示暂未实现即可) |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | +要持久化session,每一次对话都要保存起来,左侧例举出来,点击其中一个就可以继续进行对话。 | |
| 85 | +2. 现在用户的对话,和AI的回复,差别不明显,比如一个靠做一个靠右?请你思考一个简单的做法,然后 用户的发言,是可编辑的,也就是,之前的某个轮次,用户输入框的内容可以修改,修改后点击发送,则从这个地方开始对话、原来的这里之后的内容就清空(覆盖)掉了 | |
| 86 | +如何设计比较好 | |
| 74 | 87 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -516,7 +516,7 @@ def _run_similar_search(query: str) -> Optional[str]: |
| 516 | 516 | return None |
| 517 | 517 | tool = make_search_products_tool(session_id, global_registry) |
| 518 | 518 | try: |
| 519 | - out = tool.invoke({"query": query.strip(), "limit": 12}) | |
| 519 | + out = tool.invoke({"query": query.strip()}) | |
| 520 | 520 | match = SEARCH_REF_PATTERN.search(out) |
| 521 | 521 | if match: |
| 522 | 522 | return match.group(1).strip() |
| ... | ... | @@ -608,7 +608,7 @@ def display_product_card_from_item( |
| 608 | 608 | |
| 609 | 609 | def render_search_result_block(result: SearchResult, widget_prefix: str = "") -> None: |
| 610 | 610 | """ |
| 611 | - Render a full search result block in place of a [SEARCH_REF:xxx] token. | |
| 611 | + Render a full search result block in place of a [SEARCH_REF:ref_id] token. | |
| 612 | 612 | |
| 613 | 613 | widget_prefix: unique per (message, ref block) so Streamlit widget keys stay unique. |
| 614 | 614 | """ |
| ... | ... | @@ -649,7 +649,7 @@ def render_message_with_refs( |
| 649 | 649 | msg_index: int = 0, |
| 650 | 650 | ) -> None: |
| 651 | 651 | """ |
| 652 | - Render an assistant message that may contain [SEARCH_REF:xxx] tokens. | |
| 652 | + Render an assistant message that may contain [SEARCH_REF:ref_id] tokens. | |
| 653 | 653 | |
| 654 | 654 | msg_index: message index in chat, used to keep widget keys unique across messages. |
| 655 | 655 | """ |
| ... | ... | @@ -730,7 +730,7 @@ def display_message(message: dict, msg_index: int = 0): |
| 730 | 730 | |
| 731 | 731 | st.markdown("---") |
| 732 | 732 | |
| 733 | - # Render message: expand [SEARCH_REF:xxx] tokens into product card blocks | |
| 733 | + # Render message: expand [SEARCH_REF:ref_id] tokens into product card blocks | |
| 734 | 734 | session_id = st.session_state.get("session_id", "") |
| 735 | 735 | render_message_with_refs( |
| 736 | 736 | content, session_id, fallback_refs=message.get("search_refs"), msg_index=msg_index | ... | ... |
app/agents/shopping_agent.py
| ... | ... | @@ -4,7 +4,7 @@ Conversational Shopping Agent with LangGraph |
| 4 | 4 | Architecture: |
| 5 | 5 | - ReAct-style agent: plan → search → evaluate → re-plan or respond |
| 6 | 6 | - search_products is session-bound, writing curated results to SearchResultRegistry |
| 7 | -- Final AI message references results via [SEARCH_REF:xxx] tokens instead of | |
| 7 | +- Final AI message references results via [SEARCH_REF:ref_id] tokens instead of | |
| 8 | 8 | re-listing product details; the UI renders product cards from the registry |
| 9 | 9 | """ |
| 10 | 10 | |
| ... | ... | @@ -34,7 +34,7 @@ logger = logging.getLogger(__name__) |
| 34 | 34 | # Key design decisions: |
| 35 | 35 | # 1. Guides multi-query search planning with explicit evaluate-and-decide loop |
| 36 | 36 | # 2. Forbids re-listing product details in the final response |
| 37 | -# 3. Mandates [SEARCH_REF:xxx] inline citation as the only product presentation mechanism | |
| 37 | +# 3. Mandates [SEARCH_REF:ref_id] inline citation as the only product presentation mechanism | |
| 38 | 38 | SYSTEM_PROMPT = f"""角色定义 |
| 39 | 39 | 你是我们店铺的一名专业的电商导购,是一个善于倾听、主动引导、懂得搭配的“时尚顾问”,通过有温度的对话,给用户提供有价值的信息,包括需求引导、方案推荐、搜索结果推荐,最终促成满意的购物决策或转化行为。 |
| 40 | 40 | 作为我们店铺的一名专业的销售,除了本店铺的商品的推荐,你可以给用户提供有帮助的信息,但是不要虚构商品、提供本商店搜索结果以外的商品。 |
| ... | ... | @@ -52,9 +52,9 @@ SYSTEM_PROMPT = f"""角色定义 |
| 52 | 52 | 2. 如何使用make_search_products_tool: |
| 53 | 53 | 1. 可以生成多个query进行搜索:在需要搜索商品的时候,可以将需求分解为 2-4 个搜索查询,每个 query 聚焦一个明确的商品子类或搜索角度。 |
| 54 | 54 | 2. 可以根据搜索结果调整搜索策略:每次调用 search_products 后,工具会返回搜索结果的相关性的判断、以及搜索结果的topN的title,你需要决策是否要调整搜索策略,比如结果质量太差,可能需要调整搜索词、或者加大试探的query数量(不要超过3-5个)。结果太差的原因有可能是你生成的query不合理、请根据你看到的商品名称的构成组织搜索关键词。 |
| 55 | -3. 在最终回复中使用 [SEARCH_REF:xxx] 内联引用搜索结果: | |
| 56 | - 1. 搜索工具会返回一个结果引用标识[SEARCH_REF:xxx],撰写最终答复的时候请直接引用 [SEARCH_REF:xxx] ,系统会自动在该位置渲染对应的商品卡片列表,无需复述搜索结果。 | |
| 57 | - 2. 因为系统会自动将[SEARCH_REF:xxx]渲染为搜索结果,所以[SEARCH_REF:xxx]必须独占一行,且只在需要渲染该query完整的搜索结果时才进行引用,同一个结果不要重复引用。 | |
| 55 | +3. 在最终回复中使用 [SEARCH_REF:ref_id] 内联引用搜索结果: | |
| 56 | + 1. 搜索工具会返回一个结果引用标识[SEARCH_REF:ref_id],撰写最终答复的时候请直接引用 [SEARCH_REF:ref_id] ,系统会自动在该位置渲染对应的商品卡片列表,无需复述搜索结果。 | |
| 57 | + 2. 因为系统会自动将[SEARCH_REF:ref_id]渲染为搜索结果,所以[SEARCH_REF:ref_id]必须独占一行,且只在需要渲染该query完整的搜索结果时才进行引用,同一个结果不要重复引用。 | |
| 58 | 58 | 4. 今天是{datetime.now().strftime("%Y-%m-%d")},所有与当前时间(比如天气、最新或即将发生的事件)相关的问题,都要使用web_search工具)。 |
| 59 | 59 | """ |
| 60 | 60 | |
| ... | ... | @@ -72,8 +72,8 @@ SYSTEM_PROMPT___2 = """ 角色定义 |
| 72 | 72 | 2. 如何使用make_search_products_tool: |
| 73 | 73 | 1. 可以生成多个query进行搜索:在需要搜索商品的时候,可以将需求分解为 2-4 个搜索查询,每个 query 聚焦一个明确的商品子类或搜索角度。 |
| 74 | 74 | 2. 可以根据搜索结果调整搜索策略:每次调用 search_products 后,工具会返回搜索结果的相关性的判断、以及搜索结果的topN的title,你需要决策是否要调整搜索策略,比如结果质量太差,可能需要调整搜索词、或者加大试探的query数量(不要超过3-5个)。 |
| 75 | - 3. 使用 [SEARCH_REF:xxx] 内联引用搜索结果:搜索工具会返回一个结果引用标识[SEARCH_REF:xxx],撰写最终答复的时候可以直接引用将 [SEARCH_REF:xxx] ,系统会自动在该位置渲染对应的商品卡片列表,无需复述搜索结果。 | |
| 76 | - 4. 因为系统会自动将[SEARCH_REF:xxx]渲染为搜索结果,所以只在需要渲染该query完整的搜索结果时才进行引用,同一个结果不要重复引用。 | |
| 75 | + 3. 使用 [SEARCH_REF:ref_id] 内联引用搜索结果:搜索工具会返回一个结果引用标识[SEARCH_REF:ref_id],撰写最终答复的时候可以直接引用将 [SEARCH_REF:ref_id] ,系统会自动在该位置渲染对应的商品卡片列表,无需复述搜索结果。 | |
| 76 | + 4. 因为系统会自动将[SEARCH_REF:ref_id]渲染为搜索结果,所以只在需要渲染该query完整的搜索结果时才进行引用,同一个结果不要重复引用。 | |
| 77 | 77 | """ |
| 78 | 78 | |
| 79 | 79 | |
| ... | ... | @@ -226,7 +226,7 @@ class ShoppingAgent: |
| 226 | 226 | |
| 227 | 227 | Returns: |
| 228 | 228 | dict with keys: |
| 229 | - response – final AI message text (may contain [SEARCH_REF:xxx] tokens) | |
| 229 | + response – final AI message text (may contain [SEARCH_REF:ref_id] tokens) | |
| 230 | 230 | tool_calls – list of {name, args, result_preview} |
| 231 | 231 | debug_steps – detailed per-node step log |
| 232 | 232 | search_refs – dict[ref_id → SearchResult] for all searches this turn | ... | ... |
app/config.py
| ... | ... | @@ -43,6 +43,8 @@ class Settings(BaseSettings): |
| 43 | 43 | # Search Configuration |
| 44 | 44 | top_k_results: int = 10 |
| 45 | 45 | similarity_threshold: float = 0.6 |
| 46 | + # 商品搜索 API 单次请求最多返回条数(1–20),search_products 统一使用此配置 | |
| 47 | + search_products_limit: int = 20 | |
| 46 | 48 | |
| 47 | 49 | # Search API (see docs/搜索API对接指南.md) |
| 48 | 50 | search_api_base_url: str = "http://120.76.41.98:6002" | ... | ... |
app/search_registry.py
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | Search Result Registry |
| 3 | 3 | |
| 4 | 4 | Stores structured search results keyed by session and ref_id. |
| 5 | -Each [SEARCH_REF:xxx] in an AI response maps to a SearchResult stored here, | |
| 5 | +Each [SEARCH_REF:ref_id] in an AI response maps to a SearchResult stored here, | |
| 6 | 6 | allowing the UI to render product cards without the LLM ever re-listing them. |
| 7 | 7 | """ |
| 8 | 8 | ... | ... |
app/tools/search_tools.py
| ... | ... | @@ -82,7 +82,15 @@ def _assess_search_quality(query: str, raw_products: list) -> tuple[list[str], s |
| 82 | 82 | 搜索结果(共 {n} 条): |
| 83 | 83 | {product_text} |
| 84 | 84 | |
| 85 | -等级说明:Relevant=完全符合查询意图;Partially Relevant=基本相关(如品类等主需求匹配但部分属性不完全符合);Irrelevant=不相关。 | |
| 85 | +等级说明: | |
| 86 | +Relevant | |
| 87 | +The product generally satisfies the main shopping intent of the query. Minor missing, implicit, or unspecified attributes are acceptable as long as the product reasonably fits the intended use or scenario. | |
| 88 | + | |
| 89 | +Partially Relevant | |
| 90 | +The product is related to the query and matches the general category or purpose, but shows weaker alignment with the specific intent or context. | |
| 91 | + | |
| 92 | +Irrelevant | |
| 93 | +The product does not match the core intent or intended use implied by the query. | |
| 86 | 94 | |
| 87 | 95 | 请严格按以下 JSON 输出,仅输出 JSON,无其他内容: |
| 88 | 96 | {{"labels": ["Relevant", "Partially Relevant", "Irrelevant", ...], "quality_summary": "你的1-2句总结"}} |
| ... | ... | @@ -132,17 +140,17 @@ def make_search_products_tool( |
| 132 | 140 | """ |
| 133 | 141 | |
| 134 | 142 | @tool |
| 135 | - def search_products(query: str, limit: int = 20) -> str: | |
| 143 | + def search_products(query: str) -> str: | |
| 136 | 144 | """搜索商品库并做质量评估:LLM 为每条结果打等级(Relevant / Partially Relevant / Irrelevant),返回引用与 top10 标题。 |
| 137 | 145 | |
| 138 | 146 | Args: |
| 139 | 147 | query: 自然语言商品描述 |
| 140 | - limit: 最多返回条数(1-20) | |
| 141 | 148 | |
| 142 | 149 | Returns: |
| 143 | 150 | 【搜索完成】+ 结果引用 [SEARCH_REF:ref_id] + 质量情况(评估条数、Relevant/Partially Relevant 数)+ results list(top10 标题) |
| 144 | 151 | """ |
| 145 | 152 | try: |
| 153 | + limit = min(max(settings.search_products_limit, 1), 20) | |
| 146 | 154 | logger.info(f"[{session_id}] search_products: query={query!r} limit={limit}") |
| 147 | 155 | |
| 148 | 156 | url = f"{settings.search_api_base_url.rstrip('/')}/search/" |
| ... | ... | @@ -152,7 +160,7 @@ def make_search_products_tool( |
| 152 | 160 | } |
| 153 | 161 | payload = { |
| 154 | 162 | "query": query, |
| 155 | - "size": min(max(limit, 1), 20), | |
| 163 | + "size": limit, | |
| 156 | 164 | "from": 0, |
| 157 | 165 | "language": "zh", |
| 158 | 166 | "enable_rerank": True, | ... | ... |