Java Agent 开发 · Day 1 学习笔记(含作业完整标准答案)

项目: knowledge-platform(内部知识查询平台)

日期: 2026-06-16

第一部分:核心概念

1.1 三种系统对比

类型 定义 谁决定流程 项目对应

Chatbot 一问一答,无外部能力 无流程 直接调 LLM 聊天

RAG 检索增强生成,固定流水线 Java 代码写死 QaService.ask()

Agent LLM + 目标 + 工具 + 循环决策 LLM 在循环里决定 尚未实现(Day 2 起)

一句话总结: RAG = 你写流程图,LLM 填最后一格;Agent = LLM 自己画流程图,你提供工具和边界。

1.2 Agent 四大组件

组件 作用 项目已有

LLM(大脑) 理解、推理、规划 LlmAnswerSynthesizer

Tools(手脚) 搜索、查库、HTTP 等外部能力 各 Service(待抽象为 AgentTool)

Memory(记忆) 对话历史、中间结果 QaSession + QaMessage

Planning Loop ReAct 多步决策循环 暂无(QaService 是单次流水线)

1.3 五个必记术语

术语 含义

Prompt 发给 LLM 的指令;System Prompt 定义 Agent 角色与边界

Function Calling LLM 返回 JSON(工具名 + 参数),Java 解析后执行

Tool Use Function Calling 的落地实现

Context Window 一次能塞进模型的 token 上限

Hallucination(幻觉) 模型编造内容;用 Tool 拿真实数据缓解

1.4 现有 RAG 流水线

用户提问

→ ① search_documents(DocumentFeignClient)

→ ② check_relevance(AnswerRelevanceEvaluator)

→ ③ generate_from_chunks(AnswerGenerator)

→ ④ 失败 → web_search(WebSearchService)

→ ⑤ synthesize_answer(LlmAnswerSynthesizer)

→ ⑥ 仍失败 → 返回「未找到」

1.5 ReAct 循环

步骤 英文 含义

想 Thought 分析现状,决定下一步

做 Action 调用 Tool,或给出 Final Answer

看 Observation 工具返回结果,写回上下文

循环直到 Final Answer 或超过 MAX_STEPS(通常 5~8)。

MAX_STEPS 的作用: 防死循环、控成本、控延迟、限制系统访问次数。

1.6 ReAct 与 Function Calling

ReAct :思维模式(Thought → Action → Observation)

Function Calling :工程实现(LLM 返回 tool_calls JSON,Java 执行后再调 LLM)

第二部分:作业 A 标准答案(Tool 清单)

作业要求: 列出至少 5 个 Tool,写出 name、description、parameters、returns、对应类。

Tool 1:search_documents

对应类: DocumentFeignClient.search()

description: 在企业内部知识库中检索与问题相关的文档片段。

适用于: 公司制度、报销流程、部署规范、员工手册、项目文档;用户问题含「公司」「内部」「我们」等语境时优先使用。

不适用于: 外部产品文档(如 Cursor、Docker 官方文档)、实时新闻、通用百科且内部库可能无收录(应改用 web_search)。

parameters: question: string(搜索关键词或完整问题);limit: integer(返回条数,默认 8)

returns: List<ChunkSearchResult>

Tool 2:check_relevance

对应类: AnswerRelevanceEvaluator.evaluate()

description: 评估文档检索结果与用户问题的语义相关性,过滤掉跑题、弱相关的片段。

适用于: search_documents 返回结果后,判断能否基于这些片段直接生成答案。

不适用于: 尚未检索文档时(应先调用 search_documents);联网搜索结果的质量评估(web_search 结果可直接交给 synthesize_answer)。

parameters: question: string;chunks: List<ChunkSearchResult>

returns: Result { relevant: boolean, chunks: List, topicMismatch: boolean }

Tool 3:classify_intent

对应类: QuestionIntentClassifier.classify()

description: 识别用户问题的意图类型,用于选择答案格式和写作风格。

适用于: 需要决定答案结构时(如「什么是 X」vs「如何部署 X」)。可识别 CONCEPT、PROCEDURAL、COMPARISON、TROUBLESHOOTING、POLICY、LIST 等。

不适用于: 已有明确工具链且无需调整格式时(可跳过以节省一步)。

parameters: question: string

returns: AnswerType 枚举值

Tool 4:generate_from_chunks

对应类: AnswerGenerator.generate()

description: 基于企业内部文档片段,用规则引擎提取要点并格式化答案(不调用 LLM)。

适用于: check_relevance 判定为相关;问题为操作步骤、命令、列举类;需要快速、低成本地从本地文档生成答案。

不适用于: 仅有联网搜索结果;本地片段质量差或为空;需要深度推理、对比分析(应使用 synthesize_answer)。

parameters: question: string;chunks: List<ChunkSearchResult>(经 check_relevance 过滤后)

returns: GeneratedAnswer { answer, answerType }

Tool 5:web_search

对应类: WebSearchService.search()

description: 通过 Bing/百度等搜索引擎检索互联网公开信息。

适用于: search_documents 无结果或结果不相关;外部软件/开源技术/行业通用概念;用户明确询问外部产品。

不适用于: 公司内部机密制度、未公开流程;已有高质量内部文档可回答的问题。

parameters: query: string(搜索关键词,注意不是 question)

returns: List<WebSearchItem>(title, snippet, url)

Tool 6:synthesize_answer

对应类: LlmAnswerSynthesizer.synthesize()

description: 调用大语言模型,综合多源参考资料生成自然流畅的中文答案。

适用于: 已有联网搜索结果或本地+联网混合资料;概念解释、对比分析类问题;generate_from_chunks 无法产出满意答案时的兜底。

不适用于: 没有任何参考资料(应先 search_documents 或 web_search);简单列举类且本地文档已足够(优先 generate_from_chunks 以节省成本)。

parameters: question: string;sources: 参考资料(WebSearchItem 列表或文档片段)

returns: 格式化后的答案文本

典型调用顺序(RAG 视角)

search_documents → check_relevance

├─ 相关 → classify_intent → generate_from_chunks → 返回答案

└─ 不相关 → web_search → classify_intent → synthesize_answer → 返回答案

Tool Description 写法原则(评分要点)

• 写清「何时用」「何时不用」

• 用 LLM 能理解的业务语言,不是 Java 方法名

• 相邻 Tool 边界要互斥,减少 LLM 选错

• 补充 parameters、returns、对应类

• 违法/敏感内容在 Agent System Prompt 统一拦截,不要写在单个 Tool 里

第三部分:作业 B 标准答案(ReAct 轨迹)

作业要求: 选一个真实问题,逐步写出 Thought → Action → Observation → ... → Final Answer,至少 2 个 Thought、2 个 Action、1 个 Final Answer。

标准答案一:什么是 Redis?

Step 1 --- Thought

用户问的是通用技术概念「Redis」。先查内部知识库,看是否有架构文档、培训材料或术语表收录;若有则优先用内部口径回答。

Step 1 --- Action

{

"tool": "search_documents",

"arguments": {

"question": "什么是redis",

"limit": 8

}

}

Step 1 --- Observation

{

"chunks": \[\]

}

或弱相关:

{

"chunks": [

{

"documentTitle": "AI术语表",

"content": "...缓存...消息队列..."

}

]

}

Step 2 --- Thought

检索结果为空或弱相关,需确认是否可用。

Step 2 --- Action

{

"tool": "check_relevance",

"arguments": {

"question": "什么是redis",

"chunks": ...

}

}

Step 2 --- Observation

{

"relevant": false,

"topicMismatch": true

}

Step 3 --- Thought

内部资料不足以回答。Redis 是业界通用开源组件,应联网获取权威定义与典型用途。

Step 3 --- Action

{

"tool": "web_search",

"arguments": {

"query": "Redis 是什么 内存数据结构存储"

}

}

Step 3 --- Observation

{

"items": [

{

"title": "Redis 官方介绍",

"snippet": "Redis is an in-memory data structure store...",

"url": "https://redis.io/..."

},

{

"title": "Redis 中文文档",

"snippet": "Redis 是一个开源的内存数据结构存储系统,可用作数据库、缓存和消息中间件",

"url": "..."

}

]

}

Step 4 --- Thought

联网已有清晰定义,意图为概念解释(CONCEPT),需用 LLM 综合多条摘要,写成定义+用途+场景的中文回答。

Step 4 --- Action

{

"tool": "classify_intent",

"arguments": { "question": "什么是redis" }

}

然后:

{

"tool": "synthesize_answer",

"arguments": {

"question": "什么是redis",

"sources": "上一步 web_search 的 items"

}

}

Step 4 --- Observation

{

"answerType": "CONCEPT",

"draft": "(LLM 合成稿)"

}

Step 5 --- Final Answer

Redis 是一个开源的内存数据结构存储系统,数据主要放在内存中,读写非常快。它常被用作:缓存(减轻数据库压力)、会话存储、排行榜/计数器,以及消息队列等场景。支持字符串、哈希、列表、Set、Sorted Set 等多种结构,持久化可选 RDB/AOF。在微服务架构里,Redis 经常和 MySQL 配合:热数据放 Redis,冷数据落库。

易错点提醒:

• web_search 参数是 query,不是 question,也没有 limit 参数

• web_search 返回 items(title/snippet/url),不是 chunks

• relevant 字段来自 check_relevance,不是 search_documents 或 web_search 自带

• 概念题 Final Answer 需:第一句定义 + 用途 + 场景,不能只有一句话

标准答案二:Docker Compose 怎么部署 qa-service?

Step 1 --- Thought

用户问的是部署操作,属于内部项目文档或运维规范。应先搜企业内部知识库,看有没有 qa-service / Docker 相关文档。

Step 1 --- Action

{

"tool": "search_documents",

"arguments": {

"question": "Docker Compose 部署 qa-service",

"limit": 8

}

}

Step 1 --- Observation

找到 README、docker/README.md,内容主要是 java -jar 启动,无明确 docker-compose.yml。

Step 2 --- Thought

本地有 jar 启动方式,但用户明确问 Compose。需确认问题类型,并判断是否需要联网补充 Compose 模板。

Step 2 --- Action

{

"tool": "classify_intent",

"arguments": {

"question": "Docker Compose 怎么部署 qa-service?"

}

}

Step 2 --- Observation

{

"answerType": "PROCEDURAL"

}

Step 3 --- Thought

意图是操作步骤;内部文档可能不完整,应联网查 Docker Compose 部署 Spring Boot 微服务的通用做法,再结合本项目结构补充。

Step 3 --- Action

{

"tool": "web_search",

"arguments": {

"query": "Docker Compose 部署 Spring Boot 微服务"

}

}

Step 3 --- Observation

多条结果含 Compose 服务定义、端口映射、环境变量等模板。

Step 4 --- Thought

已有内部启动说明 + 联网 Compose 模板,信息足够,应综合写一份面向本项目的部署步骤。

Step 4 --- Action

{

"tool": "synthesize_answer",

"arguments": {

"question": "Docker Compose 怎么部署 qa-service?",

"sources": "内部文档 + web items"

}

}

Step 5 --- Final Answer

在本项目中部署 qa-service,建议按以下步骤:

  1. 编译:在 backend 目录执行 mvn clean package -DskipTests,生成 qa-service/target/qa-service-1.0.0-SNAPSHOT.jar。
  2. 编写 Compose 文件:创建 docker-compose.yml,定义 qa-service 服务,映射 8083:8083。
  3. 配置依赖:qa-service 通过 Feign 调用 document-service,需配置 Eureka、document-service 等地址。
  4. 启动:docker compose up -d,验证问答接口。
  5. 若仓库暂无 compose 文件,可先按 README 用 java -jar 本地验证,再容器化。
    Agent 价值说明: 看到本地只有 jar 启动、缺 Compose 后,主动决定联网,而不是有片段就强行本地答。

    标准答案三:报销需要哪些材料?(简单 3 步)
    Step 1 --- Thought
    用户问公司内部报销材料,应优先搜内部知识库中的财务制度文档。
    Step 1 --- Action
    {
    "tool": "search_documents",
    "arguments": {
    "question": "报销 材料",
    "limit": 8
    }
    }
    Step 1 --- Observation
    找到《财务报销制度》等文档片段,含材料清单。
    Step 2 --- Thought
    检索结果与问题高度相关,意图为制度/流程类,可直接从文档提取材料清单。
    Step 2 --- Action
    check_relevance → classify_intent(POLICY/PROCEDURAL)→ generate_from_chunks
    Step 2 --- Observation
    {
    "relevant": true,
    "answerType": "POLICY"
    }
    Step 3 --- Final Answer
    根据公司财务报销制度,报销通常需要:
  6. 报销单(填写完整并签字)
  7. 合法有效的发票原件
  8. 费用明细及相关审批凭证
  9. 差旅报销另需行程单、住宿水单等
    具体以最新版《财务报销制度》为准。
    说明: 此场景不需要 web_search 和 synthesize_answer,RAG 固定流水线即可高效处理。

    第四部分:作业 C 标准答案(LlmAnswerSynthesizer 改造)
    作业要求: 对照 LlmAnswerSynthesizer.callChatCompletions(),标出从 RAG 改为 Agent 需要新增/修改的 3 处。
    改造点 1:请求体增加 tools 定义
    现状(RAG): 请求体只有 model、temperature、max_tokens、messages。
    改造(Agent): 在 body 中增加 tools 数组和 tool_choice 字段。
    // 现有代码(约 106-112 行)
    ObjectNode body = objectMapper.createObjectNode();
    body.put("model", model);
    body.put("temperature", properties.getTemperature());
    body.put("max_tokens", properties.getMaxTokens());
    ArrayNode messages = body.putArray("messages");
    messages.addObject().put("role", "system").put("content", SYSTEM_PROMPT);
    messages.addObject().put("role", "user").put("content", userPrompt);

// Agent 需新增

ArrayNode tools = body.putArray("tools");

for (ToolDefinition tool : toolRegistry.all()) {

tools.add(tool.toJson());

}

body.put("tool_choice", "auto");

改造点 2:响应解析支持 tool_calls

现状(RAG): 只解析 message.content 文本。

改造(Agent): 需分支处理 content 与 tool_calls。

// 现有代码(约 129-134 行)

JsonNode content = root.path("choices").path(0).path("message").path("content");

return content.asText().trim();

// Agent 需改为

JsonNode message = root.path("choices").path(0).path("message");

JsonNode toolCalls = message.path("tool_calls");

if (toolCalls.isArray() && !toolCalls.isEmpty()) {

return LlmResponse.withToolCalls(parseToolCalls(toolCalls));

}

return LlmResponse.withContent(message.path("content").asText());

改造点 3:单次调用改为多轮循环

现状(RAG): Java 决定流程,LLM 只被调用一次生成最终文本。

改造(Agent): 外层 ReAct 循环,每轮 append messages。

public String runAgent(String question, List tools) {

List messages = initMessages(question);

for (int step = 0; step < MAX_STEPS; step++) {

LlmResponse resp = callChatCompletions(messages, tools);

if (resp.hasFinalAnswer()) {

return resp.getContent();

}

ToolCall call = resp.getToolCall();

String observation = executeTool(tools, call);

messages.add(assistantToolCallMessage(call));

messages.add(toolResultMessage(call.getId(), observation));

}

return "超过最大步数,请简化问题后重试";

}

对比总结表

维度 RAG(现在) Agent(目标)

谁决定流程 Java if/else LLM 在循环中决定

LLM 调用次数 通常 1 次 多次(每步 1 次)

请求体 仅 messages messages + tools

响应解析 仅 content content 或 tool_calls

Tool 结果 Java 变量传递 append 到 messages 再调 LLM

第五部分:自测题标准答案

Q1:search_documents 和 web_search 的 description 怎么写?

search_documents: 在企业内部知识库中搜索与问题相关的文档片段。优先用于公司制度、流程、内部规范类问题。不适用于外部产品文档、实时新闻、通用百科。

web_search: 通过搜索引擎检索互联网公开信息。当内部知识库无结果,或问题涉及外部产品、通用技术概念时使用。不适用于公司内部机密制度、未公开流程。

原则: 写清何时用/何时不用;两个 Tool 边界互斥;用业务语言而非 Java 方法名。

Q2:为什么需要 MAX_STEPS?

• 防无限循环:模型可能反复调用同一工具

• 控成本:每步 ≈ 1 次 LLM + N 次外部 API

• 控延迟:用户等待有上限

• 安全:限制 Agent 对系统的访问次数

超限策略: 返回「请缩小问题范围」,或将已有 Observation 交给 LLM 做部分回答。

Q3:固定流水线 vs Agent 优缺点?

固定流水线(QaService) Agent

优点 行为可预测;易调试;延迟低;成本低 灵活;可处理复杂多步任务;可组合工具

缺点 无法应对需多步推理的复杂问题;流程变更要改代码 不可预测;调试难;成本高;可能幻觉或死循环

适用 简单 FAQ、单轮问答 对比分析、先查 A 再查 B 再总结

结论: 不是替代关系。简单 FAQ 用 RAG 足够;复杂任务用 Agent;演进路径为 RAG 作默认、复杂问题走 Agent。

附录 A:ReAct 作业模板

用户问题

Step 1

Thought :(为什么先做这一步)

Action :{ "tool": "", "arguments": {} }

Observation:(工具返回什么)

Step 2

Thought :...

Action :...

Observation:...

Step N

Thought :...

Final Answer :...

附录 B:后续课程预告

Day 主题

Day 2 AgentTool 接口 + 第一个 Tool 实现

Day 3 Function Calling + ReAct 循环(Java)

Day 4 多轮对话记忆与上下文管理

Day 5 Mini Agent:自主搜文档 + 联网

相关推荐
何以解忧,唯有..1 小时前
Go语言中的const:常量声明与iota枚举详解
java·开发语言·golang
范什么特西1 小时前
Spring boot细节
java·spring boot·后端
Ysouy1 小时前
Spring Data Elasticsearch 全流程学习教程
java·spring·elasticsearch
沪飘大军1 小时前
goldRush-专门分析黄金的投资理财agent
java·开发语言·elasticsearch
beethobe1 小时前
PythonQt 学习之旅(一):从零构建 C++ 与 Python 的桥梁
c++·python·学习
鹏易灵2 小时前
C++——2.常量与 const、constexpr 初识详解
java·开发语言·c++
qq_452396232 小时前
第十三篇:《K8s 安全基础:RBAC、ServiceAccount、Pod Security》
java·安全·kubernetes
如果你想拥有什么先让自己配得上拥有2 小时前
创业全周期证券学习法评价与系统观分析
学习
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 37 天:异常场景测试:断电、拔插、干扰、非法指令
单片机·嵌入式硬件·学习