c.py 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import json
  2. from fastapi import FastAPI
  3. from sse_starlette.sse import EventSourceResponse
  4. from langgraph.graph import StateGraph, MessagesState, START, END
  5. from langchain_core.messages import HumanMessage
  6. from backend.core.llm_factory import get_llm
  7. from typing_extensions import TypedDict
  8. from langgraph.graph.message import add_messages
  9. from langchain_core.messages import BaseMessage
  10. from typing import Annotated, Optional
  11. # ========== 2. 构建最简 LangGraph(只有 1 个节点) ==========
  12. async def call_model(state: MessagesState, config):
  13. """节点函数:直接调用 LLM"""
  14. model = get_llm(agent_type="qa",streaming=True)
  15. response = await model.ainvoke(state["messages"])
  16. return {"messages": [response]} # 返回追加消息
  17. class MessagesState(TypedDict):
  18. """
  19. 智能问答 Agent 的完整状态定义。
  20. 所有节点通过读写此 State 进行数据传递。
  21. """
  22. # ── ① 消息历史(LangGraph 核心,add_messages reducer)────────
  23. # 节点每次返回 messages 时自动追加,不覆盖历史
  24. messages: Annotated[list[BaseMessage], add_messages]
  25. # 构建图:START -> call_model -> END
  26. graph_builder = StateGraph(MessagesState)
  27. graph_builder.add_node("call_model", call_model)
  28. graph_builder.add_edge(START, "call_model")
  29. graph_builder.add_edge("call_model", END)
  30. graph = graph_builder.compile()
  31. # ========== 3. FastAPI 应用 ==========
  32. app_web = FastAPI()
  33. @app_web.post("/demo/stream")
  34. async def stream_chat(message: str):
  35. """流式接口:接收 message,推送 SSE 事件"""
  36. async def event_generator():
  37. # 初始状态:只放用户输入
  38. initial_state = {"messages": [HumanMessage(content=message)]}
  39. try:
  40. # ⭐ 核心:astream_events 把图执行过程变成事件流
  41. async for event in graph.astream_events(initial_state, version="v2"):
  42. print(event)
  43. print("*"*80)
  44. evt = event["event"]
  45. node = event.get("metadata", {}).get("langgraph_node", "")
  46. # --- 事件1: 节点开始(模拟“思考中”) ---
  47. if evt == "on_chain_start" and node == "call_model":
  48. yield {
  49. "data": json.dumps({"type": "status", "msg": "🤔 AI 正在思考..."}, ensure_ascii=False)
  50. }
  51. # --- 事件2: LLM 逐字吐出 token(最核心) ---
  52. elif evt == "on_chat_model_stream" and node == "call_model":
  53. chunk = event["data"].get("chunk")
  54. if chunk and chunk.content:
  55. yield {
  56. "data": json.dumps({"type": "token", "content": chunk.content},ensure_ascii=False)
  57. }
  58. # --- 事件3: 节点结束(捕获完整回复) ---
  59. elif evt == "on_chain_end" and node == "call_model":
  60. output = event["data"].get("output", {})
  61. # 这里 output 就是 AIMessage 对象,取出完整内容备用
  62. full_answer = output.get("messages", [])[-1].content if output.get("messages") else ""
  63. yield {
  64. "data": json.dumps({"type": "meta", "full_answer": full_answer},ensure_ascii=False)
  65. }
  66. except Exception as e:
  67. yield {
  68. "data": json.dumps({"type": "error", "message": str(e)},ensure_ascii=False)
  69. }
  70. return
  71. # --- 事件4: 结束信号 ---
  72. yield {"data": json.dumps({"type": "done"},ensure_ascii=False)}
  73. return EventSourceResponse(event_generator())
  74. # ========== 4. 启动服务 ==========
  75. if __name__ == "__main__":
  76. import uvicorn
  77. uvicorn.run(app_web, host="0.0.0.0", port=8000)