algorithm-agent / report /project_report.tex
fanjingbo111's picture
Deploy algorithm agent app
ae0a268 verified
Raw
History Blame Contribute Delete
16.9 kB
\documentclass[11pt,fontset=none]{ctexart}
\usepackage[a4paper,margin=2.35cm]{geometry}
\usepackage{array}
\usepackage{amsmath}
\usepackage{booktabs}
\usepackage{enumitem}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{tikz}
\usepackage{xcolor}
\usepackage{listings}
\usetikzlibrary{arrows.meta,positioning,shapes.geometric}
\setCJKmainfont{Songti SC}
\setCJKsansfont{Songti SC}
\setCJKmonofont{Songti SC}
\hypersetup{colorlinks=true,linkcolor=blue,urlcolor=blue}
\emergencystretch=3em
\setlist[itemize]{leftmargin=2em}
\setlist[enumerate]{leftmargin=2em}
\lstset{
basicstyle=\ttfamily\small,
breaklines=true,
frame=single,
columns=fullflexible
}
\title{通用算法问题求解 Agent 系统设计与运行报告}
\author{高级算法设计与应用课程大作业}
\date{\today}
\begin{document}
\maketitle
\section{问题背景}
高级算法设计与应用课程中的算法题通常不是单纯的数值计算题,而是要求学生完成完整的算法设计过程:
理解自然语言题意,抽取输入、约束和目标,判断问题属于哪一类算法范式,给出算法步骤或伪代码,
证明正确性,分析复杂度,并在有样例时验证结果。传统大模型问答可以直接生成文字答案,但如果缺少
过程约束,容易出现三个问题:第一,模型可能跳过建模和算法选择理由;第二,模型生成的答案难以复查
中间依据;第三,样例计算结果可能没有经过工具验证。
因此,本项目设计了一个面向通用算法问题的可解释 Agent 系统。系统不为某一个题型单独写死求解器,
而是把一次求解拆成多个可观察模块:会话记忆、任务规划、算法范式检索、公开大模型执行、轻量脚本
验证、答案评估、循环控制、反思整理和报告生成。用户在网页中输入任意算法问题后,前端会以对话方式
展示每个模块的输出,并在最后生成 PDF 和 LaTeX 源码。系统使用公开可访问的 OpenAI-compatible API
接入 Agnes AI 的 \texttt{agnes-2.0-flash} 模型,不依赖内部未公开模型。
本系统的课程价值在于:它不是只给出最终答案,而是展示“算法问题如何被优化为可求解任务”的全过程。
对于课程大作业,网页可用于公开演示,PDF 可作为一次求解轨迹的归档,MP4 录屏可说明系统设计和运行
效果。
\section{Agent 系统总体设计}
系统由前端、后端、Agent 核心和报告生成四部分组成。前端采用自定义 HTML/CSS/JavaScript,实现类似
对话式应用的界面:左侧是历史记录,右侧顶部显示模型和配置状态,中间滚动展示 Agent 工作流,底部
固定输入框。后端使用 FastAPI 提供静态页面、状态接口、SSE 流式运行接口和报告下载接口。Agent 核心
\texttt{agent\_core.py} 实现,工具层由 \texttt{tools.py} 实现,报告由 \texttt{reporting.py}
写出 LaTeX 并编译为 PDF。
\begin{table}[htbp]
\centering
\caption{系统主要文件与职责}
\label{tab:files}
\begin{tabular}{p{4cm}p{9cm}}
\toprule
文件 & 职责 \\
\midrule
\texttt{server.py} & FastAPI 服务入口,读取 \texttt{AGNES\_API\_KEY},提供页面、SSE 运行流和报告下载。 \\
\texttt{agent\_core.py} & 定义 Agent 状态和 Memory、Planner、Retriever、Executor 等模块。 \\
\texttt{tools.py} & 提供算法范式知识检索、Python 代码提取、安全脚本运行和离线降级框架。 \\
\texttt{reporting.py} & 根据一次求解轨迹生成 LaTeX 源码和 PDF 报告。 \\
\texttt{static/index.html} & 前端页面结构。 \\
\texttt{static/app.js} & SSE 接收、对话渲染、Markdown 与 LaTeX 公式渲染、历史记录管理。 \\
\texttt{static/styles.css} & 页面布局和视觉样式。 \\
\bottomrule
\end{tabular}
\end{table}
系统运行时,前端不会让用户填写模型名、接口地址或 API Key。模型固定为 \texttt{agnes-2.0-flash},
接口固定为 \texttt{https://apihub.agnes-ai.com/v1/chat/completions},API Key 从后端环境变量或
部署平台 Secrets 中读取。这样老师访问公开页面时可以直接使用,不需要在网页上手动上传或输入 key。
\section{Agent Flow}
本系统的核心是一个可观察的 Agent Flow。一次问题求解不是由单次模型调用直接完成,而是按模块推进:
Memory 记录输入,Planner 结构化问题,Retriever 检索算法范式和工具说明,Executor 调用公开 LLM
生成解答和可选验证脚本,Script Runner 运行轻量脚本验证样例,Evaluator 检查答案质量,
Loop Controller 判断是否需要修订,Reflector 形成最终回答,Artifact Writer 生成报告产物。
\begin{center}
\begin{tikzpicture}[
node distance=7mm,
box/.style={draw, rounded corners, align=center, minimum width=38mm, minimum height=8mm, fill=blue!8},
side/.style={draw, rounded corners, align=center, minimum width=39mm, minimum height=8mm, fill=orange!15},
outbox/.style={draw, rounded corners, align=center, minimum width=38mm, minimum height=8mm, fill=green!12},
decision/.style={draw, diamond, aspect=2.25, align=center, inner sep=1mm, fill=orange!8},
arrow/.style={-Latex, thick}
]
\node[box] (question) {User Question\\用户问题};
\node[box, below=of question] (memory) {Memory\\会话状态};
\node[box, below=of memory] (planner) {Planner\\结构化规划};
\node[box, below=of planner] (retriever) {Retriever\\范式检索};
\node[box, below=of retriever] (executor) {Executor\\LLM 执行};
\node[box, below=of executor] (script) {Script Runner\\脚本验证};
\node[box, below=of script] (evaluator) {Evaluator\\质量评估};
\node[decision, below=8mm of evaluator] (need) {Needs\\Improvement?};
\node[box, below=9mm of need] (reflector) {Reflector\\反思整理};
\node[outbox, below left=8mm and 18mm of reflector] (writer) {Artifact Writer\\报告生成};
\node[outbox, below=of writer] (final) {Final Answer\\最终答案};
\node[side, right=21mm of need] (loop) {Loop Controller\\循环控制};
\node[side, below=of loop] (supp) {Supplemental Retrieval\\补充检索};
\draw[arrow] (question) -- (memory);
\draw[arrow] (memory) -- (planner);
\draw[arrow] (planner) -- (retriever);
\draw[arrow] (retriever) -- (executor);
\draw[arrow] (executor) -- (script);
\draw[arrow] (script) -- (evaluator);
\draw[arrow] (evaluator) -- (need);
\draw[arrow] (need) -- node[right, font=\small] {No} (reflector);
\draw[arrow] (reflector) -- (writer);
\draw[arrow] (writer) -- (final);
\draw[arrow] (need) -- node[above, font=\small] {Yes} (loop);
\draw[arrow] (loop) -- (supp);
\draw[arrow] (supp.west) -- ++(-14mm,0) |- (executor.east);
\end{tikzpicture}
\end{center}
该流程强调两点。第一,中间结果全部可见。前端通过 SSE 接收后端事件,每完成一个模块就把模块名称、
时间和输出内容追加到工作流面板。第二,工具调用是通用能力,而不是固定题解。Retriever 检索的是
课程算法范式知识,包括递归与分治、动态规划、贪心、回溯、分支限界、随机化算法、遗传算法、模拟退火、
图算法以及复杂度与正确性证明;Script Runner 运行的是 LLM 生成的轻量 Python 验证脚本,而不是为某个
题型预置的专用求解器。
\section{模块说明}
\subsection{Memory}
Memory 负责建立本轮求解状态。状态对象保存原始问题、模型配置、中间模块输出、验证脚本结果、评估
结果、最终答案和报告路径。由于系统不使用上传文件,也不做课程资料 RAG,Memory 只记录本轮对话和
Agent 运行轨迹。
\subsection{Planner}
Planner 将自然语言算法题转化为结构化任务。它会要求模型输出 JSON 形式的问题类型、输入变量、约束、
优化目标或证明目标、候选算法范式、是否需要脚本验证以及不超过五步的执行计划。如果公开 LLM 调用失败,
Planner 会基于本地算法范式检索结果生成保底计划,保证前端仍能展示完整流程。
\subsection{Retriever}
Retriever 使用内置算法范式库完成轻量检索。检索逻辑不是上传 PDF 后做向量召回,而是根据问题文本中的
关键词和算法语义匹配课程常见范式。例如,出现“背包、最优、序列”会提高动态规划得分;出现“节点、边、
路径”会提高图算法得分;出现“温度、邻域、接受概率”会提高模拟退火得分。Retriever 输出的内容会被
Executor 用作求解提示,也会展示在前端作为可解释依据。
\subsection{Executor}
Executor 调用公开 LLM 生成算法解答。提示词要求它必须包含问题建模、算法选择理由、核心步骤或伪代码、
正确性说明、复杂度分析;若题目有具体样例,还要求生成一个不读写文件、不导入外部包的 Python 验证脚本。
这一步体现了提示词工程的作用:把“直接回答问题”约束为“按算法设计规范回答问题”。
\subsection{Script Runner}
Script Runner 从 Executor 的回答中提取最后一个 Python 代码块,并在受限环境中执行。它会禁止
\texttt{import}、\texttt{open}、\texttt{eval}、\texttt{exec} 等危险结构,只允许基础内置函数和
\texttt{math}。脚本运行结果会作为工具调用证据返回给 Evaluator 和前端。若 Executor 没有生成脚本,
系统也会明确显示未运行脚本的原因。
\subsection{Evaluator 与 Loop Controller}
Evaluator 根据原始问题、初稿答案和脚本结果判断答案是否合格。检查标准包括:是否回应原问题,是否
说明算法选择,是否给出步骤或伪代码,是否包含正确性说明和复杂度分析,是否合理利用脚本结果。
Loop Controller 根据评估结论决定流程走向:如果答案不足,则要求 Reflector 按评估意见修订;如果
答案合格,则直接进入最终整理。
\subsection{Reflector 与 Artifact Writer}
Reflector 将规划结果、检索依据、执行结果和评估意见整理成最终答案。Artifact Writer 把完整轨迹写入
LaTeX,并生成 PDF 报告。这样一次求解既有网页上的实时过程,也有可提交、可复查的文档产物。
\section{前端运行界面}
\ref{fig:app} 展示了系统在样例问题运行过程中的网页界面。页面左侧是历史记录,右侧顶部展示固定
模型和 API 状态,中部正在流式展示 Agent 工作流,底部是固定输入框。截图中可以看到用户问题已经进入
对话区,工作流面板正在展示 Agent 启动、Memory 和 Planner 等模块输出。系统支持 Markdown 和 LaTeX
公式渲染,适合展示算法伪代码、复杂度表达式和数学推导。
\begin{figure}[htbp]
\centering
\includegraphics[width=\textwidth]{../screenshots/app_workflow_running.png}
\caption{通用算法问题求解 Agent 的样例运行过程截图}
\label{fig:app}
\end{figure}
\section{样例问题求解过程}
本节使用如下样例问题说明 Agent 如何完成一次完整求解:
\begin{lstlisting}
给定一个有向带权图,节点为 A, B, C, D, E。
边为 A->B:4, A->C:2, B->C:1, B->D:5, C->D:8, C->E:10, D->E:2。
请从 A 到 E 找到最短路径,并说明应该使用什么算法。
\end{lstlisting}
\subsection{Memory 输出}
Memory 记录用户原始问题,并初始化本轮任务状态。本轮问题包含明确的节点、边、边权、起点 A、终点 E
和求解目标,因此不需要向用户追问额外输入。
\subsection{Planner 输出}
Planner 将问题结构化为:
\begin{lstlisting}
problem_type: 有向带权图单源到单点最短路径
inputs:
nodes = [A, B, C, D, E]
edges = [(A,B,4), (A,C,2), (B,C,1), (B,D,5),
(C,D,8), (C,E,10), (D,E,2)]
constraints:
graph is directed
all edge weights are non-negative
objective:
minimize total path weight from A to E
output:
shortest distance, shortest path, algorithm choice and reason
\end{lstlisting}
Planner 的执行计划为:先确认图模型和权重条件,再选择最短路径算法,然后计算距离和前驱,最后给出
路径、复杂度和正确性说明。
\subsection{Retriever 输出}
Retriever 检索到的主要算法范式为“图算法”和“复杂度与正确性证明”。由于题目包含“节点、边、路径、
有向带权图”等关键词,系统判断这是图算法问题。Retriever 同时提醒需要回答以下关键问题:图是否有向、
是否带权、目标是最短路径还是连通性、边权是否允许负数。根据 Planner 抽取的权重集合
\(\{4,2,1,5,8,10,2\}\),所有边权均为非负,因此适合使用 Dijkstra 算法。如果存在负权边,则应考虑
Bellman-Ford;本例不需要。
\subsection{Executor 与 Script Runner 输出}
Executor 生成的求解思路如下。设 \(\text{dist}[v]\) 表示从 A 到节点 \(v\) 的当前最短距离估计,
\(\text{prev}[v]\) 表示最短路径树中的前驱节点。初始化 \(\text{dist}[A]=0\),其余节点为无穷大。
每轮从未确定节点中选择 \(\text{dist}\) 最小的节点,并用它的出边更新相邻节点距离。因为本例所有边权
非负,当一个节点被选出时,它的最短距离已经确定。
\begin{table}[htbp]
\centering
\caption{样例问题中 Dijkstra 的距离更新过程}
\label{tab:dijkstra}
\begin{tabular}{p{1.4cm}p{1.6cm}p{7.7cm}p{2.5cm}}
\toprule
轮次 & 确定节点 & 松弛结果 & 当前目标距离 \\
\midrule
1 & A & 更新 \(dist(B)=4\)\(dist(C)=2\) & \(dist(E)=\infty\) \\
2 & C & 更新 \(dist(D)=10\)\(dist(E)=12\) & \(dist(E)=12\) \\
3 & B &\(B\to D\)\(dist(D)\) 从 10 改为 9 & \(dist(E)=12\) \\
4 & D &\(D\to E\)\(dist(E)\) 从 12 改为 11 & \(dist(E)=11\) \\
5 & E & 终点 E 被确定,算法结束 & \(dist(E)=11\) \\
\bottomrule
\end{tabular}
\end{table}
Script Runner 可运行的验证脚本逻辑可以概括为:
\begin{lstlisting}
nodes = ["A", "B", "C", "D", "E"]
edges = {
"A": [("B", 4), ("C", 2)],
"B": [("C", 1), ("D", 5)],
"C": [("D", 8), ("E", 10)],
"D": [("E", 2)],
"E": [],
}
# Dijkstra: repeatedly select the unvisited node with minimum distance
# and relax its outgoing edges.
\end{lstlisting}
脚本验证结果应输出最短距离 11,路径为 \(A \to B \to D \to E\)
\subsection{Evaluator、Reflector 与最终答案}
Evaluator 检查答案是否包含算法选择理由、求解步骤、样例结果、正确性说明和复杂度分析。本例中,答案
满足所有必要项,因此 Loop Controller 不需要触发补充检索。Reflector 将各模块结果整理为最终回答:
\begin{quote}
应使用 Dijkstra 算法。因为该有向图所有边权均为非负,Dijkstra 每次选择当前距离最小的未确定节点,
该节点的最短距离不会再被后续路径改进。对给定图运行算法后,从 A 到 E 的最短路径为
\(A \to B \to D \to E\),总权重为 \(4+5+2=11\)。本例若使用二叉堆优先队列实现,时间复杂度为
\(O((|V|+|E|)\log |V|)\),空间复杂度为 \(O(|V|+|E|)\)
\end{quote}
\section{系统特点与局限}
系统的主要特点包括:
\begin{itemize}
\item \textbf{通用性:}不为每个样例新增专门求解器,而是通过范式检索、LLM 规划和脚本验证处理通用算法问题。
\item \textbf{可解释性:}Memory、Planner、Retriever、Executor、Script Runner、Evaluator 等模块输出均在前端显示。
\item \textbf{可复查性:}最终 PDF 保存原始问题、结构化结果、工具调用结果、评估记录和最终答案。
\item \textbf{可部署性:}项目使用 FastAPI 和 Docker,可部署到 Hugging Face Spaces 并公开访问。
\end{itemize}
系统也存在局限。第一,公开 LLM 的质量会影响 Executor 的初稿质量;第二,Script Runner 只适合小样例
和轻量验证,不适合运行大规模实验;第三,当前 Retriever 使用内置算法范式库,而不是完整课程资料检索,
因此它更适合展示通用算法设计流程,而不是替代教材检索系统。
\section{结论}
本项目实现了一个面向通用算法问题的可解释 Agent 系统。它将自然语言算法题拆解为结构化任务,利用
公开大模型生成算法设计,通过内置算法范式和轻量脚本验证增强可靠性,并把完整求解轨迹展示在网页和
PDF 报告中。样例问题表明,该系统能够从输入图模型出发,自动选择 Dijkstra 算法,给出最短路径
\(A \to B \to D \to E\) 和最短距离 11,同时说明适用条件、正确性依据和复杂度。整体上,系统重点
展示了一个算法求解 Agent 的工作机制,而不是只展示某一道题的固定答案。
\end{document}