Commit 01b46131b481e01e5462c3d022dc1ec862f96194

Authored by tangwang
1 parent bad17b15

流程跑通

@@ -19,7 +19,7 @@ SIMILARITY_THRESHOLD=0.6 @@ -19,7 +19,7 @@ SIMILARITY_THRESHOLD=0.6
19 19
20 # Search API (see docs/搜索API对接指南.md) 20 # Search API (see docs/搜索API对接指南.md)
21 SEARCH_API_BASE_URL=http://120.76.41.98:6002 21 SEARCH_API_BASE_URL=http://120.76.41.98:6002
22 -SEARCH_API_TENANT_ID=162 22 +SEARCH_API_TENANT_ID=170
23 23
24 # ==================== 24 # ====================
25 # Application Configuration 25 # Application Configuration
@@ -245,6 +245,10 @@ def initialize_session(): @@ -245,6 +245,10 @@ def initialize_session():
245 if "show_image_upload" not in st.session_state: 245 if "show_image_upload" not in st.session_state:
246 st.session_state.show_image_upload = False 246 st.session_state.show_image_upload = False
247 247
  248 + # Debug panel toggle
  249 + if "show_debug" not in st.session_state:
  250 + st.session_state.show_debug = False
  251 +
248 252
249 def save_uploaded_image(uploaded_file) -> Optional[str]: 253 def save_uploaded_image(uploaded_file) -> Optional[str]:
250 """Save uploaded image to temp directory""" 254 """Save uploaded image to temp directory"""
@@ -394,6 +398,7 @@ def display_message(message: dict): @@ -394,6 +398,7 @@ def display_message(message: dict):
394 content = message["content"] 398 content = message["content"]
395 image_path = message.get("image_path") 399 image_path = message.get("image_path")
396 tool_calls = message.get("tool_calls", []) 400 tool_calls = message.get("tool_calls", [])
  401 + debug_steps = message.get("debug_steps", [])
397 402
398 if role == "user": 403 if role == "user":
399 st.markdown('<div class="message user-message">', unsafe_allow_html=True) 404 st.markdown('<div class="message user-message">', unsafe_allow_html=True)
@@ -411,9 +416,42 @@ def display_message(message: dict): @@ -411,9 +416,42 @@ def display_message(message: dict):
411 else: # assistant 416 else: # assistant
412 # Display tool calls horizontally - only tool names 417 # Display tool calls horizontally - only tool names
413 if tool_calls: 418 if tool_calls:
414 - tool_names = [tc['name'] for tc in tool_calls] 419 + tool_names = [tc["name"] for tc in tool_calls]
415 st.caption(" → ".join(tool_names)) 420 st.caption(" → ".join(tool_names))
416 st.markdown("") 421 st.markdown("")
  422 +
  423 + # Optional: detailed debug panel (reasoning + tool details)
  424 + if debug_steps and st.session_state.get("show_debug"):
  425 + with st.expander("思考 & 工具调用详细过程", expanded=False):
  426 + for idx, step in enumerate(debug_steps, 1):
  427 + node = step.get("node", "unknown")
  428 + st.markdown(f"**Step {idx} – {node}**")
  429 +
  430 + if node == "agent":
  431 + msgs = step.get("messages", [])
  432 + if msgs:
  433 + st.markdown("**Agent Messages**")
  434 + for m in msgs:
  435 + role = m.get("type", "assistant")
  436 + content = m.get("content", "")
  437 + st.markdown(f"- `{role}`: {content}")
  438 +
  439 + tcs = step.get("tool_calls", [])
  440 + if tcs:
  441 + st.markdown("**Planned Tool Calls**")
  442 + for j, tc in enumerate(tcs, 1):
  443 + st.markdown(f"- **{j}. {tc.get('name')}**")
  444 + st.code(tc.get("args", {}), language="json")
  445 +
  446 + elif node == "tools":
  447 + results = step.get("results", [])
  448 + if results:
  449 + st.markdown("**Tool Results**")
  450 + for j, r in enumerate(results, 1):
  451 + st.markdown(f"- **Result {j}:**")
  452 + st.code(r.get("content", ""), language="text")
  453 +
  454 + st.markdown("---")
417 455
418 # Extract and display products if any 456 # Extract and display products if any
419 products = extract_products_from_response(content) 457 products = extract_products_from_response(content)
@@ -486,9 +524,9 @@ def display_welcome(): @@ -486,9 +524,9 @@ def display_welcome():
486 st.markdown( 524 st.markdown(
487 """ 525 """
488 <div class="feature-card"> 526 <div class="feature-card">
489 - <div class="feature-icon">💬</div>  
490 - <div class="feature-title">Text Search</div>  
491 - <div>Describe what you want</div> 527 + <div class="feature-icon">💗</div>
  528 + <div class="feature-title">懂你</div>
  529 + <div>能记住你的偏好,给你推荐适合的</div>
492 </div> 530 </div>
493 """, 531 """,
494 unsafe_allow_html=True, 532 unsafe_allow_html=True,
@@ -498,9 +536,9 @@ def display_welcome(): @@ -498,9 +536,9 @@ def display_welcome():
498 st.markdown( 536 st.markdown(
499 """ 537 """
500 <div class="feature-card"> 538 <div class="feature-card">
501 - <div class="feature-icon">📸</div>  
502 - <div class="feature-title">Image Search</div>  
503 - <div>Upload product photos</div> 539 + <div class="feature-icon">🛍️</div>
  540 + <div class="feature-title">懂商品</div>
  541 + <div>深度理解店铺内所有商品,智能匹配你的需求</div>
504 </div> 542 </div>
505 """, 543 """,
506 unsafe_allow_html=True, 544 unsafe_allow_html=True,
@@ -510,9 +548,9 @@ def display_welcome(): @@ -510,9 +548,9 @@ def display_welcome():
510 st.markdown( 548 st.markdown(
511 """ 549 """
512 <div class="feature-card"> 550 <div class="feature-card">
513 - <div class="feature-icon">🔍</div>  
514 - <div class="feature-title">Visual Analysis</div>  
515 - <div>AI analyzes prodcut style</div> 551 + <div class="feature-icon">💭</div>
  552 + <div class="feature-title">贴心</div>
  553 + <div>任意聊</div>
516 </div> 554 </div>
517 """, 555 """,
518 unsafe_allow_html=True, 556 unsafe_allow_html=True,
@@ -522,9 +560,9 @@ def display_welcome(): @@ -522,9 +560,9 @@ def display_welcome():
522 st.markdown( 560 st.markdown(
523 """ 561 """
524 <div class="feature-card"> 562 <div class="feature-card">
525 - <div class="feature-icon">💭</div>  
526 - <div class="feature-title">Conversational</div>  
527 - <div>Remembers context</div> 563 + <div class="feature-icon">👗</div>
  564 + <div class="feature-title">懂时尚</div>
  565 + <div>穿搭顾问 + 轻松对比</div>
528 </div> 566 </div>
529 """, 567 """,
530 unsafe_allow_html=True, 568 unsafe_allow_html=True,
@@ -559,6 +597,14 @@ def main(): @@ -559,6 +597,14 @@ def main():
559 st.session_state.uploaded_image = None 597 st.session_state.uploaded_image = None
560 st.rerun() 598 st.rerun()
561 599
  600 + # Debug toggle
  601 + st.markdown("---")
  602 + st.checkbox(
  603 + "显示调试过程 (debug)",
  604 + key="show_debug",
  605 + help="展开后可查看中间思考过程及工具调用详情",
  606 + )
  607 +
562 st.markdown("---") 608 st.markdown("---")
563 st.caption(f"Session: `{st.session_state.session_id[:8]}...`") 609 st.caption(f"Session: `{st.session_state.session_id[:8]}...`")
564 610
@@ -696,6 +742,7 @@ What are you looking for today?&quot;&quot;&quot; @@ -696,6 +742,7 @@ What are you looking for today?&quot;&quot;&quot;
696 "role": "assistant", 742 "role": "assistant",
697 "content": response, 743 "content": response,
698 "tool_calls": tool_calls, 744 "tool_calls": tool_calls,
  745 + "debug_steps": result.get("debug_steps", []),
699 } 746 }
700 ) 747 )
701 748
app/agents/shopping_agent.py
@@ -78,38 +78,34 @@ class ShoppingAgent: @@ -78,38 +78,34 @@ class ShoppingAgent:
78 """Build the LangGraph StateGraph""" 78 """Build the LangGraph StateGraph"""
79 79
80 # System prompt for the agent 80 # System prompt for the agent
81 - system_prompt = """You are an intelligent fashion shopping assistant. You can:  
82 -1. Search for products by text description (use search_products)  
83 -2. Analyze image style and attributes (use analyze_image_style)  
84 -  
85 -When a user asks about products:  
86 -- For text queries: use search_products directly  
87 -- For image uploads: use analyze_image_style first to understand the product, then use search_products with the extracted description  
88 -- You can call multiple tools in sequence if needed  
89 -- Always provide helpful, friendly responses  
90 -  
91 -CRITICAL FORMATTING RULES:  
92 -When presenting product results, you MUST use this EXACT format for EACH product:  
93 -  
94 -1. [Product Name]  
95 - ID: [Product ID Number]  
96 - Category: [Category]  
97 - Color: [Color]  
98 - Gender: [Gender]  
99 - (Include Season, Usage, Relevance if available)  
100 -  
101 -Example: 81 + system_prompt = """你是一位智能时尚购物助手,你可以:
  82 +1. 根据文字描述搜索商品(使用 search_products)
  83 +2. 分析图片风格和属性(使用 analyze_image_style)
  84 +
  85 +当用户咨询商品时:
  86 +- 文字提问:直接使用 search_products 搜索
  87 +- 图片上传:先用 analyze_image_style 理解商品,再用提取的描述调用 search_products 搜索
  88 +- 可按需连续调用多个工具
  89 +- 始终保持有用、友好的回复风格
  90 +
  91 +关键格式规则:
  92 +展示商品结果时,每个商品必须严格按以下格式输出:
  93 +
  94 +1. [标题 title]
  95 + ID: [商品ID]
  96 + 分类: [category_path]
  97 + 中文名: [title_cn](如有)
  98 + 标签: [tags](如有)
  99 +
  100 +示例:
102 1. Puma Men White 3/4 Length Pants 101 1. Puma Men White 3/4 Length Pants
103 ID: 12345 102 ID: 12345
104 - Category: Apparel > Bottomwear > Track Pants  
105 - Color: White  
106 - Gender: Men  
107 - Season: Summer  
108 - Usage: Sports  
109 - Relevance: 95.2% 103 + 分类: 服饰 > 裤装 > 运动裤
  104 + 中文名: 彪马男士白色九分运动裤
  105 + 标签: 运动,夏季,白色
110 106
111 -DO NOT skip the ID field! It is essential for displaying product images.  
112 -Be conversational in your introduction, but preserve the exact product format.""" 107 +不可省略 ID 字段!它是展示商品图片的关键。
  108 +介绍要口语化,但必须保持上述商品格式。"""
113 109
114 def agent_node(state: AgentState): 110 def agent_node(state: AgentState):
115 """Agent decision node - decides which tools to call or when to respond""" 111 """Agent decision node - decides which tools to call or when to respond"""
@@ -165,7 +161,9 @@ Be conversational in your introduction, but preserve the exact product format.&quot;&quot; @@ -165,7 +161,9 @@ Be conversational in your introduction, but preserve the exact product format.&quot;&quot;
165 image_path: Optional path to uploaded image 161 image_path: Optional path to uploaded image
166 162
167 Returns: 163 Returns:
168 - Dict with response and metadata 164 + Dict with response and metadata, including:
  165 + - tool_calls: list of tool calls with args and (truncated) results
  166 + - debug_steps: detailed intermediate reasoning & tool execution steps
169 """ 167 """
170 try: 168 try:
171 logger.info( 169 logger.info(
@@ -191,32 +189,74 @@ Be conversational in your introduction, but preserve the exact product format.&quot;&quot; @@ -191,32 +189,74 @@ Be conversational in your introduction, but preserve the exact product format.&quot;&quot;
191 "current_image_path": image_path, 189 "current_image_path": image_path,
192 } 190 }
193 191
194 - # Track tool calls 192 + # Track tool calls (high-level) and detailed debug steps
195 tool_calls = [] 193 tool_calls = []
  194 + debug_steps = []
196 195
197 - # Stream events to capture tool calls 196 + # Stream events to capture tool calls and intermediate reasoning
198 for event in self.graph.stream(input_state, config=config): 197 for event in self.graph.stream(input_state, config=config):
199 logger.info(f"Event: {event}") 198 logger.info(f"Event: {event}")
200 -  
201 - # Check for agent node (tool calls) 199 +
  200 + # Agent node: LLM reasoning & tool decisions
202 if "agent" in event: 201 if "agent" in event:
203 agent_output = event["agent"] 202 agent_output = event["agent"]
204 - if "messages" in agent_output:  
205 - for msg in agent_output["messages"]:  
206 - if hasattr(msg, "tool_calls") and msg.tool_calls:  
207 - for tc in msg.tool_calls:  
208 - tool_calls.append({  
209 - "name": tc["name"],  
210 - "args": tc.get("args", {}),  
211 - })  
212 -  
213 - # Check for tool node (tool results) 203 + messages = agent_output.get("messages", [])
  204 +
  205 + step_messages = []
  206 + step_tool_calls = []
  207 +
  208 + for msg in messages:
  209 + msg_text = _extract_message_text(msg)
  210 + msg_entry = {
  211 + "type": getattr(msg, "type", "assistant"),
  212 + "content": msg_text[:500], # truncate for safety
  213 + }
  214 + step_messages.append(msg_entry)
  215 +
  216 + # Capture tool calls from this agent message
  217 + if hasattr(msg, "tool_calls") and msg.tool_calls:
  218 + for tc in msg.tool_calls:
  219 + tc_entry = {
  220 + "name": tc.get("name"),
  221 + "args": tc.get("args", {}),
  222 + }
  223 + tool_calls.append(tc_entry)
  224 + step_tool_calls.append(tc_entry)
  225 +
  226 + debug_steps.append(
  227 + {
  228 + "node": "agent",
  229 + "messages": step_messages,
  230 + "tool_calls": step_tool_calls,
  231 + }
  232 + )
  233 +
  234 + # Tool node: actual tool execution results
214 if "tools" in event: 235 if "tools" in event:
215 tools_output = event["tools"] 236 tools_output = event["tools"]
216 - if "messages" in tools_output:  
217 - for i, msg in enumerate(tools_output["messages"]):  
218 - if i < len(tool_calls):  
219 - tool_calls[i]["result"] = str(msg.content)[:200] + "..." 237 + messages = tools_output.get("messages", [])
  238 +
  239 + step_tool_results = []
  240 +
  241 + for i, msg in enumerate(messages):
  242 + content_text = _extract_message_text(msg)
  243 + result_preview = content_text[:500] + ("..." if len(content_text) > 500 else "")
  244 +
  245 + if i < len(tool_calls):
  246 + tool_calls[i]["result"] = result_preview
  247 +
  248 + step_tool_results.append(
  249 + {
  250 + "content": result_preview,
  251 + }
  252 + )
  253 +
  254 + debug_steps.append(
  255 + {
  256 + "node": "tools",
  257 + "results": step_tool_results,
  258 + }
  259 + )
220 260
221 # Get final state 261 # Get final state
222 final_state = self.graph.get_state(config) 262 final_state = self.graph.get_state(config)
@@ -228,6 +268,7 @@ Be conversational in your introduction, but preserve the exact product format.&quot;&quot; @@ -228,6 +268,7 @@ Be conversational in your introduction, but preserve the exact product format.&quot;&quot;
228 return { 268 return {
229 "response": response_text, 269 "response": response_text,
230 "tool_calls": tool_calls, 270 "tool_calls": tool_calls,
  271 + "debug_steps": debug_steps,
231 "error": False, 272 "error": False,
232 } 273 }
233 274
cli_debug.py 0 → 100644
@@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
  1 +import json
  2 +from typing import Optional
  3 +
  4 +from app.agents.shopping_agent import ShoppingAgent
  5 +
  6 +
  7 +def run_once(agent: ShoppingAgent, query: str, image_path: Optional[str] = None) -> None:
  8 + """Run a single query through the agent and pretty-print details."""
  9 + result = agent.chat(query=query, image_path=image_path)
  10 +
  11 + print("\n=== Assistant Response ===")
  12 + print(result.get("response", ""))
  13 +
  14 + print("\n=== Tool Calls ===")
  15 + tool_calls = result.get("tool_calls", []) or []
  16 + if not tool_calls:
  17 + print("(no tool calls)")
  18 + else:
  19 + for i, tc in enumerate(tool_calls, 1):
  20 + print(f"[{i}] {tc.get('name')}")
  21 + print(" args:")
  22 + print(" " + json.dumps(tc.get("args", {}), ensure_ascii=False, indent=2).replace("\n", "\n "))
  23 + if "result" in tc:
  24 + print(" result (truncated):")
  25 + print(" " + str(tc.get("result")).replace("\n", "\n "))
  26 +
  27 + print("\n=== Debug Steps ===")
  28 + debug_steps = result.get("debug_steps", []) or []
  29 + if not debug_steps:
  30 + print("(no debug steps)")
  31 + else:
  32 + for idx, step in enumerate(debug_steps, 1):
  33 + node = step.get("node", "unknown")
  34 + print(f"\n--- Step {idx} [{node}] ---")
  35 +
  36 + if node == "agent":
  37 + msgs = step.get("messages", []) or []
  38 + if msgs:
  39 + print(" Agent messages:")
  40 + for m in msgs:
  41 + role = m.get("type", "assistant")
  42 + content = m.get("content", "")
  43 + print(f" - {role}: {content}")
  44 +
  45 + tcs = step.get("tool_calls", []) or []
  46 + if tcs:
  47 + print(" Planned tool calls:")
  48 + for j, tc in enumerate(tcs, 1):
  49 + print(f" [{j}] {tc.get('name')}")
  50 + print(
  51 + " args: "
  52 + + json.dumps(tc.get("args", {}), ensure_ascii=False)
  53 + )
  54 +
  55 + elif node == "tools":
  56 + results = step.get("results", []) or []
  57 + if results:
  58 + print(" Tool results:")
  59 + for j, r in enumerate(results, 1):
  60 + content = r.get("content", "")
  61 + print(f" [{j}] {content}")
  62 +
  63 +
  64 +def main() -> None:
  65 + """Simple CLI debugger to inspect agent reasoning and tool usage."""
  66 + agent = ShoppingAgent(session_id="cli-debug")
  67 + print("ShopAgent CLI Debugger")
  68 + print("输入你的问题,或者输入 `exit` 退出。\n")
  69 +
  70 + while True:
  71 + try:
  72 + query = input("你:").strip()
  73 + except (EOFError, KeyboardInterrupt):
  74 + print("\n再见 👋")
  75 + break
  76 +
  77 + if not query:
  78 + continue
  79 + if query.lower() in {"exit", "quit"}:
  80 + print("再见 👋")
  81 + break
  82 +
  83 + run_once(agent, query=query)
  84 + print("\n" + "=" * 60 + "\n")
  85 +
  86 +
  87 +if __name__ == "__main__":
  88 + main()
  89 +