Download api/main.py from Maggei/finsight: direct link, hf CLI and curl.
- Browser
- Download file 4.05 kB
-
https://huggingface.co/spaces/Maggei/finsight/resolve/main/api/main.py
- Command line
-
hf download hf://spaces/Maggei/finsight/api/main.py
-
curl -L -o main.py https://huggingface.co/spaces/Maggei/finsight/resolve/main/api/main.py
4.05 kB
| """FinSight FastAPI 接口层。 | |
| 接口: | |
| GET /health 健康检查 + 知识库规模 | |
| POST /analyze 文档分析(走 LangGraph:router → 4Agent并行 → synthesis) | |
| POST /chat 问答(自动 RAG 混合检索;返回答案 + 来源) | |
| POST /chat/stream 问答流式输出(text/plain 逐块) | |
| POST /upload 上传文本入知识库 | |
| 所有请求统一经 LangGraph 状态机执行(router 决定路径)。同步图调用由 FastAPI | |
| 自动放到线程池,天然支持并发。换微调/推理服务只动 agents/llm.py,本层不变。 | |
| 启动:.venv/bin/uvicorn api.main:app --port 8000 | |
| """ | |
| from fastapi import FastAPI, UploadFile | |
| from fastapi.responses import StreamingResponse | |
| from pydantic import BaseModel | |
| from agents.analysts import QA | |
| from agents.llm import chat_stream | |
| from graph import analyze | |
| from graph.workflow import get_retriever, refresh_retriever | |
| from rag import KnowledgeBase, read_pdf | |
| app = FastAPI(title="FinSight", description="金融研报多Agent分析系统", version="0.1") | |
| class AnalyzeReq(BaseModel): | |
| text: str | |
| class ChatReq(BaseModel): | |
| question: str | |
| context: str | None = None # 显式给则跳过 RAG 检索 | |
| class UploadReq(BaseModel): | |
| doc_id: str | |
| text: str | |
| def health(): | |
| return {"status": "ok", "kb_chunks": KnowledgeBase().count(), | |
| "models": {"analysts": "finsight-qwen", "qa/router": "qwen2.5:7b-instruct"}} | |
| def analyze_doc(req: AnalyzeReq): | |
| """文档分析:返回 intent + 结构化报告(若路由判为问答则返回 answer)。""" | |
| r = analyze(req.text) | |
| out = {"intent": r["intent"], "router": r.get("router")} | |
| if r["intent"] == "doc_analysis": | |
| out["report"] = r["report"] | |
| else: | |
| out["answer"] = r["answer"].get("result") | |
| out["sources"] = r.get("sources", []) | |
| return out | |
| def chat_qa(req: ChatReq): | |
| """问答:自动 RAG 检索注入,返回答案 + 来源。""" | |
| r = analyze(req.question, context=req.context) | |
| if r["intent"] == "doc_analysis": # 用户把文档发进了问答口,如实回路由结果 | |
| return {"intent": "doc_analysis", "report": r["report"]} | |
| return {"intent": "qa", "answer": r["answer"].get("result"), | |
| "sources": r.get("sources", [])} | |
| def chat_stream_qa(req: ChatReq): | |
| """流式问答:先检索注入上下文,再逐块流式输出纯文本答案。""" | |
| ctx = req.context | |
| if ctx is None: | |
| try: | |
| ctx = get_retriever().context(req.question, k=4) or None | |
| except Exception: | |
| ctx = None | |
| system = ("你是专业的金融问答助手。基于问题(如提供【参考资料】则优先依据资料)" | |
| "给出准确、客观的回答,不编造数据,涉及投资建议时提示风险。") | |
| user = req.question if ctx is None else f"【参考资料】\n{ctx}\n\n【问题】\n{req.question}" | |
| return StreamingResponse( | |
| chat_stream(system, user, model=QA.model), | |
| media_type="text/plain; charset=utf-8", | |
| ) | |
| def upload(req: UploadReq): | |
| """把一段文本切块入库,并刷新检索器。""" | |
| kb = KnowledgeBase() | |
| n = kb.add(req.doc_id, req.text, meta={"source": "upload"}) | |
| refresh_retriever() | |
| return {"doc_id": req.doc_id, "chunks_added": n, "kb_total": kb.count()} | |
| async def upload_pdf(file: UploadFile): | |
| """上传研报 PDF 入库:解析文本层 → 切块 → 入库 → 刷新检索器。doc_id 取文件名。""" | |
| doc_id = (file.filename or "uploaded").rsplit(".", 1)[0] | |
| text = read_pdf(file.file) # 直接读上传流,不落盘 | |
| if not text.strip(): | |
| return {"doc_id": doc_id, "chunks_added": 0, | |
| "error": "无文本层(可能是扫描件),未入库"} | |
| kb = KnowledgeBase() | |
| n = kb.add(doc_id, text, meta={"source": "pdf", "file": file.filename}) | |
| refresh_retriever() | |
| return {"doc_id": doc_id, "chunks_added": n, "kb_total": kb.count()} | |