Tool call failures (httpx network errors) propagated as unhandled exceptions
through LangGraph TaskGroup, killing SSE stream mid-flight. Frontend received
neither tokens nor done event, leaving it stuck.
- except: sends {"type":"error"} SSE event so frontend shows error message
- finally: persists partial AI content and always sends {"type":"done"}
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.6 KiB
4.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Overview
so-c-chat-clone — 企业级 Gemini 风格对话系统,前后端分离。基于 LangGraph ReAct Agent 编排,支持知识库检索、工单查询、外部搜索、文档生成、沙盒执行。
Commands
Backend
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --port 8000 --reload # Dev server
Frontend (read-only unless explicitly authorized)
cd frontend
npm install && npm run dev # localhost:3000
npm run build
Deployment
# Manual deploy (Oryx builds dependencies on Azure)
az webapp up --name soc-backend --resource-group Operation --runtime "PYTHON:3.12"
# Or push to main branch — GitHub Actions auto-deploys via .github/workflows/deploy-backend.yml
git push origin main
Architecture
Request Flow
Frontend POST /api/chat/stream
→ api/chat.py: resolve_tools() → get_chat_graph() → astream_events()
→ graph/builder.py: create_react_agent (with tools) OR plain StateGraph (no tools)
→ tools/*.py: LangChain @tool functions call external services
→ SSE events: token / tool_start / tool_end / done
Graph Strategy (graph/builder.py)
- With tools:
create_react_agent(llm, tools, checkpointer)— ReAct pattern, LLM decides tool calls - Without tools: Simple
StateGraph(ChatState)with singlecall_modelnode - Graphs cached by
(model, frozenset(tool_names))to avoid re-compilation - Model presets:
flash(max_tokens=500, temp=0.2),pro(max_tokens=4096, temp=0.3)
Tool System (tools/__init__.py)
Frontend sends tool keys → resolve_tools() maps to LangChain @tool objects → bound to ReAct agent.
| Frontend Key | Tools | External Service |
|---|---|---|
"knowledge" |
kb_search |
KB Agent (Azure AI Search) |
"tickets" |
ticket_list, ticket_detail |
Gongdan API |
"search" |
web_search |
Jina Search/Reader/Rerank |
"document" |
generate_document |
Doc Creator Agent |
"sandbox" |
sandbox_run |
Daytona API |
Data Layer
- PostgreSQL (
store/postgres.py): SQLAlchemy async ORM —ConversationandMessagemodels, auto-creates tables on startup - LangGraph Checkpointer (
store/memory.py):AsyncPostgresSavervia psycopg (separate connection from SQLAlchemy, usessslmode=requirenotssl=require) - Redis (
cache/redis.py): Search result caching, key=search:{hash}:{model}, TTL=300s, graceful degradation on failure - Azure Blob Storage (
storage/blob.py): File uploads/downloads for attachments and generated docs - Azure Service Bus (
tasks/bus.py): Async task dispatch for long-running operations
SSE Event Protocol
{"type": "token", "content": "..."} // Streamed text chunk
{"type": "tool_start", "tool": "kb_search"}
{"type": "tool_end", "tool": "kb_search"}
{"type": "done"} // End of stream
Lifespan (main.py)
Startup creates DB tables. Shutdown closes: checkpointer → Redis → Blob client → Service Bus → SQLAlchemy engine.
Configuration
All config via pydantic-settings in app/config.py, reads from .env file. Full credentials reference in EXTERNAL_SERVICES.md.
Key env vars: AZURE_OPENAI_*, DATABASE_URL, KB_AGENT_*, GONGDAN_*, JINA_API_KEY, REDIS_URL, DOC_AGENT_*, DAYTONA_*, AZURE_STORAGE_CONNECTION_STRING, AZURE_SERVICE_BUS_CONNECTION_STRING.
Deployment
- Azure Web App:
soc-backendinOperationresource group, Python 3.12, B1 Linux - Startup:
gunicorn -w 2 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --timeout 120 app.main:app - Critical setting:
WEBSITES_PORT=8000(Azure defaults to 8080) - CI/CD: GitHub Actions with OIDC auth (
azure/login@v2) → zip deploy, triggers onbackend/**changes to main - Always On: Enabled to avoid cold starts
Constraints
- Frontend is read-only unless explicitly authorized
- Azure resources:
OperationandAuthDataresource groups only — allazcommands must include--resource-group - Tool invocation policy: User-selected tools are passed to ReAct Agent as available. Agent decides whether to call them. If it decides not to, it must explain why.
- Search depth:
flash= quick (top 3, no Reader/Rerank),pro= deep (top 10, concurrent Reader, Rerank top 5)
Known Issues
GET /api/tickets/{id}returns 404 — upstream Gongdan API issue, not backend bugconfig.pyhas hardcoded DB/Redis passwords as defaults — should be empty strings (security risk if repo goes public)