feat(backends): wire LangGraph backend behind AGENT_BACKEND switch (assignment 2)

- src/backends/langgraph.py: LangGraphBackend implements ChatBackend;
  complete() runs one graph turn per session (thread_id=sessionId) and
  returns state-prefixed content; stream() emits the full state prefix
  before body fragments (full-answer slicing, not yet real incremental
  output - assignment 8)
- src/core/config.py: AGENT_BACKEND=fastgpt|langgraph, langgraph mode
  no longer requires FastGPT credentials at startup
- src/core/fastgpt_client.py: lifespan builds the backend by switch
- test/backends/test_langgraph_backend.py: adapter acceptance tests

Verified:
- pytest: 48 passed
- AGENT_BACKEND=langgraph uvicorn: POST /chat -> 1002;
  browser_addon -> browser_addon.1002; SSE = stage_code, text_delta,
  single done

Known limit: stream slices a complete answer; no formUpdate deltas yet
(assignment 6).
This commit is contained in:
Eric Wang
2026-10-19 14:29:31 +08:00
parent 5020903791
commit 5d68b731d4
4 changed files with 157 additions and 11 deletions
+12
View File
@@ -15,9 +15,21 @@ class Config:
FASTGPT_BASE_URL = os.getenv("ANALYSIS_SERVICE_URL")
FASTGPT_APP_ID = os.getenv("APP_ID")
# Chat backend switch(作业 2)
# "fastgpt" 保持现有行为;"langgraph" 使用内存图 fake 回复。
AGENT_BACKEND = os.getenv("AGENT_BACKEND", "fastgpt")
@classmethod
def validate(cls):
"""Validate required configuration."""
# TODO(作业 2): 语音管线仍依赖 FastGPT。当前仅按文本后端开关校验;
# 接入语音开关后,需要单独校验语音路径所需配置。
if cls.AGENT_BACKEND not in ("fastgpt", "langgraph"):
raise ValueError(
f"AGENT_BACKEND must be 'fastgpt' or 'langgraph', got {cls.AGENT_BACKEND!r}"
)
if cls.AGENT_BACKEND != "fastgpt":
return
required = [
("FASTGPT_BASE_URL (ANALYSIS_SERVICE_URL)", cls.FASTGPT_BASE_URL),
("FASTGPT_API_KEY (ANALYSIS_AUTH_TOKEN)", cls.FASTGPT_API_KEY),