新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java + Spring实现Hermes Agent:龙虾、Skills、Mcp与沙箱代码执行环境配置思路

发布时间:2026/11/12 20:44:21来源:尧图网络
Java + Spring实现Hermes Agent:龙虾、Skills、Mcp与沙箱代码执行环境配置思路
1. 为什么要在 Java Spring 里搭 Hermes AgentHermes Agent 是一套把「记忆、任务调度、技能热插拔、MCP 工具、沙箱执行」拼在一起的 Agent 运行骨架。放到 Java Spring 技术栈里它要解决的核心问题是让一个 Spring Boot 服务既能跟大模型对话又能在对话之外记住事实、定时干活、按需加载技能包、复用外部工具生态并且把模型生成的代码关进隔离环境里跑。适合谁适合已经有一套 Spring Boot 后端、想在上面长出 Agent 能力、又不想把整套东西重写成 Python 的团队。我把它拆成四个模块来落地龙虾记忆管理短期会话历史 长期事实清单、Skills按请求热插拔的技能包、Mcp复用外部工具协议、沙箱代码执行环境把 Bash / Read / Write / Edit 关进容器。这四个模块共享一个 workspace 目录短期记忆由框架兜底写长期记忆由模型自己用文件工具维护Skills 和沙箱通过 seeding 把脚本喂进隔离环境Mcp 则按请求开短连接拿工具回调。整条链路里模型看到的工具签名始终是 Spring AI 的Tool隔离边界藏在方法实现里业务代码不用改。下面按「前置准备 → 可复制配置 → 验证请求 → 排错」的顺序走一遍配置骨架可以直接抄。2. TaoToken 前置统一 Key 与 API 通道在接 Mcp 和模型之前先把模型通道统一掉。TaoToken 提供 OpenAI 兼容的 API 通道一个 Key 可以走多个模型省得每个 provider 配一套 base-url 和鉴权。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 这个不加 UTM。你需要先拿到 API Key再去控制台确认通道可用。拿 Key 的入口在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你后面要长期跑编码类 Agent可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。注意Key 只放在服务端环境变量或配置中心别写进前端请求体也别提交到仓库。Mcp 的鉴权头同理走服务端注入。拿到 Key 之后Spring AI 这边直接复用 OpenAI starter把 base-url 指到 TaoToken 的 API 地址即可。这样ChatModel、ChatClient、advisor、tool call、memory 这一整套都能直接用不用自己实现协议。3. 可复制配置Spring Boot 骨架 Mcp settings.json3.1 application.yml 骨架spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: deepseek-chat temperature: 0.3 chat: workspace: ${user.home}/hermes-workspace sandbox: mode: LOCAL # LOCAL / DOCKER image: ghcr.io/spring-ai-community/agents-runtime:latest mcp: request-timeout-seconds: 30workspace是四个模块共享的根目录短期会话历史落在{workspace}/conversations/长期事实清单落在{workspace}/AGENT.md和{workspace}/memories/*.md。迁机器时把整个 workspace 拷过去就行。3.2 短期记忆文件版 ChatMemoryRepositorySpring AI 自带的InMemoryChatMemoryRepository进程一重启就清空做 Agent 不够用。写一个落到 YAML 的实现每个会话一个文件Component public class FileSystemChatMemoryRepository implements ChatMemoryRepository { private final Path conversationsDir; public FileSystemChatMemoryRepository(Value(${chat.workspace}) String workspace) { this.conversationsDir Path.of(workspace, conversations); } Override public ListMessage findByConversationId(String id) { Path f conversationsDir.resolve(chat- id .yaml); return Files.exists(f) ? ChatYamlSerializer.deserialize(YamlParser.parse(Files.readString(f)).body()) : List.of(); } Override public void saveAll(String id, ListMessage msgs) { // 写 frontmatter body只追加增量 } Override public void deleteByConversationId(String id) { // 删文件 } }conversationId直接用通道名web、telegram-123、discord-456多通道天然隔离。这里有个坑Spring 原生的MessageWindowChatMemory内部用HashSet会把消息顺序打乱DeepSeek 这类对顺序敏感的模型会直接报错。换成LinkedHashSet保留顺序并且把窗口化从写入侧挪到读取侧——磁盘留全量给模型时再截最近 N 条。3.3 长期记忆让模型自己维护 AGENT.md长期记忆不专门搞 MemoryTool复用 Read / Write / Edit 三个通用文件工具让模型自己在 workspace 里维护事实清单。装配时把FileSystemTools给模型MessageChatMemoryAdvisor给框架ChatClient.builder(chatModel) .defaultSystem(p - p.text(agentPrompt).param(WORKSPACE, workspace)) .defaultTools(FileSystemTools.builder().build()) .defaultAdvisors( ToolCallAdvisor.builder().build(), MessageChatMemoryAdvisor.builder(chatMemory).build() ) .build();Tool的 description 就是给模型看的说明书Spring AI 会把它拼进 JSON Schema 发给模型写不写得清楚直接决定模型用不用得对。Write 的描述里要把「先 Read 再 Write」「不要主动创建文档文件」这些约束讲明白。3.4 Mcp 接入settings.json 示例Mcp 按请求开短连接请求里带mcpConfig服务端 connect → initialize → 拿 callbacks → 喂给模型 → 请求结束 close。settings.json 示例{ mcpServers: { github: { url: https://mcp.example.com/github/mcp, headers: { Authorization: Bearer ${GITHUB_TOKEN} } }, brave-search: { url: https://mcp.example.com/brave?key${BRAVE_KEY} } } }构建逻辑放在DynamicMcpClientFactory里每请求出一个McpSessionpublic McpSession build(MapString, McpServerConfig mcpConfig) { ListMcpSyncClient clients new ArrayList(); for (var entry : mcpConfig.entrySet()) { var cfg entry.getValue(); var transport HttpClientStreamableHttpTransport .builder(originOf(cfg.url())) .endpoint(pathOf(cfg.url())) .httpRequestCustomizer((req, m, ep, body, ctx) - cfg.headers().forEach(req::header)) .build(); McpSyncClient client McpClient.sync(transport) .requestTimeout(Duration.ofSeconds(30)) .build(); client.initialize(); clients.add(client); } return new McpSession(clients); }McpSession实现AutoCloseable同步接口用 try-with-resources流式接口在doFinally里关。多个 server 中某一个 initialize 失败时要把已经打开的 client 都closeGracefully再抛异常不能留半开状态。3.5 沙箱执行环境模型生成的 shell 或 Python 代码绝对不能直接在宿主机上跑。做法是本地Tool方法加沙箱执行环境工具签名还是用 Spring AI 的Tool暴露给模型方法实现里不直接Runtime.exec而是把命令通过统一的Sandbox接口转出去Tool(name Bash, description Execute a bash command inside an isolated sandbox container. Use for terminal ops like npm/pip/python/mvn; NOT for file IO. Skill files live under ./skills/name/. ) public String bash(ToolParam String command, ToolParam(required false) Long timeout) { ExecSpec spec ExecSpec.builder() .command(bash, -lc, command) .timeout(timeoutOf(timeout)) .env(envOverrides) .build(); ExecResult r sandbox.exec(spec); return formatForLlm(r); }切后端就是配置一行的事Sandbox sandbox switch (props.getMode()) { case DOCKER - DockerSandbox.builder().image(props.getImage()).build(); case LOCAL - LocalSandbox.builder().tempDirectory(chat-sandbox-).build(); };平时开发用 LOCAL 起得快生产或跑不可信 skill 就切 DOCKER。沙箱按请求级 try-with-resources 管每次/chat或/chat/stream开一个、结束关一个别在进程里复用。4. 验证请求从对话到工具调用配置搭好后先验证模型通道通不通。用模型对话入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 发一条消息确认 Key 和 base-url 没问题。然后在 Spring Boot 里发一个带 Skills 和 Mcp 的请求curl -N -X POST http://localhost:8080/chat/stream \ -H Content-Type: application/json \ -d { userId: 1001, assistantId: 7, sessionId: s-xxx, query: 把附件 csv 画成折线图, skills: [ {name: chart-maker, url: https://cdn.example.com/skills/chart-maker-0.3.zip} ], mcpConfig: { github: { url: https://mcp.example.com/github/mcp, headers: {Authorization: Bearer ghp_xxx} } } }预期看到的事件序列类似event: reasoning {text: 让我先查一下...} event: tool_call [{id: c1, name: Bash, args: ls skills/}] event: tool_result [{id: c1, name: Bash, result: chart-maker}] event: token {text: 找到了 chart-maker我用它来画图...}tool_call和tool_result这两个事件需要额外处理。Spring AI 的ToolCallAdvisor打开streamToolCallResponses(true)后含 toolCalls 的中间响应会透传tool_call好转但工具执行结果默认只进下一轮 conversationHistory不会作为独立 chunk 发出来。解法是装饰一层ToolCallingManager在executeToolCalls后把本轮工具响应旁路到一个 sinkclass ObservableToolCallingManager implements ToolCallingManager { private final ToolCallingManager delegate; private final Sinks.ManyChatEvent sink; Override public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse resp) { ToolExecutionResult result delegate.executeToolCalls(prompt, resp); try { ListChatEvent.ToolResultRef refs extractToolResponses(result); if (!refs.isEmpty()) sink.tryEmitNext(ChatEvent.toolResult(refs)); } catch (RuntimeException e) { log.warn(emit tool_result failed: {}, e.getMessage()); } return result; } }装配时把它喂给ToolCallAdvisor再把 sink 跟主流 Flux 合一下ChatClient.create(chatModel).prompt().user(req.query()) .advisors(ToolCallAdvisor.builder() .toolCallingManager(observable) .streamToolCallResponses(true) .build()) .stream().chatResponse() .mergeWith(toolEventSink.asFlux()) .map(this::toEvent);5. 本篇常见错排查Mcp URL 带 query string 被吞掉。MCP SDK 内部走URI.resolve(base, endpoint)如果 endpoint 以/开头会把 base 上的?keyxxx直接吞掉。把 URL 拆成 origin 和相对 endpoint query 再喂给 builder 才能解决。Mcp 鉴权 401。鉴权要走 headers 字段配合httpRequestCustomizer注入每次 POST 都带上否则 server 端直接 401。Mcp 连接泄漏。MCP 走 HTTP 长流或 stdio 子进程忘了 close 会泄漏连接和进程。McpSession实现AutoCloseable同步接口用 try-with-resources流式接口在doFinally里关。沙箱环境变量丢失。Docker 走ExecSpec.env对应docker exec -eLocal 模式还得在命令前加export ...绕开bash -lc的 login profile 和 WSLENV 白名单导致的变量丢失。skill 文件 seed 进沙箱时二进制损坏。skill 默认是脚本加 Markdown 这类文本遇到超过阈值或读不出 UTF-8 的直接 skip 加 warn免得SandboxFiles.create把二进制损坏。seeding 过程中抛异常要立刻把已创建的 sandbox close 掉不要留孤儿容器或临时目录。消息顺序错乱。前面提过MessageWindowChatMemory内部HashSet会打乱顺序换成LinkedHashSet并把窗口化挪到读取侧。模型路由不生效。写一个ModelRouter按 modelName 前缀路由到不同 Bean/chat/stream入口拿请求里的 modelName 解析一下就行。每个 provider 自己的Bean配置照常写路由这层只是个 switch。6. 继续往下走四个模块跑通之后下一步是把任务调度接进来。用 JobRunr 做长期任务模型用工具调用TaskTool创建/调度任务TaskManager落库并往JobScheduler塞一条到点了 JobRunr 反序列化 lambda、回调TaskHandler.executeTask(taskId)执行。Job(retries 3)一行就能让失败自动重试三次。注意taskId而不是整个 Task 对象作为参数JobRunr 要把 lambda 序列化进存储参数得是简单可序列化的值。如果你要长期跑编码类 AgentCoding Plan 那条通道更适合 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入过程中遇到 Mcp 或沙箱的问题先翻接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 再对着 API Keys 页面确认通道状态 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。控制台在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 可以看调用记录和用量。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

【外设】之大彩串口显示屏 2026/11/13 0:43:49

【外设】之大彩串口显示屏

大彩串口屏初步使用 1 .官网下载 STM32 屏幕 GUI 设计资料 http://www.gz-dc.com/category/typeid/4112 找到 STM32 Keil 工程,移植相关代码因项目而异进行移植,由于项目简单,本人只对用到的指令接口进行修改。 比如:注意事项&…

阅读更多 →
无法下载Windows系统iso文件 2026/11/12 23:59:09

无法下载Windows系统iso文件

当我遇到这个问题的时候,我打开了一个网站: 登录 然后我打算下载的时候: 突然那个官方的连接就可以下载了:

阅读更多 →
【清华代码熊】DeepSeek V4.1 Flash 后训练详解 2026/11/13 10:09:00

【清华代码熊】DeepSeek V4.1 Flash 后训练详解

📌 上期解析了 DeepSeek V4.1 Flash 模型架构改进,本期解析 DeepSeek V4.1 Flash 预训练/后训练技术: 🌟 预训练:45T 文本 多模态混合语料、直接训练 sparse attention(取消 DeepSeek V4 的 dense 冷启动&…

阅读更多 →
Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ... 2026/11/13 13:31:48

Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ...

文章主要内容和创新点 主要内容 本文聚焦于多模态大语言模型(MLLM)强化学习(RL)训练中的效率问题,提出了一个名为Shuffle-R1的框架。研究发现,当前RL训练存在两个关键缺陷: 优势值坍缩(Advantage Collapsing):批次中大多数优势值集中在零附近,导致有效梯度信号被淹…

阅读更多 →
PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction 2026/11/12 15:57:44

PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction

一、文章主要内容总结 本文聚焦于利用大型语言模型(LLMs)实现个人身份信息(PII)脱敏的研究,旨在解决传统脱敏方法(如基于规则的系统、领域特定命名实体识别(NER)模型)泛化能力差、跨格式/跨语境适应性弱的问题。 研究通过全面评估多种LLM架构(包括密集型LLM(D-LLM…

阅读更多 →
LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model 2026/11/13 0:02:27

LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model

文章主要内容和创新点 主要内容 本文聚焦于二进制图像-文本相关性评估任务(判断图像与文本“相关”或“不相关”),针对该任务中文本格式多样、相关性定义随场景变化等挑战,提出了基于多模态大语言模型(MLLM)的解决方案LLaVA-RE。 模型设计:LLaVA-RE基于LLaVA 1.5架构,…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉