Files
socweb/backend/app/tools/kb.py
T
gongzhiyongandClaude Sonnet 4.6 47921d1a45 fix: resolve 6 gen-ui issues — timeline completeness, regen sync, localStorage persistence
## Fixes
- [#3] Status events now add/update a status ActivityNode in timeline (upsertWsStatusNode)
- [#4] Done callback adds done node; error callback adds error node + sets session.status="error"
- [#5] handleRegenerate fully synced with workspace: status/tool/card/done/error all handled
- [#1][#2] localStorage persistence: completed/error sessions auto-saved, lazy-loaded on demand
- [#1] handleSelectConversation preloads workspace sessions from localStorage for history messages
- Refactored completeWsSession to include done ActivityNode in timeline
- Added errorWsSession, upsertWsStatusNode, saveWsToStorage, loadWsFromStorage helpers

## Known limitation
- [#7] workspace_card merge:true not yet used by backend (all cards are append-only for now)
- History workspace recovery depends on localStorage (browser-local, not cross-device)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:08:23 +08:00

80 lines
2.6 KiB
Python

"""Knowledge base search tool — calls KB Agent (Azure AI Search).
Includes a single retry on ReadTimeout to handle Azure App Service cold
starts (can take 20-30s). All exceptions are caught so the tool returns
a user-friendly message instead of crashing the ReAct agent loop.
"""
from __future__ import annotations
import asyncio
import logging
import httpx
from langchain_core.tools import tool
from app.config import settings
logger = logging.getLogger(__name__)
@tool
async def kb_search(query: str) -> str:
"""Search the internal knowledge base for documents related to a query.
Use this tool when the user asks about internal products, technical
documentation, project plans, or anything that might be covered by
the company knowledge base.
Args:
query: The search query in natural language.
"""
url = f"{settings.kb_agent_url}{settings.kb_agent_search_path}"
headers = {
"Content-Type": "application/json",
"api-key": settings.kb_agent_api_key,
}
payload = {
"query": query,
"top": 5,
"search_mode": "hybrid",
}
for attempt in range(2):
try:
async with httpx.AsyncClient(timeout=settings.kb_agent_search_timeout_sec) as client:
resp = await client.post(url, json=payload, headers=headers)
resp.raise_for_status()
data = resp.json()
results = data.get("results", [])
if not results:
return "知识库中未找到相关内容。"
parts: list[str] = []
for r in results:
title = r.get("title", "Untitled")
content = r.get("content", "")
category = r.get("category", "")
# Truncate very long content to keep context manageable
if len(content) > 1500:
content = content[:1500] + "..."
header = f"[{title}]"
if category:
header += f" ({category})"
parts.append(f"{header}\n{content}")
return "\n\n---\n\n".join(parts)
except httpx.ReadTimeout:
if attempt == 0:
logger.warning("KB Agent read timeout (attempt 1), retrying after 2s...")
await asyncio.sleep(2)
continue
logger.error("KB Agent read timeout after retry")
return "知识库检索超时,请稍后重试。"
except Exception as exc:
logger.error("KB Agent search failed: %s", exc, exc_info=True)
return f"知识库检索出错:{exc}"