README · demo 2

AI Agent Demo 工程说明

这个目录是一个最小 Spring Boot demo,不是完整 Agent 产品。它把笔记里的 RAG / Trace / Agent API 思路落到一个可运行后端切片:本地 Markdown 检索、基于来源的 deterministic 回答、trace 查询。

当前结构

路径作用当前状态
pom.xmlMaven 项目定义,Spring Boot 4.1.0,Java 17基础 starter、web、test
src/main/java/com/example/demo/DemoApplication.java应用入口默认启动类
src/main/java/com/example/demo/agentAgent API 编排请求、回答、来源、trace 串联
src/main/java/com/example/demo/notes本地 Markdown 检索扫描 技术栈/AI Agent 笔记并返回片段
src/main/java/com/example/demo/tracetrace 存储内存索引 + JSONL 持久化每次请求的阶段记录
src/main/java/com/example/demo/eval固定用例评测检查拒答、来源数量、答案关键词
src/main/java/com/example/demo/tool工具契约与执行search/read/trace/debug/eval/memory 的 tool schema
src/main/java/com/example/demo/memory记忆存储MemoryRecord / MemoryWriteRequest / JSONL 追加存储
src/main/java/com/example/demo/mcpMCP JSON-RPC transportinitializetools/listtools/call 的最小包装
src/main/java/com/example/demo/webREST 接口/API/chat/API/notes/*/API/tools/*/API/traces/{id}/API/eval
src/main/resources/application.properties配置入口agent.notes-root=..agent.memory-pathagent.trace-path
src/test/java/com/example/demo测试context、search、agent/refusal/trace
eval-cases.json固定评测用例smoke 后跑 eval gate

接口

接口方法作用验收点
/API/notes/search?q=RAG&limit=5GET检索本地 Markdown返回 path/title/score/snippet
/API/notes/read?path=README.mdGET安全读取一篇 Markdown返回 path/title/content
/API/chatPOST输入问题,返回带来源回答返回 traceId/refused/answer/sources/trace
代码块JAVA · 2 行收起展开
| `/API/traces/{traceId}` | GET | 查看一次请求的 trace | 能看到检索、拒答或回答阶段 |
| `/API/traces/{traceId}/debug` | GET | 查看 trace 派生诊断报告 | 能看到状态、失败归因、证据数量、下一步建议 |

| /API/eval | POST | 跑固定用例 | 返回 total/passed/failed/results | | /API/tools | GET | 查看工具 schema | 返回带 readOnly 标记的 tool definitions | | /API/tools/call | POST | 调用工具 | 返回 ok/result/error | | /API/memories | GET/POST | 查询或写入 demo memory | id/type/content/source/confidence/createdAt | | /mcp | POST | MCP JSON-RPC 入口 | initializetools/listtools/call |

/API/chat 示例:

代码块JSON · 4 行收起展开
{
  "message": "MCP 的 tool schema 为什么重要?",
  "limit": 3
}

/mcp 当前对齐 MCP 2025-11-25 稳定规范的工具子集,参考:Model Context Protocol specification。它只实现教学必需的三类 JSON-RPC 方法:

MCP method当前行为底层实现
initialize返回协议版本、serverInfo、tools capabilityMcpService.initializeResult()
tools/list暴露 search_notes/read_note/get_trace/debug_trace/run_eval/list_memories/write_memoryToolService.definitions()
tools/call调用工具,失败时返回 isError=trueToolService.call()

它暂时不做 session、resources、prompts、sampling、完整 Streamable HTTP 细节;这些属于后续生产化 MCP。

返回字段:

字段含义
traceId一次请求的唯一 ID,用于复盘
refused是否因为没有证据而拒答
answerdeterministic demo 回答,不代表真实 LLM
sources本次回答使用的笔记来源
tracevalidate / retrieve / answer / refuse 等阶段

代码阅读顺序

顺序文件先看什么读懂后应该能回答
1web/AgentController.java三个 REST 入口如何分流HTTP 请求怎么进入应用层
2agent/ChatRequest.javamessagelimiteffectiveLimit()外部输入在哪里被收紧
3agent/AgentService.javavalidate -> retrieve -> answer/refuse -> save traceAgent 最小运行链路是什么
4notes/NoteSearchService.javaterms()score()snippet()本地笔记怎样被召回和排序
5trace/TraceService.javasave()find()loadExisting()、JSONL 边界为什么服务重启后仍能复盘
6trace/DebugReportService.javareport()issue()recommendations()怎么把 trace 转成失败归因和下一步动作
7notes/NoteReadService.javaresolve()read()怎么防止路径逃逸和未授权读取
8tool/ToolService.javadefinitions()call()search/read/trace/debug/eval 如何变成 tool
9mcp/McpService.javainitializeResult()toolsListResult()callTool()ToolService 怎么映射成 MCP
10memory/MemoryService.javawrite()list()append()readAll()记忆如何校验、落盘、再查询
11eval/EvalService.javarun()reasons()怎么用固定用例抓退化
12AgentControllerTests.java / McpControllerTests.java / MemoryControllerTests.javaMockMvc 端到端断言接口层是否真的串起来

核心字段和函数

字段/函数作用后续升级点
ChatRequestmessage用户问题原文加入 session/userId/taskType
ChatRequestlimit / effectiveLimit()控制检索证据数量变成 context budget 计算
AgentResponserefused标记是否因为缺少证据拒答加 refusalReason
AgentResponsesources回答引用的证据加 chunkId、scoreDetail、line range
AgentServiceanswer()单次 Agent 请求主流程接入 LLM、planner、tool call
NoteSearchServicesearch()扫描 Markdown 并返回排序片段替换为 embedding/vector/hybrid search
NoteSearchServicescore()关键词评分替换为 BM25 + rerank
NoteReadServiceread()只读可检索 Markdown,并阻止路径逃逸加 line range 和 frontmatter
TraceServicesave() / find() / loadExisting()保存、查询、重载请求复盘记录替换为数据库或 OpenTelemetry
DebugReportstatus / issue / failedPhase / recommendations把原始 trace 变成可读诊断结果加检索质量、prompt 版本、tool error 分类
DebugReportServicereport()根据 traceId 生成失败归因和下一步建议加 trace 对比、聚合统计、报告落盘
EvalCaseexpectRefused / minSources / requiredAnswerTerms固定质量门槛加 latency、forbiddenTerms、sourcePath 断言
EvalServicerun()批量跑用例并输出 pass/fail加报告落盘和 CI gate
ToolDefinitioninputSchema / readOnly描述工具参数和风险边界映射成 MCP tool schema
ToolServicedefinitions() / call()工具注册与统一执行入口加权限确认、超时、审计
McpRequestjsonrpc / id / method / paramsMCP/JSON-RPC 请求对象加 notification 和 session
McpServicehandle() / mcpTool() / toolResult()把本地工具映射成 MCP initialize/list/call加 resources、prompts、streamable HTTP
MemoryWriteRequesttype / content / source / confidence记忆写入命令,要求说明类别、内容、来源、置信度加 userId、traceId、ttl、confirmPolicy
MemoryRecordid / createdAt已落盘记忆证据,方便 trace/tool/MCP 返回引用加 version、updatedAt、supersedes
MemoryServicewrite() / list()JSONL 追加写入和轻量查询替换为数据库、索引、撤销和冲突合并
AgentControllerTestschatEndpointReturnsSourcesAndReadableTrace()验证 HTTP -> Agent -> Search -> Trace后续加错误码、超时、权限测试

适合扩展的方向

阶段新增模块验收门槛
1ChatController + AgentService已完成:结构化响应、拒答、sources、trace
2TraceService + DebugReportService已完成:每次请求有 traceId、阶段、耗时、JSONL 持久化、debug report
3NoteSearchService已完成:检索结果带来源、分数、引用片段
4EvalService已完成:固定用例检查拒答、来源、答案关键词
5NoteReadService已完成:安全读取可检索 Markdown,阻止路径逃逸
6ToolService已完成:工具 schema、读写分级边界、结构化错误
7McpService已完成:最小 MCP JSON-RPC 包装,复用当前 tool registry
8MemoryService已完成:JSONL 追加写入,记录 source/type/confidence/createdAt

失败模式

失败表现处理
只写 Controller业务逻辑散在接口层把 Agent 流程放进 service
无 trace回答错了无法复盘每次请求保存 trace
无 schema工具参数不可控工具输入先建 DTO / JSON Schema
读取越界../outside.md 之类路径逃逸service 拦截,HTTP 返回 400,tool 返回结构化 error
无测试demo 一改就坏至少保留 context load 和 service 单测

本地运行

代码块POWERSHELL · 3 行收起展开
cd "F:\2. ObsidianNotes\技术栈\AI Agent\demo"
.\mvnw.cmd test
.\mvnw.cmd spring-boot:run

如果本机默认 JAVA_HOME 不可用,先临时指定可用 JDK,再运行 Maven。

代码块POWERSHELL · 3 行收起展开
$env:JAVA_HOME='D:\JAVA_TechTool\JDKs\oracle-24.0.1'
$env:Path="$env:JAVA_HOME\bin;$env:Path"
.\mvnw.cmd test

启动服务后,用 smoke 脚本验证三条接口链路:

代码块POWERSHELL · 8 行收起展开
.\mvnw.cmd spring-boot:run
.\smoke-test.ps1
.\eval-test.ps1
.\tool-test.ps1
.\mcp-test.ps1
.\memory-test.ps1
.\trace-test.ps1
.\debug-test.ps1

如果服务跑在其他端口:

代码块POWERSHELL · 7 行收起展开
.\smoke-test.ps1 -BaseUrl "http://localhost:18080"
.\eval-test.ps1 -BaseUrl "http://localhost:18080"
.\tool-test.ps1 -BaseUrl "http://localhost:18080"
.\mcp-test.ps1 -BaseUrl "http://localhost:18080"
.\memory-test.ps1 -BaseUrl "http://localhost:18080"
.\trace-test.ps1 -BaseUrl "http://localhost:18080"
.\debug-test.ps1 -BaseUrl "http://localhost:18080"

smoke 脚本必须同时证明三件事:

检查证明
/API/notes/search 有结果Markdown 检索能读到本地笔记
/API/chatsourcestraceId回答不是无来源编造
/API/traces/{traceId}steps请求能复盘阶段

eval 脚本再证明固定质量门槛没有退化:

检查证明
expectRefused该拒答时拒答,该回答时不拒答
minSources回答必须有足够来源
requiredAnswerTerms关键概念没有从答案中消失

eval-test.ps1 会把 JSON body 转成 UTF-8 bytes 再发送,因为 eval-cases.json 里可能包含中文断言词;直接用 PowerShell 5 的字符串 body 容易把中文发成 ?

tool 脚本证明工具契约和安全边界:

检查证明
/API/toolsread_note schema工具能力可以被 Agent 发现
read_note 能读取 README.md工具执行能返回真实资源
read_note 拒绝 ../outside.md工具不会绕过 notes root

mcp 脚本证明 transport 层没有绕开工具边界:

检查证明
initialize 返回 2025-11-25demo 对齐当前稳定 MCP 协议版本的最小握手
tools/list 返回 read-only annotations外部 Agent 可以区分只读工具和写入工具
tools/call read_note 成功/失败都结构化MCP 层复用 ToolService 的安全边界

memory 脚本证明记忆写入边界:

检查证明
/API/memories 写入并查询memory 不是只存在内存里的临时变量
write_memory/list_memories 工具调用外部 Agent 能通过 tool contract 使用记忆
data/ 被 git 忽略demo 运行数据不会污染笔记仓库

trace 脚本证明复盘入口一致:

检查证明
/API/chat 产生 traceId每次回答都有复盘入口
/API/traces/{traceId} 读回 traceHTTP 层能复盘一次请求
get_trace tool / MCP callTool/MCP 层复用同一份 trace store

debug 脚本证明 trace 能转成诊断:

检查证明
/API/chat 产生拒答 tracedemo 有可复现失败样例
/API/traces/{traceId}/debug 返回 issue=no_sourcestrace 能定位到检索证据不足
debug_trace tool / MCP call外部 Agent 能拿到同一份诊断报告

边界

这个 demo 暂时不做真实 LLM、向量库、数据库、完整生产 MCP 会话/resources/prompts 和任意文件写入工具。当前目标是先证明一条可测试、可复盘、可评测、可工具化、可 MCP 包装的后端链路:

代码块TEXT · 7 行收起展开
HTTP request -> AgentService -> NoteSearchService -> grounded answer/refusal -> TraceService
EvalCase -> EvalService -> AgentService -> pass/fail reasons
ToolCallRequest -> ToolService -> search/read/trace/debug/eval -> ToolCallResponse
McpRequest -> McpService -> ToolService -> McpResponse
MemoryWriteRequest -> MemoryService -> data/memories.jsonl
TraceRecord -> TraceService -> data/traces.jsonl
TraceRecord -> DebugReportService -> DebugReport