在大厂日爆那里看到一则消息:某大模型公司启动离职脱敏程序,核心成员必须强制签署协议,离职需提前三个月同步公司。听起来有些夸张,但确实暗示着 AI 行业正在脱离传统互联网的轨道,在薪酬、制度和规则上都走向完全不同的方向。与此同时,面试中的 AI 含量也在飙升,传统 Java 后端八股的比重持续下降,七三开的趋势已经很明显。
所以,今天就以我们实际开发的 PaiCLI(基于 Java 的 AI Coding Agent)为例,完整拆解高频 AI Agent 面试的八个核心问题,所有回答均源自实战经验。

01、你在 Agent 项目中具体做了什么?
“先整体介绍一下,你在项目里负责了哪些模块?”
我答道:“PaiCLI 一共迭代了 21 个版本,从最原始的 ReAct 一路做到了支持 Multi-Agent 的可交付产品。我主要负责三块核心工作。”

第一块,Agent 引擎。 实现 ReAct 模式的主循环,完整覆盖 LLM 调用、工具执行、上下文管理全流程。每次循环都要做预算检查、上下文压缩、工具调度,这些机制都是从头设计和实现的。
第二块,LLM 多模型接入。 写了一套 OpenAI 兼容的抽象客户端,基于 OkHttp3 做 SSE 流式解析,支持智谱、DeepSeek、阶跃星辰、Kimi 四个国产模型的无缝切换。每个模型都有自己的特殊处理,比如智谱支持 prompt caching,DeepSeek 支持 1M 上下文窗口。
第三块,Tool Use 与 MCP 集成。 内置了 11 个核心工具(文件读写、命令执行、代码搜索、网页抓取等),同时支持动态接入 MCP Server 的外部工具。

02、Agent 的循环执行是怎么设计的?
“你们的 ReAct 循环具体怎么设计的?”
“其实很简单,核心就是一个 while(true) 循环,退出条件全部交给 LLM 自己决定。”

每次循环做四件事:
- 第一步,检查预算——看 token 用量、迭代轮数,超了就强制退出;
- 第二步,判断上下文是否需要压缩;
- 第三步,把对话历史和工具定义一起发给 LLM;
- 第四步,解析 LLM 的返回,如果有
tool_calls 就执行工具,并把结果塞回对话历史,进入下一轮;如果没有,说明任务完成,直接返回最终答案。

追问:“退出靠 LLM 自己判断,万一把它卷进死循环呢?反复调同一个工具停不下来怎么办?”
“我们有兜底策略:跟踪两个指标——累计 token 消耗(默认上限是模型最大上下文窗口的 80%)和循环迭代次数。任何一个超标,都会直接跳出循环并返回提示给用户。”
03、是框架内置的工具调用还是 Prompt 控制输出?
“你们用 Spring AI 了吗?工具调用的逻辑是框架搞定的还是自己写的?”
“没用 Spring AI,全是自己写的。”
PaiCLI 定义了一个 LlmClient 接口作为 LLM 调用的抽象层,底下基类基于 OkHttp3 实现了 OpenAI 兼容协议的 SSE 流式请求,工具调用的流程也是自己解析的。

LLM 返回的 SSE 流里,tool_calls 是分片到达的——一个工具调用的 id、name、arguments 可能散落在多个 SSE event 中。我们会把这些碎片存起来,流结束后再组装成完整的 ToolCall 对象。
追问:“为什么不用 Spring AI?自己造轮子不怕维护成本高吗?”
“PaiCLI 是命令行工具,启动速度很关键,Spring AI 太重了。而且现在大家都是 AI Coding,基本不存在实现不了的功能。”
04、System Prompt 是怎么设计的?
“聊聊你们的系统提示词设计,是一次性写死还是动态拼装的?”
“动态拼装,而且是分层组装。” 我们有一个 PromptAssembler,负责把系统提示词从多个 Markdown 文件里拼出来。

整个提示词分成七层:
- Base 层 (
base.md):定义 PaiCLI 的身份、语言要求、可用工具列表与使用策略;
- 性格层 (如
calm.md):定义冷静理性的风格;
- 模式指令层:ReAct、Plan、Multi-Agent 各一个 Markdown,告诉模型当前工作方式;
- 审批层:控制哪些工具需要用户确认才能执行;
- 动态上下文层:注入记忆摘要和外部上下文源;
- Skill 层:把启用的 Skill 描述注入;
- 输出/协作层:上下文管理指令和协作协议。
追问:“提示词文件放哪?用户能自定义吗?”
“有三级加载优先级:JAR 包内置的 resources/prompts/(默认);用户目录 ~/.paicli/prompts/(可覆盖默认);项目目录 .paicli/prompts/(优先级最高,适合团队定制)。这个设计参考了 Claude Code 的 CLAUDE.md 机制,让不同项目可以有不同的 Agent 行为。”
05、调用 LLM 的全过程
“从用户输入到最终返回,整个 LLM 调用过程完整说一遍。”
全流程如下:

第一步,构建上下文。 从短期记忆提取相关信息,拼进系统提示词的动态上下文。
memoryManager.addUserMessage(userInput);
String memoryContext = memoryManager.buildContextForQuery(
userInput, contextProfile.memoryContextTokens()
);
updateSystemPromptWithMemory(memoryContext);
第二步,用户消息入历史。 如果有图片引用(例如截图),会转成 base64 的 ContentPart 一并打包。
第三步,进入 ReAct 循环。 先检查是否需要压缩上下文,再检查预算。
第四步,构建 HTTP 请求。 将对话历史和工具定义序列化为 OpenAI 格式的 JSON:messages 数组里每条消息带 role 和 content,tools 数组里每个工具带 name、description 和 parameters 的 JSON Schema。
第五步,发送请求并解析 SSE 流。 OkHttp3 建立长连接,逐行读取 data: 开头的事件。每个事件的 delta 可能包含三种信息:reasoning_content(思考过程)、content(回复内容)、tool_calls(工具调用)。三个字段通过 StreamRenderer 实时渲染给用户。
第六步,流结束后组装 ChatResponse。 包含完整的 content、reasoning、toolCalls 列表、token 统计。
第七步,回到 Agent 循环,决定是执行工具还是返回结果。

追问:“实时渲染时,思考过程和回复内容怎么区分?”
“靠 SSE 事件里的字段名。DeepSeek 和智谱的深度思考模式会在 delta 里返回 reasoning_content 字段,普通回复走 content 字段。PaiCLI 内部维护了两个状态,思考过程用折叠样式展示,不会和最终回复混在一起。”(内心 OS:这题我太熟了,流式渲染那块调了三天才搞定各种边界情况🥲)
“工具定义什么时候发给模型的?是注册时就发,还是每次请求都发?”
“每次请求都发。” 在 ReAct 循环里调用 LLM 之前,都会从 ToolRegistry 拉一份最新工具定义:
List<LlmClient.Tool> toolDefinitions = toolRegistry.getToolDefinitions();
LlmClient.ChatResponse response = llmClient.chat(
conversationHistory, toolDefinitions, streamRenderer
);
这样做的好处是:如果用户中途通过 /mcp 命令加载了新的 MCP Server,新工具在下一次循环就能被 LLM 感知到。
工具的注册发生在构造阶段。内置工具逐个注册,例如 read_file 包含名称、描述、参数的 JSON Schema 和执行函数:
tools.put("read_file", new Tool(
"read_file",
"读取文件内容(仅限项目根目录之内)",
createParameters(
new Param("path", "string", "文件路径", true),
new Param("offset", "integer", "起始行号", false),
new Param("limit", "integer", "最多读取多少行", false)
),
args -> {
Path safe = pathGuard.resolveSafe(args.get("path"));
return readFileForTool(safe, args);
}
));
MCP 工具是动态注册的。启动 MCP Server 后,registerMcpTool() 会把外部工具注册到同一个 Map,命名空间用 mcp__{server}__{tool} 格式隔离。

追问:“执行呢?一个请求返回多个 tool_calls,串行还是并行?”
“看数量。单个直接在当前线程执行;多个开线程池并行跑,最多 4 个并发。”

并行执行配有两道超时保护:单个命令 60 秒超时,整个工具批次 90 秒超时。若某工具超时,对应的 Future 会被取消并返回 TimedOut 状态,不影响其他工具的结果。
“模型返回一堆 JSON,你怎么把它和具体的工具函数对应上?”
“关键就在 tool_calls 里的 name 字段。” LLM 返回的每个 tool_call 包含三个核心字段:id(唯一标识)、function.name(工具名称)、function.arguments(参数 JSON)。name 就是注册时用的那个名字,比如 read_file、execute_command。

在 SSE 流式场景下,这三个字段不是一次到齐的,我们按 index 累积碎片,流结束后组装成 ToolCall 列表。然后根据 name 从 ToolRegistry 的 Map 中查找对应的执行函数,把 arguments 的 JSON 解析成参数 Map,传给工具函数执行。执行结果带上 id 打包成 tool 类型的消息塞回对话历史。
for (ToolExecutionResult toolResult : toolResults) {
conversationHistory.add(
LlmClient.Message.tool(toolResult.id(), toolResult.result())
);
}
追问:“如果模型幻觉出一个不存在的工具名,比如 delete_database,怎么办?”
“找不到对应 key 时,返回错误信息‘未知工具:delete_database’。这条错误也会作为 tool 消息塞回对话历史,LLM 看到后通常会自我纠正,换个正确工具重试。另外在 base.md 系统提示词里我们也明确列出了所有可用工具,从源头减少幻觉。”(内心 OS:嘿嘿嘿,老王,这种细节追问真难不住我🤣)
08、记忆压缩方式,怎么生成摘要?
“前面提到的上下文压缩,具体怎么做?怎么生成摘要?”
“在每次 LLM 调用前触发。检查逻辑很简单:估算当前对话历史的 token 数,低于阈值(默认模型最大上下文窗口的 90%)就跳过;超过阈值则启动压缩。”

压缩不是全量处理,而是按“轮次”分割。 保留最近 3 轮对话不动,把更早的对话拿出来生成摘要。分割点必须落在 user 消息的边界,绝不能把一组 tool_call 和 tool_result 拆开,否则 LLM 会晕。
拿到待压缩消息后,调用 LLM 生成摘要,摘要提示词经过精心设计,要求保留四类关键信息:用户的核心诉求、Agent 已完成的操作、达成的共识、未解决的问题。
private static final String SUMMARY_PROMPT = """
请把下面的对话历史压缩成简明摘要,保留:
1. 用户提出的关键诉求与目标
2. Agent 已经完成的关键操作(哪些工具调用了什么、返回了什么核心结果)
3. 已经达成的共识或结论
4. 仍未解决的问题或待办
不要复述每条原文,不要列举所有工具调用,不要保留无关闲聊。
输出 1-3 段中文,不要用列表,不要加任何前缀或元描述。
""";
摘要生成后,重建对话历史:系统提示词 → 一条 user 消息(装压缩摘要) → 一条 assistant 消息(“好的,我已了解之前的上下文,请继续。”) → 最近 3 轮原始对话。加这条 assistant 消息是为了满足 OpenAI 兼容协议中 user / assistant 消息必须交替的约定。
追问:“摘要输入有长度限制吗?万一要压几万字的对话呢?”
“限制在 6 万字符,超出部分截断取前 6 万字符送摘要。这是防止摘要请求本身撑爆 LLM 上下文。实际使用中,3 轮保留 + 90% 阈值的组合下,待压缩内容一般在 2-3 万字符,很少触及上限。”
“压缩后 token 能降多少?”
“通常压到原来的 20%-30%。比如压缩前 8 万 token,压缩后大概 2-3 万。摘要本身只有几百到一千多 token,加上保留的最近 3 轮原始对话,总 token 数大幅下降。日志里会打印压缩前后的 token 数和消息数,方便观察效果。”

如何写到简历上?
AI编程助手|Agent开发|PaiCLI 2026-03 ~ 至今
项目简介: 基于 Java 的 AI Coding Agent 命令行工具,对标 Claude Code,支持 ReAct、Plan-and-Execute、Multi-Agent 三种执行模式,接入智谱、DeepSeek、阶跃星辰、Kimi 等国产大模型。
技术栈: Java 17、OkHttp3、Jackson、JLine3、MCP Protocol
核心职责:
- 设计并实现 ReAct 主循环引擎,基于 LLM 自主决策的
while(true) 架构,集成预算管理(token/迭代双维度),实现自动上下文压缩。
- 基于 OkHttp3 + SSE 实现 OpenAI 兼容的多模型接入层,支持 ToolCall 分片累积解析。
- 内置 11 个核心工具 + MCP 动态扩展,支持多工具并行执行(最大 4 并发)、超时保护和有序结果返回。
- 实现 7 层系统提示词组装架构,支持 JAR 内置 → 用户级 → 项目级三级覆盖,实现模式切换和上下文动态注入。
- 设计上下文压缩机制,按用户轮次分割、LLM 生成摘要、保留最近 3 轮原始对话,token 压缩率达 70%-80%,解决长对话场景下的上下文溢出问题。
以上内容提炼自云栈社区对 PaiCLI 项目的深度实践,如果你的 简历 里也需要补充 AI Agent 相关的项目亮点,或者正在准备相关面试,欢迎来社区进一步探讨交流。