Spaces:
Sleeping
Sleeping
| \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} | |