修 reload 后 workflow 不更新问题

前面已经完成从Django上传论文的PDF并且重建知识向量数据库 ,但是这个功能仍存问题,因为在FastAPI中,重建工作是重新赋值了全局变量rag,而没有重建workflow,我的工作流在工具选择节点和工具执行节点是用到工厂函数创建的,这个节点中是有rag变量作为依赖的,所以如果我重建了rag,但是没有更新workflow,那么实现的工作流在后续问答环节可能无法回复与新上传论文相关的问题,所以这里需要修复。所以对FastAPI中app/main.py/reload_kb做一个小调整:

@app.post("/reload_kb")
def reload_kb():
    global rag, workflow # 重建工作流

    logger.info("Reloading knowledge base...")

    docs = load_pdfs(DATA_DIR)
    logger.info(f"docs数量: {len(docs)}")

    chunks = process_documents(docs)
    logger.info(f"chunks数量: {len(chunks)}")

    rag = RAGSystem(chunks)
    rag.build_index()

    workflow = AgentWorkflow(TOOLS, rag=rag) # 在新的工作流流中传入重建的rag

    logger.info("Knowledge base and AgentWorkflow reloaded successfully.")

    return {
        "status": "success",
        "message": f"Knowledge base reloaded. Total chunks: {len(chunks)}",
        "total_docs": len(docs),
        "total_chunks": len(chunks),
    }

补 Agent Trace 最小展示

补充Agent Trace的目的在于让系统返回的结果不是黑箱操作,让用户可以看到Agent系统是经过什么样的工作流,完成了任务,但是这次的实现先根据系统的情况,写固定的工作流,然后写出展示界面,当后面给workflow增加了条件边以后,再让系统根据走过的节点,动态返回Trace。现在先修改FastAPI的/ask,讲原有的:

answer = result.get("final_answer", "")
chunks = result.get("retrieved_chunks", [])
...
return {
    "session_id": req.session_id,
    "question": req.question,
    "answer": answer,
    "chunks": chunks,
}

修改为下面的样子,这样系统FastAPI返回的结果将会包含workflow:

answer = result.get("final_answer", "")
chunks = result.get("retrieved_chunks", [])

decision = result.get("decision", {})
tool_result = result.get("tool_result", {})

agent_trace = {
    "route_decision": decision,
    "tool_used": tool_result.get("tool_name", ""),
    "tool_input": tool_result.get("tool_input", ""),
    "workflow": [
        "choose_tool",
        "execute_tool",
        "generate_answer"
    ]
}
...
return {
    "session_id": req.session_id,
    "question": req.question,
    "answer": answer,
    "chunks": chunks,
    "agent_trace": agent_trace,
}

完善FastAPI后端服务之后,在Django层做适当的调整以接住后段传来的workflow将,调整django_shell/chat/views.py中的chat_home将:

error = None
chunks = []
current_session_id = request.GET.get("session_id", "demo-session").strip()
...
result = ask_ai(session_id=current_session_id, question=question)
chunks = result.get("chunks", [])
...
return render(
    request,
    "chat/chat_home.html",
    {
        "error": error,
        "current_session_id": current_session_id,
        "messages": messages,
        "recent_sessions": recent_sessions,
        "chunks": chunks,
    }
)

改为:

error = None
chunks = []
agent_trace = None
current_session_id = request.GET.get("session_id", "demo-session").strip()
...
result = ask_ai(session_id=current_session_id, question=question)
chunks = result.get("chunks", [])
agent_trace = result.get("agent_trace")
...
return render(
    request,
    "chat/chat_home.html",
    {
        "error": error,
        "current_session_id": current_session_id,
        "messages": messages,
        "recent_sessions": recent_sessions,
        "chunks": chunks,
        "agent_trace": agent_trace,
    }
)

随后在django_shell/templates/chat/chat_home.html模版层添加agent_trace卡片:

{% if agent_trace %}
<div class="card">
    <h3>Agent Trace</h3>

    <p>
        <strong>Tool Used:</strong>
        {{ agent_trace.tool_used }}
    </p>

    <p>
        <strong>Tool Input:</strong>
        {{ agent_trace.tool_input }}
    </p>

    <p>
        <strong>Workflow:</strong>
        {% for step in agent_trace.workflow %}
            {{ step }}{% if not forloop.last %} → {% endif %}
        {% endfor %}
    </p>

    <p>
        <strong>Route Decision:</strong>
        {{ agent_trace.route_decision }}
    </p>
</div>
{% endif %}

页面展示再整理一轮

前面的页面有点“毛坯”,看着不太好好看,这一节给页面增加一些CSS美化。在原有的卡片上增加一些类标签。

{% if agent_trace %}
<div class="card trace-card">
    <h3>Agent Trace</h3>
    <p class="section-desc">展示本轮问答中 LangGraph Agent 的工具选择和执行路径。</p>

    <div class="trace-grid">
        <div class="trace-item">
            <span class="trace-label">Tool Used</span>
            <strong>{{ agent_trace.tool_used }}</strong>
        </div>

        <div class="trace-item">
            <span class="trace-label">Tool Input</span>
            <strong>{{ agent_trace.tool_input }}</strong>
        </div>

        <div class="trace-item">
            <span class="trace-label">Workflow</span>
            <strong>
                {% for step in agent_trace.workflow %}
                    {{ step }}{% if not forloop.last %} → {% endif %}
                {% endfor %}
            </strong>
        </div>
    </div>

    <details>
        <summary>Route Decision Raw Data</summary>
        <pre>{{ agent_trace.route_decision }}</pre>
    </details>
</div>
{% endif %}

随后添加CSS美化:

.section-desc {
    color: #666;
    font-size: 14px;
    margin-top: -6px;
    margin-bottom: 14px;
}

.trace-card {
    border-left: 4px solid #2563eb;
}

.trace-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
}

.trace-item {
    background: #f3f4f6;
    border-radius: 10px;
    padding: 12px;
}

.trace-label {
    display: block;
    color: #6b7280;
    font-size: 12px;
    margin-bottom: 4px;
}

pre {
    background: #111827;
    color: #f9fafb;
    padding: 12px;
    border-radius: 8px;
    overflow-x: auto;
}

同样的检索检索Top-K也增加相应的美化:

{% if chunks %}
<div class="card">
    <h3>Retrieved Context (Top-K)</h3>
    <p class="section-desc">展示 RAG 检索阶段命中的论文片段,用于说明回答依据。</p>

    {% for c in chunks %}
        <div class="chunk-card">
            <div class="chunk-source">{{ c.source }}</div>
            <div class="chunk-text">{{ c.text }}</div>
        </div>
    {% endfor %}
</div>
{% endif %}

美化:

.chunk-card {
    background: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    padding: 12px;
    margin-bottom: 12px;
}

.chunk-source {
    font-weight: bold;
    color: #2563eb;
    margin-bottom: 6px;
}

.chunk-text {
    color: #374151;
    line-height: 1.6;
    white-space: pre-wrap;
}

upload.html页面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Upload Papers</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 960px;
            margin: 24px auto;
            padding: 0 16px;
            background: #f7f8fa;
        }

        .nav {
            margin-bottom: 20px;
        }

        .nav a {
            margin-right: 16px;
            text-decoration: none;
            color: #2563eb;
        }

        .card {
            background: white;
            border-radius: 12px;
            padding: 16px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
            margin-bottom: 20px;
        }

        button {
            background: #2563eb;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 8px;
            cursor: pointer;
        }

        .message {
            color: #047857;
            font-weight: bold;
        }

        .error {
            color: #b91c1c;
            font-weight: bold;
        }

        .file-item {
            padding: 8px 0;
            border-bottom: 1px solid #e5e7eb;
        }

        .desc {
            color: #666;
            line-height: 1.6;
        }
    </style>
</head>
<body>
    <h1>Upload Papers</h1>
    <p class="desc">
        上传 PDF 后,Django 会将文件保存到本地 data 目录,并调用 FastAPI 的 /reload_kb 接口重建知识库。
    </p>

    <div class="nav">
        <a href="{% url 'chat_home' %}">Chat Home</a>
        <a href="{% url 'session_list' %}">Session History</a>
        <a href="{% url 'upload_page' %}">Upload Papers</a>
    </div>

    <div class="card">
        <h3>Upload New Paper</h3>

        {% if message %}
            <p class="message">{{ message }}</p>
        {% endif %}

        {% if error %}
            <p class="error">{{ error }}</p>
        {% endif %}

        <form method="post" enctype="multipart/form-data">
            {% csrf_token %}
            <input type="file" name="paper_file" accept=".pdf">
            <button type="submit">Upload and Reload Knowledge Base</button>
        </form>
    </div>

    <div class="card">
        <h3>Current Knowledge Base Files</h3>

        {% if files %}
            {% for f in files %}
                <div class="file-item">📄 {{ f }}</div>
            {% endfor %}
        {% else %}
            <p class="desc">暂无文件</p>
        {% endif %}
    </div>
</body>
</html>

README、架构图

1. 项目定位

一个面向 科研论文阅读、检索问答与辅助分析 的 AI 应用工程项目。

项目以 RAG 检索增强问答 为核心能力,使用 FastAPI 提供 AI 推理服务,使用 LangGraph 编排 Agent 工具调用流程,并通过 Django 构建产品壳层,实现论文上传、知识库更新、聊天问答、历史会话保存、检索片段展示和 Agent Trace 展示。

当前版本已经形成一个完整的最小产品闭环:

上传论文 -> 重建知识库 -> 发起问答 -> Agent 选择工具 -> RAG 检索回答 -> 展示检索片段 -> 保存历史会话

2.项目架构图

User

Django Product Shell

Chat Page

Upload Page

Session History

FastAPI AI Service

POST /reload_kb

AgentWorkflow

LangGraph StateGraph

choose_tool

execute_tool

generate_answer

Answer + Retrieved Chunks + Agent Trace

Tools Layer

RAG Tool

Calculator Tool

Time Tool

Web Search Tool

LLM Tool

RAGSystem

FAISS Vector Index

Rerank

Answer Generation

MCP Web Search

SQLite

ChatSession

ChatMessage

3.请求时序图

问答请求时序图

SQLite RAGSystem Tools Layer LangGraph StateGraph AgentWorkflow FastAPI AI Service Django Product Shell SQLite RAGSystem Tools Layer LangGraph StateGraph AgentWorkflow FastAPI AI Service Django Product Shell alt [Tool is rag] [Tool is calculator / time / web_search / llm] User Submit question POST /ask session_id + question Load short-term chat history invoke(session_id, query, chat_history) graph.invoke(state) choose_tool execute_tool call rag_tool ask_with_trace(query, chat_history) Retrieve related chunks Rerank chunks Generate answer with context answer + retrieved_chunks call selected tool tool output generate_answer final state result state answer + chunks + agent_trace Save ChatSession Save user message Save assistant message Render answer, Agent Trace, Retrieved Context User

上传论文与重建知识库时序图

AgentWorkflow RAGSystem Data Loader FastAPI AI Service data Directory Django Upload Page AgentWorkflow RAGSystem Data Loader FastAPI AI Service data Directory Django Upload Page User Upload PDF Save PDF to data/ POST /reload_kb load_pdfs(DATA_DIR) documents process_documents(documents) chunks Create new RAGSystem(chunks) Build embeddings Build FAISS index Rebuild AgentWorkflow(TOOLS, rag) reload status + total_docs + total_chunks Show upload result and current knowledge base files User

如果这篇文章对你有帮助,可以点个赞~
完整代码地址:https://github.com/1186141415/Paper-RAG-Agent-with-LangGraph

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐