写在前面:前四篇我完成了 AI 调用、多场景模板、知识库检索、RAG 增强讲解。但是在最近和组员们进行交流后我们发现,原先的Agent工作流实在过于简单,整个流程是一次性让 AI 完成所有任务——理解题目、分析代码、判题、检索知识、生成讲解,全部塞进一次调用里,这样可能会产生不少的问题,并且生成的辅导效果可能也会不太好,所以我们决定对agent工作流进行优化,从开始单次调用的流程优化成多轮推理的流程。

一、问题:单次调用的局限

之前的流程是这样的:

用户提交代码 + 判题结果 + 检索到的知识 → 一次性发给 AI → AI 一次性返回讲解

这种做法有几个问题:

问题 说明
过于简单 流程就涉及文本的整理,然后一股脑丢进去进行一次调用,流程简单,质量不高
无法纠错 如果某一步理解错了,后面的分析全错,但没有机会修正
BUG难调 不知道 AI 内部思考过程,出了问题难以定位

二、解决方案:多轮推理

核心思想很简单:把复杂任务拆成多个任务,每一步的结果传给下一步。

每一步都在“增强”上一步的信息,最终质量远高于单次调用

流程如下

第1次调用:只做一件事 → 理解题目和代码
        ↓
第2次调用:基于上一步的结果,分析错误原因
        ↓
第3次调用:基于分析结果,制定改进方案
        ↓
第4次调用:基于以上所有,生成最终讲解

三、技术实现

3.1 四轮任务设计

轮次 任务 输入 输出
Round 1 理解问题 题目 + 代码 理解报告
Round 2 分析错误 理解报告 + 判题结果 + 检索知识 错误分析
Round 3 制定方案 错误分析 改进方案
Round 4 生成讲解 理解报告 + 错误分析 + 改进方案 最终辅导

3.2 记忆机制

每轮的结果需要保存下来,传给下一轮

3.3 四轮 Prompt 详细设计

Round 1:理解问题
func buildRound1Prompt(title, desc, code string) string {
    return fmt.Sprintf(`【任务】理解题目和代码,不要分析错误。

【题目】
标题:%s
描述:%s

【用户代码】
%s

1. 这道题的核心考点是什么?
2. 用户代码用了什么方法?
3. 代码的主要逻辑是什么?

`, title, desc, code)
}
Round 2:分析错误
func buildRound2Prompt(prevResult, status, errorMsg, knowledgeContext string) string {
    return fmt.Sprintf(`【任务】基于上一步的理解,分析代码为什么没通过。

【上一步理解】
%s

【判题结果】
状态:%s
错误信息:%s

【检索到的知识】
%s

1. 代码失败的核心原因是什么?
2. 这属于哪类问题(超时/逻辑错误/语法错误)?
3. 关键问题出在哪里?

`, prevResult, status, errorMsg, knowledgeContext)
}
Round 3:制定方案
func buildRound3Prompt(prevResult, status string) string {
    return fmt.Sprintf(`【任务】基于分析结果,给出改进方案。

【上一步分析】
%s

【判题结果】
状态:%s

1. 具体应该怎么修改代码?
2. 修改后的复杂度是多少?
3. 有没有更好的解法?

`, prevResult, status)
}
Round 4:生成讲解
func buildRound4Prompt(round1Result, round2Result, round3Result string) string {
    return fmt.Sprintf(`【任务】生成最终学习辅导。

【问题理解】
%s

【错误分析】
%s

【改进方案】
%s

请按以下格式输出最终辅导:
1. 问题定位:
2. 原因分析:
3. 改进方向:
4. 拓展学习:

要求:不要直接给完整代码,只提供思路。`, round1Result, round2Result, round3Result)
}

四、效果对比

那继续拿我之前用的那道阶乘问题的56分WA代码来测试

4.1 输出质量对比

这是之前流程的输出

1. **问题定位**:代码中 `while(ans%10000==0)ans/=10000;` 的消零触发条件设置错误,且取模操作 `%10000` 放置在除法之后(或未及时除零),导致中间运算过程发生**精度截断**和**有效状态丢失**。
 
2. **原因分析**:阶乘累乘过程中,末尾的 `0` 是由因子 `2` 和 `5` 配对产生的,通常只会连续出现1~3个(如末尾为 `10, 120, 3500` 等)。此时 `ans % 10000 == 0` 条件并不成立,导致末尾的 `0` 在循环中始终未被清除。后续继续乘新数字时,这些未清除的 `0` 会不断累积,而紧接着的 `ans %= 10000` 会直接截断高位有效数字。由于**模运算与除法不满足交换律**,这种“先截断、后(不)除零”的操作破坏了原始数值的数学性质,最终导致高位有效位丢失,结果出错。这正对应了参考资料[2]中强调的**“常见错误:边界条件处理错误”**,此处即是对“何时该消零”以及“取模与除法的执行顺序”这两个关键边界/状态处理不当,引发了计算链的精度雪崩。
 
3. **改进方向**:
   - **修正消零逻辑**:将判断条件改为 `while(ans % 10 == 0) ans /= 10;`,确保每次乘法产生末尾 `0` 后**立即**清除,避免零的堆积。
   - **调整取模时机与基数**:必须在除零**之后**再执行取模操作(`ans %= 1000000;` 或更大),且取模基数应适当放大(如保留5~8位十进制数)。这样既能防止 `unsigned long long` 溢出,又能保留足够的高位信息,供后续乘法正确进位。
   - **进阶思路**:若追求更严谨的数学解法,可参考状态压缩的思想,单独统计阶乘展开式中质因子 `2` 和 `5` 的个数,配对消去后,仅用多余的 `2` 与其他非2/5因子相乘,并在每步直接对 `10`(或稍大模数)取模,从而彻底规避大数模拟带来的边界陷阱。

这是进行agent流优化后的输出

Round 4: 生成讲解

1. 问题定位:
本题核心在于“大数阶乘末位非零数字”的计算。你采用了迭代累乘过程中“反复除以10000去零”与“模10000防溢出”的模拟策略。核心偏差在于该动态去零与低精度取模的组合,破坏了阶乘质因子的自然生长规律,导致有效计算位提前丢失。

2. 原因分析:
属于典型逻辑错误。阶乘末尾的零严格由质因子2与5配对产生(`2×5=10`)。你的“除以10000去零”属于粗放的十进制截断,会误删尚未与5配对的独立因子2,打断质因子计数链。同时,仅保留模10000的精度过低,末位非零数字实际由累乘积的低位与更高位共同进位决定,截断必然导致最终 `%10` 结果偏离真实值。

3. 改进方向:
转向**质因子分离+高位保模**思路,具体分三步:
- 遍历 `2~N`,对每个数循环提取其中的因子2和5,分别累加至 `cnt2`、`cnt5`,仅将剥离2/5后的剩余部分参与累乘。
- 累乘过程中对较大基数(如 `100000`)取模,确保保留足够有效位以抵抗溢出,同时不干扰个位规律。
- 遍历结束后,计算多余因子2的贡献 `2^(cnt2 - cnt5)`(可用快速幂),将其乘入累乘结果并对 `100000` 取模,最后 `%10` 即得答案。该方案严格维持了2与5的配对关系,时间复杂度 `O(N)`,空间 `O(1)`。

4. 拓展学习:
当 `N` 达到 `10^9` 级别时,`O(N)` 线性遍历会超时。建议进阶掌握**数论递推法**:利用公式 `f(n) = (f(n/5) × f(n%5) × 2^(n/5)) % 10` 递归求解。该公式本质是将数列按5分组,剥离所有5后,剩余部分呈现固定周期规律;多余的2随分组数呈指数变化,结合快速幂可将复杂度压至 `O(log N)`。动手推导分组规律与周期映射,能大幅提升对数论模运算与阶乘性质的敏感度。

质量确实有所提高,下面除了指出错误并分析,也讲解了原理

4.2 可观测性对比

如果单次调用,那么我们只能得到最终的结果,没有办法看到ai是如何思考的

多轮推理

除了上面的最终讲解,实际上我们也可以选择输出每一步的结果。

Round 1: 理解问题
本题核心考点是大数阶乘计算中的溢出处理与末位非零数字的提取技巧。用户代码采用了“迭代累乘+模运算压缩+动态去零”的模拟方法。其主要逻辑为...


Round 2: 分析错误

代码失败的核心原因是“循环除以10000去零”的策略破坏了阶乘中因子2与5的正确配对,导致有效精度丢失。这属于典型的**逻辑错误**。关键问题出在:...


Round 3: 制定方案

具体修改为:独立统计1~n中质因子2和5的总数,遍历时剔除每个数中的2与5,将剩余部分累乘并对100000取模;遍历结束后补回多余的2(计算`2^(cnt2-cnt5) % 1...


这个也十分重要,我们可以看到每个round我们都能检查对应的结果(这里为了防止文本太多没有全部输出出来),这样如果项目测试的时候出现了问题我们可以很方便地进行追溯。

五、遇到的问题与解决

API 调用超时

现象:多轮推理中某一轮超时失败

实际上每次调用的时间都挺长的,如果不对字数进行限制,到第二第三步总是会发生超时的问题。

原因

  • 每轮都要完整调用 API

  • 后几轮需要处理前面生成的较长上下文

  • 默认 60 秒超时不够

解决方案

  1. 延长超时到 180 秒

  2. 在 Prompt 中限制每轮输出长度(前面的的限制300字,结果限制800字)

六、反思

  1. 逐步推理的复杂结构能够提升质量:把任务拆分,每部分任务都进行单独地递进式调用,效果会更好,因为这样可以利用多次调用形成类似递进式思考的效果,每轮的结果都能对下一轮产生增强。

  2. 让 AI 展示思考过程:多轮推理的中间输出,其实就是 AI 的“思考过程”。这对调试非常有用——哪里出错了,一眼就能看出来。之前单次调用如果出了问题完全不知道是哪一步AI想错了。

  3. 长度限制很重要:一开始我担心限制字数会让 AI 讲的非常简略,失去讲解的作用。但限制之后发现并不会,如果合理地限制字数,能让AI 更聚焦,输出质量反而可能更高,而且还解决了超时的问题。

七、下一步计划

  • 第六篇:知识点图谱原型设计

  • 第七篇:个性化推荐模块实现

还有就是会逐步补齐知识库的100条知识条目

而且实际上,这个Agent工作流并未解决无法纠错的问题,只能是说出错了可以看到问题在哪,并不会自动进行纠错,后面会增加每轮的自动验证和修正过程,防止ai间歇性犯病出错,或者我们继续讨论,考虑设计其他更有质量的Agent工作流进行更换。

八、小结

这次我完成了 Agent 工作流从“单次调用”到“多轮推理”的优化,把任务拆解成“理解→分析→方案→生成”四步,让Agent 工作流不再是像一开始的整合文本后简单地一次询问,而是具备了推理的过程,提高了讲解文本的质量,也使项目更具备智能体的特征。

Logo

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

更多推荐