diff --git a/.env.example b/.env.example
index 13ab59a..746aff3 100644
--- a/.env.example
+++ b/.env.example
@@ -19,7 +19,7 @@ SIMILARITY_THRESHOLD=0.6
# Search API (see docs/搜索API对接指南.md)
SEARCH_API_BASE_URL=http://120.76.41.98:6002
-SEARCH_API_TENANT_ID=162
+SEARCH_API_TENANT_ID=170
# ====================
# Application Configuration
diff --git a/app.py b/app.py
index 342861a..2b9e2e7 100644
--- a/app.py
+++ b/app.py
@@ -245,6 +245,10 @@ def initialize_session():
if "show_image_upload" not in st.session_state:
st.session_state.show_image_upload = False
+ # Debug panel toggle
+ if "show_debug" not in st.session_state:
+ st.session_state.show_debug = False
+
def save_uploaded_image(uploaded_file) -> Optional[str]:
"""Save uploaded image to temp directory"""
@@ -394,6 +398,7 @@ def display_message(message: dict):
content = message["content"]
image_path = message.get("image_path")
tool_calls = message.get("tool_calls", [])
+ debug_steps = message.get("debug_steps", [])
if role == "user":
st.markdown('
', unsafe_allow_html=True)
@@ -411,9 +416,42 @@ def display_message(message: dict):
else: # assistant
# Display tool calls horizontally - only tool names
if tool_calls:
- tool_names = [tc['name'] for tc in tool_calls]
+ tool_names = [tc["name"] for tc in tool_calls]
st.caption(" → ".join(tool_names))
st.markdown("")
+
+ # Optional: detailed debug panel (reasoning + tool details)
+ if debug_steps and st.session_state.get("show_debug"):
+ with st.expander("思考 & 工具调用详细过程", expanded=False):
+ for idx, step in enumerate(debug_steps, 1):
+ node = step.get("node", "unknown")
+ st.markdown(f"**Step {idx} – {node}**")
+
+ if node == "agent":
+ msgs = step.get("messages", [])
+ if msgs:
+ st.markdown("**Agent Messages**")
+ for m in msgs:
+ role = m.get("type", "assistant")
+ content = m.get("content", "")
+ st.markdown(f"- `{role}`: {content}")
+
+ tcs = step.get("tool_calls", [])
+ if tcs:
+ st.markdown("**Planned Tool Calls**")
+ for j, tc in enumerate(tcs, 1):
+ st.markdown(f"- **{j}. {tc.get('name')}**")
+ st.code(tc.get("args", {}), language="json")
+
+ elif node == "tools":
+ results = step.get("results", [])
+ if results:
+ st.markdown("**Tool Results**")
+ for j, r in enumerate(results, 1):
+ st.markdown(f"- **Result {j}:**")
+ st.code(r.get("content", ""), language="text")
+
+ st.markdown("---")
# Extract and display products if any
products = extract_products_from_response(content)
@@ -486,9 +524,9 @@ def display_welcome():
st.markdown(
"""
-
💬
-
Text Search
-
Describe what you want
+
💗
+
懂你
+
能记住你的偏好,给你推荐适合的
""",
unsafe_allow_html=True,
@@ -498,9 +536,9 @@ def display_welcome():
st.markdown(
"""
-
📸
-
Image Search
-
Upload product photos
+
🛍️
+
懂商品
+
深度理解店铺内所有商品,智能匹配你的需求
""",
unsafe_allow_html=True,
@@ -510,9 +548,9 @@ def display_welcome():
st.markdown(
"""
-
🔍
-
Visual Analysis
-
AI analyzes prodcut style
+
💭
+
贴心
+
任意聊
""",
unsafe_allow_html=True,
@@ -522,9 +560,9 @@ def display_welcome():
st.markdown(
"""
-
💭
-
Conversational
-
Remembers context
+
👗
+
懂时尚
+
穿搭顾问 + 轻松对比
""",
unsafe_allow_html=True,
@@ -559,6 +597,14 @@ def main():
st.session_state.uploaded_image = None
st.rerun()
+ # Debug toggle
+ st.markdown("---")
+ st.checkbox(
+ "显示调试过程 (debug)",
+ key="show_debug",
+ help="展开后可查看中间思考过程及工具调用详情",
+ )
+
st.markdown("---")
st.caption(f"Session: `{st.session_state.session_id[:8]}...`")
@@ -696,6 +742,7 @@ What are you looking for today?"""
"role": "assistant",
"content": response,
"tool_calls": tool_calls,
+ "debug_steps": result.get("debug_steps", []),
}
)
diff --git a/app/agents/shopping_agent.py b/app/agents/shopping_agent.py
index 481fd74..e2a6963 100644
--- a/app/agents/shopping_agent.py
+++ b/app/agents/shopping_agent.py
@@ -78,38 +78,34 @@ class ShoppingAgent:
"""Build the LangGraph StateGraph"""
# System prompt for the agent
- system_prompt = """You are an intelligent fashion shopping assistant. You can:
-1. Search for products by text description (use search_products)
-2. Analyze image style and attributes (use analyze_image_style)
-
-When a user asks about products:
-- For text queries: use search_products directly
-- For image uploads: use analyze_image_style first to understand the product, then use search_products with the extracted description
-- You can call multiple tools in sequence if needed
-- Always provide helpful, friendly responses
-
-CRITICAL FORMATTING RULES:
-When presenting product results, you MUST use this EXACT format for EACH product:
-
-1. [Product Name]
- ID: [Product ID Number]
- Category: [Category]
- Color: [Color]
- Gender: [Gender]
- (Include Season, Usage, Relevance if available)
-
-Example:
+ system_prompt = """你是一位智能时尚购物助手,你可以:
+1. 根据文字描述搜索商品(使用 search_products)
+2. 分析图片风格和属性(使用 analyze_image_style)
+
+当用户咨询商品时:
+- 文字提问:直接使用 search_products 搜索
+- 图片上传:先用 analyze_image_style 理解商品,再用提取的描述调用 search_products 搜索
+- 可按需连续调用多个工具
+- 始终保持有用、友好的回复风格
+
+关键格式规则:
+展示商品结果时,每个商品必须严格按以下格式输出:
+
+1. [标题 title]
+ ID: [商品ID]
+ 分类: [category_path]
+ 中文名: [title_cn](如有)
+ 标签: [tags](如有)
+
+示例:
1. Puma Men White 3/4 Length Pants
ID: 12345
- Category: Apparel > Bottomwear > Track Pants
- Color: White
- Gender: Men
- Season: Summer
- Usage: Sports
- Relevance: 95.2%
+ 分类: 服饰 > 裤装 > 运动裤
+ 中文名: 彪马男士白色九分运动裤
+ 标签: 运动,夏季,白色
-DO NOT skip the ID field! It is essential for displaying product images.
-Be conversational in your introduction, but preserve the exact product format."""
+不可省略 ID 字段!它是展示商品图片的关键。
+介绍要口语化,但必须保持上述商品格式。"""
def agent_node(state: AgentState):
"""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.""
image_path: Optional path to uploaded image
Returns:
- Dict with response and metadata
+ Dict with response and metadata, including:
+ - tool_calls: list of tool calls with args and (truncated) results
+ - debug_steps: detailed intermediate reasoning & tool execution steps
"""
try:
logger.info(
@@ -191,32 +189,74 @@ Be conversational in your introduction, but preserve the exact product format.""
"current_image_path": image_path,
}
- # Track tool calls
+ # Track tool calls (high-level) and detailed debug steps
tool_calls = []
+ debug_steps = []
- # Stream events to capture tool calls
+ # Stream events to capture tool calls and intermediate reasoning
for event in self.graph.stream(input_state, config=config):
logger.info(f"Event: {event}")
-
- # Check for agent node (tool calls)
+
+ # Agent node: LLM reasoning & tool decisions
if "agent" in event:
agent_output = event["agent"]
- if "messages" in agent_output:
- for msg in agent_output["messages"]:
- if hasattr(msg, "tool_calls") and msg.tool_calls:
- for tc in msg.tool_calls:
- tool_calls.append({
- "name": tc["name"],
- "args": tc.get("args", {}),
- })
-
- # Check for tool node (tool results)
+ messages = agent_output.get("messages", [])
+
+ step_messages = []
+ step_tool_calls = []
+
+ for msg in messages:
+ msg_text = _extract_message_text(msg)
+ msg_entry = {
+ "type": getattr(msg, "type", "assistant"),
+ "content": msg_text[:500], # truncate for safety
+ }
+ step_messages.append(msg_entry)
+
+ # Capture tool calls from this agent message
+ if hasattr(msg, "tool_calls") and msg.tool_calls:
+ for tc in msg.tool_calls:
+ tc_entry = {
+ "name": tc.get("name"),
+ "args": tc.get("args", {}),
+ }
+ tool_calls.append(tc_entry)
+ step_tool_calls.append(tc_entry)
+
+ debug_steps.append(
+ {
+ "node": "agent",
+ "messages": step_messages,
+ "tool_calls": step_tool_calls,
+ }
+ )
+
+ # Tool node: actual tool execution results
if "tools" in event:
tools_output = event["tools"]
- if "messages" in tools_output:
- for i, msg in enumerate(tools_output["messages"]):
- if i < len(tool_calls):
- tool_calls[i]["result"] = str(msg.content)[:200] + "..."
+ messages = tools_output.get("messages", [])
+
+ step_tool_results = []
+
+ for i, msg in enumerate(messages):
+ content_text = _extract_message_text(msg)
+ result_preview = content_text[:500] + ("..." if len(content_text) > 500 else "")
+
+ if i < len(tool_calls):
+ tool_calls[i]["result"] = result_preview
+
+ step_tool_results.append(
+ {
+ "content": result_preview,
+ }
+ )
+
+ debug_steps.append(
+ {
+ "node": "tools",
+ "results": step_tool_results,
+ }
+ )
# Get final state
final_state = self.graph.get_state(config)
@@ -228,6 +268,7 @@ Be conversational in your introduction, but preserve the exact product format.""
return {
"response": response_text,
"tool_calls": tool_calls,
+ "debug_steps": debug_steps,
"error": False,
}
diff --git a/cli_debug.py b/cli_debug.py
new file mode 100644
index 0000000..705dac0
--- /dev/null
+++ b/cli_debug.py
@@ -0,0 +1,89 @@
+import json
+from typing import Optional
+
+from app.agents.shopping_agent import ShoppingAgent
+
+
+def run_once(agent: ShoppingAgent, query: str, image_path: Optional[str] = None) -> None:
+ """Run a single query through the agent and pretty-print details."""
+ result = agent.chat(query=query, image_path=image_path)
+
+ print("\n=== Assistant Response ===")
+ print(result.get("response", ""))
+
+ print("\n=== Tool Calls ===")
+ tool_calls = result.get("tool_calls", []) or []
+ if not tool_calls:
+ print("(no tool calls)")
+ else:
+ for i, tc in enumerate(tool_calls, 1):
+ print(f"[{i}] {tc.get('name')}")
+ print(" args:")
+ print(" " + json.dumps(tc.get("args", {}), ensure_ascii=False, indent=2).replace("\n", "\n "))
+ if "result" in tc:
+ print(" result (truncated):")
+ print(" " + str(tc.get("result")).replace("\n", "\n "))
+
+ print("\n=== Debug Steps ===")
+ debug_steps = result.get("debug_steps", []) or []
+ if not debug_steps:
+ print("(no debug steps)")
+ else:
+ for idx, step in enumerate(debug_steps, 1):
+ node = step.get("node", "unknown")
+ print(f"\n--- Step {idx} [{node}] ---")
+
+ if node == "agent":
+ msgs = step.get("messages", []) or []
+ if msgs:
+ print(" Agent messages:")
+ for m in msgs:
+ role = m.get("type", "assistant")
+ content = m.get("content", "")
+ print(f" - {role}: {content}")
+
+ tcs = step.get("tool_calls", []) or []
+ if tcs:
+ print(" Planned tool calls:")
+ for j, tc in enumerate(tcs, 1):
+ print(f" [{j}] {tc.get('name')}")
+ print(
+ " args: "
+ + json.dumps(tc.get("args", {}), ensure_ascii=False)
+ )
+
+ elif node == "tools":
+ results = step.get("results", []) or []
+ if results:
+ print(" Tool results:")
+ for j, r in enumerate(results, 1):
+ content = r.get("content", "")
+ print(f" [{j}] {content}")
+
+
+def main() -> None:
+ """Simple CLI debugger to inspect agent reasoning and tool usage."""
+ agent = ShoppingAgent(session_id="cli-debug")
+ print("ShopAgent CLI Debugger")
+ print("输入你的问题,或者输入 `exit` 退出。\n")
+
+ while True:
+ try:
+ query = input("你:").strip()
+ except (EOFError, KeyboardInterrupt):
+ print("\n再见 👋")
+ break
+
+ if not query:
+ continue
+ if query.lower() in {"exit", "quit"}:
+ print("再见 👋")
+ break
+
+ run_once(agent, query=query)
+ print("\n" + "=" * 60 + "\n")
+
+
+if __name__ == "__main__":
+ main()
+
--
libgit2 0.21.2