当前位置: 首页 > article >正文

Java + Spring实现Hermes Agent之龙虾、Skills、Mcp和沙箱代码执行环境思路

一、记忆管理短期 长期融合Spring AI 自带的InMemoryChatMemoryRepository进程一重启就清空了做 Agent 显然不够用。我们参考了 JavaClaw 和 Claude Code 的做法把记忆分成两层都落到同一个 workspace 目录下层级谁来写落在哪里给模型的方式短期会话历史框架Advisor自动写{workspace}/conversations/chat-{channel}.yamlMessageChatMemoryAdvisor每轮注入历史长期跨会话事实暴露Read/Write/Edit工具 模型写{workspace}/AGENT.md、{workspace}/memories/*.md在 system prompt 里告诉它去哪读按需Read融合点就是这个共享的 workspace。短期由框架兜底每条消息进来都自动追加长期由模型自己决定什么时候要写怎么组织文件名。两层互不打架。短期写一个文件版ChatMemoryRepository每个会话一个 YAML 文件frontmatter 记时间body 是消息列表编辑器里也能直接打开看--- createdAt: 2026-03-21T10:00:00Z updatedAt: 2026-03-21T10:05:30Z --- - user: | 今天北京天气怎么样 - assistant: | 今天北京晴气温 18~26℃。实现就是 Spring AI 的ChatMemoryRepository接口。可以参考Ref/JavaClaw/.../FileSystemChatMemoryRepository.java核心方法长这样Component public class FileSystemChatMemoryRepository implements AppendableChatMemoryRepository { private final Path conversationsDir; // {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 appendAll(String id, ListMessage msgs) { // 只追加增量避免每次都把整段历史读出来再写回去 saveAll(id, Stream.concat(findByConversationId(id).stream(), msgs.stream()).toList()); } Override public void saveAll(String id, ListMessage msgs) { /* 写 frontmatter body */ } Override public void deleteByConversationId(String id) { /* 删文件 */ } }这里conversationId我们直接用通道名web、telegram-123、discord-456多通道之间天然就隔离开了迁机器只要把conversations/拷过去就行。另外JavaClaw 还顺手 fork 了 Spring 原生的MessageWindowChatMemory内部HashSet换成LinkedHashSet保留顺序并且把窗口化从写入侧挪到读取侧——磁盘上留全量给模型时再截最近 N 条。生产里建议照抄原版那个HashSet会把消息顺序打乱DeepSeek 这类对消息顺序敏感的模型会直接报错。长期让模型自己用FileSystemTools维护记忆JavaClaw 这边没有专门搞一个 MemoryTool思路是复用 Read / Write / Edit 这些通用文件工具让模型自己在 workspace 里维护AGENT.md写成 Claude Code 风格的事实清单{workspace}/ ├── AGENT.md 长期事实清单开机时让模型读一下 ├── conversations/ │ ├── chat-web.yaml 短期Web 通道历史 │ └── chat-telegram-123.yaml └── memories/ 可选分类别的记忆文件 ├── user_profile.md └── project_q2.md装配也没多少东西关键就是FileSystemTools给模型MessageChatMemoryAdvisor给框架ChatClient.builder(chatModel) .defaultSystem(p - p.text(agentPrompt) .param(WORKSPACE, workspace)) // 告诉模型 workspace 在哪 .defaultTools(FileSystemTools.builder().build()) // 注册 Read/Write/Edit 三个工具给模型 .defaultAdvisors( ToolCallAdvisor.builder().build(), MessageChatMemoryAdvisor.builder(chatMemory).build() // 短期由 Advisor 接管 ) .build();这里的FileSystemTools是 spring-ai-community 的库内部用Tool注解把Read/Write/Edit三个方法暴露给模型。Tool的description就是给模型看的使用说明书Spring AI 会把这段 description 拼到工具的 JSON Schema 里发给模型所以写不写得清楚直接决定模型用不用得对。Write大致长这样Tool(name Write, description Writes a file to the local filesystem. Usage: - This tool will overwrite the existing file if there is one at the provided path. - If this is an existing file, you MUST use the Read tool first to read the files contents. This tool will fail if you did not read the file first. - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required. - NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User. - Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked. ) public String write( ToolParam(description The absolute path to the file to write (must be absolute, not relative)) String filePath, ToolParam(description The content to write to the file) String content) { // ... 真正落盘如果没有沙箱直IO接写有沙箱就用沙箱提供的操作方法。 }Read 和 Edit 同理描述里把什么时候用、参数怎么填、有什么限制讲明白即可。 .defaultTools(FileSystemTools.builder().build()) 这一行就把这三个工具加进了 ChatClient 可见的工具列表模型这边看到的就跟任何普通函数工具一样。 调用时带上 conversationId短期历史就会自动追加到对应 YAML java chatClient.prompt(question) .advisors(a - a.param(ChatMemory.CONVERSATION_ID, telegram-123)) .call().content();短期记忆是MessageChatMemoryAdvisor在每轮请求前自动把消息追加到 YAML不需要模型同意、模型也不知道。长期记忆刚好反过来——是模型在对话里觉得这个事实值得记一下自己发起一次工具调用Write(AGENT.md, ...)Spring AI 的工具调用循环再把这次调用路由到FileSystemTools.write真正写文件。框架在这条路径上只是传话筒记什么、什么时候记决定权在模型自己。二、任务调度用 JobRunr 接长期任务记忆解决了它记得但 Agent 还差一块——它能在你不在的时候干活。比如每天早上 9 点帮我把昨天的日志汇总一下发到 Telegram或者30 分钟后提醒我开会。一次性、定时、cron 周期这些都不是会话内能搞定的得有个真正的调度器。JavaClaw 选了 JobRunr。我们看了一圈下来也觉得它对 Agent 场景挺合适任务用 lambda 表达式调度x - x.executeTask(taskId)JobRunr 帮你做序列化、持久化、重启恢复自带 dashboard能看到队列里有什么、跑过什么、失败了几次Job(retries N)一行加重试跟 Spring Boot 集成顺JobScheduler直接注入整体结构是三层模型用工具调用TaskTool创建/调度任务 →TaskManager落库并往JobScheduler塞一条 → 到点了JobRunr反序列化 lambda、回调TaskHandler.executeTask(taskId)执行。TaskManager这层薄到不行Component public class TaskManager { private final JobScheduler jobScheduler; private final TaskRepository taskRepository; public void create(String name, String desc) { // 立即执行 Task task taskRepository.save(Task.newTask(name, desc)); jobScheduler.TaskHandlerenqueue(x - x.executeTask(task.getId())); } public void schedule(LocalDateTime when, String name, String desc) { // 一次性定时 Task task taskRepository.save(Task.newTask(name, desc)); jobScheduler.TaskHandlerschedule(when, x - x.executeTask(task.getId())); } public void scheduleRecurrently(String cron, String name, String desc) { // cron 周期 RecurringTask task taskRepository.save(RecurringTask.newRecurringTask(name, desc)); jobScheduler.RecurringTaskHandlerscheduleRecurrently( task.getName(), cron, x - x.executeTask(task.getId())); } }到点真正干活的是TaskHandler.executeTask——拿到 taskId从仓库捞出任务描述喂给 Agent 自己处理写回状态。Job(retries 3)一行就能让 JobRunr 在失败时自动重试三次Component public class TaskHandler { Job(name %0, retries 3) public void executeTask(String taskId) { Task task taskRepository.getTaskById(taskId); Task inProgress taskRepository.save(task.withStatus(Status.in_progress)); try { TaskResult result agent.prompt(taskId, formatTaskForAgent(inProgress), TaskResult.class); taskRepository.save(inProgress.withFeedback(result.feedback()) .withStatus(result.newStatus())); notifyUser(task.getName(), result); // 通过 ChannelRegistry 推回去 } catch (Exception e) { taskRepository.save(inProgress.withStatus(Status.todo)); // 失败回滚到 todo让 retry 重新跑 throw e; } } }注意一个小细节taskId而不是整个Task对象作为参数。JobRunr 要把这条 lambda 序列化进存储里参数得是简单可序列化的值存 ID、跑的时候再去仓库捞是更稳的做法。剩下就是把任务能力暴露给模型——一个TaskTool三个Tool方法分别对应createTask/scheduleTask/scheduleRecurringTask描述里把什么时候用、参数怎么填写清楚就行Tool(description Schedules a task using JobRunr that repeats at regular intervals based on a cron expression. Use this for recurring activities like daily reports, weekly checks, etc. - cronExpression: standard cron, e.g. 0 12 * * * for daily at noon - name: short identifier (e.g. weekly-log-cleanup) - description: what the task should do ) public String scheduleRecurringTask(String cronExpression, String name, String description) { taskManager.scheduleRecurrently(cronExpression, name, description); return Task name scheduled with cron cronExpression .; }用户说每天早上九点帮我同步一下昨天的 commits模型自己就会拼出 cron 表达式调scheduleRecurringTask。到点 JobRunr 触发TaskHandler让 Agent 真正执行结果通过通道推回给用户。几个值得注意的点任务的 conversationId 和聊天会话的 conversationId 不是一回事——JavaClaw 这边直接拿taskId当 Agent prompt 的 conversationId意思是这条任务有自己独立的对话上下文不会跟用户的实时聊天混在一起。当然代价是任务结果回推时没有原始会话的上下文注释里也标了 TODO。JobRunr 默认用 H2 存储就能跑生产环境换 Postgres / MySQL / Mongo 都行存储层是插拔的。如果要做集群多个实例共享同一个存储JobRunr 自己会做 leader 选举和分片业务代码不用变。dashboard 默认在/dashboard部署到生产记得加鉴权或者关掉里面能看到所有任务的执行历史和栈。三、Skills 动态热插拔Skill 我们没有写死成 Spring Bean而是当成一包跟着请求进来的资源。请求里带一组name url服务端按userId/assistantId/sessionId分桶把 zip 下载下来、解压、喂给SkillsTool让模型看见元数据和Sandbox把脚本本体 seed 进容器。同一个 ChatClient 每次请求按需重新组装一遍工具集不用重启进程就能切换能力。请求长这样POST /chat/stream { userId: 1001, assistantId: 7, sessionId: s-xxx, query: 把附件 csv 画成折线图, skills: [ { name: pdf-extractor, url: https://cdn.example.com/skills/pdf-extractor-1.2.zip }, { name: chart-maker, url: https://cdn.example.com/skills/chart-maker-0.3.zip } ] }切技能就是改这个skills数组前端可以做成一个勾选列表用户点哪个就带哪个。/chat/stream里大致是这么组装的public FluxChatEvent streamChat(ChatRequest req) { // 1. 按 user/assistant/session 分桶下载和解压命中缓存直接复用 ListResource skillDirs skillCache.resolve( req.userId(), req.assistantId(), req.sessionId(), req.skills()); // 2. 有 skill 才起沙箱并把 skill 文件 seed 进去 Sandbox sandbox skillDirs.isEmpty() ? null : sandboxFactory.create(skillDirs); // 3. SkillsTool 暴露目录元数据让模型自己决定读哪一个 ToolCallback[] skillTools skillDirs.isEmpty() ? new ToolCallback[0] : new ToolCallback[]{ SkillsTool.builder().addSkillsResources(skillDirs).build() }; // 4. 每请求新建 spec工具集 内置 沙箱 skill MCP var spec ChatClient.create(chatModel).prompt().user(req.query()); if (sandbox ! null) { spec.tools(new SandboxBashTool(sandbox, ...), new SandboxFileSystemTools(sandbox), finalAnswerTool); } spec.toolCallbacks(skillTools); // 5. 流式返回结束时关沙箱 return spec.stream().chatResponse() .map(this::toEvent) .doFinally(s - { if (sandbox ! null) sandbox.close(); }); }下载skills缓存目录我们按用户ID/助手ID/对话窗口ID三段分桶.skillDirs为空时干脆不起沙箱纯文本对话不用付容器启动开销。SkillsTool本身只暴露元数据脚本本体始终在沙箱里跑宿主机不会被 skill 触达。至于下载失败的容错——单个 skill 下载或解压失败不应该整次请求都挂掉记一条 warn 跳过就好其他 skill 继续生效全都失败就退化成纯文本对话。四、MCP让 Agent 复用外部生态的工具MCPModel Context Protocol 是 Anthropic 推的工具协议层。服务端按协议暴露 tools / resources / prompts客户端连上就能用自己这边不用再写一遍工具适配代码。Spring AI 1.0 给了McpSyncClient和SyncMcpToolCallbackProvider能把任意 MCP server 的工具一键转成ToolCallback[]塞进ChatClient就能让模型调用。GitHub、Slack、Filesystem、Playwright 这些现成 server 拿来即用。我们的做法是按请求开一组短连接——请求里带mcpConfig服务端 connect → initialize → 拿 callbacks → 喂给模型 → 请求结束 close。不在进程里长连避免连接泄漏也方便用户随时切 server。POST /chat/stream { query: 查一下仓库 spring-ai 最近的 issue并搜一下相关网页, mcpConfig: { github: { url: https://mcp.example.com/github/mcp, headers: { Authorization: Bearer ghp_xxx } }, brave-search: { url: https://mcp.example.com/brave?keyxxx } } }模型这一侧看到的就是 GitHub 加 Brave 两个 server 暴露的工具合集按需调。构建逻辑放在一个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(); // 握手并拉 tool 列表 clients.add(client); } return new McpSession(clients); } public static final class McpSession implements AutoCloseable { private final ListMcpSyncClient clients; public ToolCallback[] getToolCallbacks() { return SyncMcpToolCallbackProvider.builder() .mcpClients(clients).build() .getToolCallbacks(); // 合并多 server 的工具 } Override public void close() { clients.forEach(McpSyncClient::closeGracefully); } }/chat/stream里和 skill 工具拼到一起就行try (McpSession mcp mcpFactory.build(req.mcpConfig())) { spec.toolCallbacks(mcp.getToolCallbacks()); return spec.stream().chatResponse().map(...); }这块当时踩过几个坑值得提一下。一个是 URL 带 query string 的问题。MCP SDK 内部走的是URI.resolve(base, endpoint)如果 endpoint 以/开头会把 base 上的?keyxxx直接吞掉。后来我们把 URL 拆成 origin 和相对endpointquery再喂给 builder 才解决。第二个是鉴权要走headers字段配合httpRequestCustomizer注入每次 POST 都带上否则 server 端 401。第三个是连接生命周期——MCP 走 HTTP 长流或 stdio 子进程忘了 close 会泄漏连接和进程。所以McpSession实现了AutoCloseable同步接口用 try-with-resources流式接口在doFinally里关。最后是多个 server 中某一个 initialize 失败的兜底要把已经打开的 client 都closeGracefully再抛异常不能留半开状态。五、沙箱代码执行环境模型生成的 shell 或 Python 代码绝对不能直接在宿主机上跑一条rm -rf就足够把服务搞掉。常规做法是把执行能力封到容器里。但 Spring AI 的Tool注解又特别顺手不想为了沙箱就放弃这一套工具描述方式。我们的做法是本地Tool方法加沙箱执行环境分工大致是LLM ──tool_call──► 本地 Tool 方法 (Bash / Read / Write / Edit) │ ▼ Sandbox.exec(...) ← spring-ai-community/agent-sandbox │ ▼ LocalSandbox / DockerSandbox / E2BSandbox (进程 / 容器 / 云端 microVM)工具签名、描述、参数 Schema 还是用 Spring AI 的Tool暴露给模型方法实现里不直接Runtime.exec而是把命令通过统一的Sandbox接口转出去。后端跑在哪里——本机进程、Docker、还是 E2B microVM——只是个配置开关业务代码完全不用改这是 agent-sandbox 那套 API 给我们的。跟 Skills 配合也很自然SandboxFactory把 skill 目录从宿主机复制到沙箱内部的skills/name/路径下模型在Bash里直接python skills/pdf-extractor/run.py就能跑。这一步我们叫seeding就像数据库 seed data 那个意思沙箱起来是空的得先把种子文件埋进去——后面提到这个词都是指这件事。Tool这层非常薄public class SandboxBashTool { private final Sandbox sandbox; private final Duration defaultTimeout; private final MapString, String envOverrides; 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 — Read/Write/Edit have their own tools. 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) // userId / apiKey 等机密参数的透传类似于Tool里面的ToolContext .build(); ExecResult r sandbox.exec(spec); // 真正的隔离边界 return formatForLlm(r); // stdout/stderr/exitCode 拼一下截到 30k } }Read/Write/Edit同理都是Tool方法里直接调sandbox.files()的 APITool(name Read, description ...) public String read(...) { return sandbox.files().read(path); } Tool(name Write, description ...) public String write(...) { sandbox.files().create(path, content); ... } Tool(name Edit, description ...) public String edit(...) { /* read → replace → write */ }模型这一侧看到的还是Bash / Read / Write / Edit四个常规工具完全感觉不出来后面是个容器。切后端就是配置一行的事# application.yml chat: sandbox: mode: DOCKER # LOCAL / DOCKER (E2B 同理可扩) image: ghcr.io/spring-ai-community/agents-runtime:latestSandbox sandbox switch (props.getMode()) { case DOCKER - DockerSandbox.builder().image(props.getImage()).build(); case LOCAL - LocalSandbox.builder().tempDirectory(chat-sandbox-).build(); };平时开发用LOCAL起得快、调试方便生产或者跑不可信 skill 就切DOCKER要更强隔离的话可以接E2BSandbox云端 Firecracker microVM文档在 agent-sandbox 上。几点经验沙箱按请求级 try-with-resources 来管每次/chat或/chat/stream开一个、结束关一个别在进程里复用容器会越攒越多。流式接口同样在doFinally里关掉。LOCAL模式我们留着是给开发用的但启动时要打 warn——它没有任何隔离别哪天被人误用到生产去。环境变量在两种模式下要分两路注入。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 掉不要留一个孤儿容器或临时目录。六、可观察性让用户看见 Agent 每一步Agent 跑工具调用经常一轮接一轮要是只把最终回答推给前端用户那边就是十几秒甚至几十秒的空白体验很差出问题也没法排查。我们的做法是把整个工具调用循环里发生的事情都拆成事件吐到 SSE 流里——token 在出、思考在写、工具被调了、工具返回了什么前端按事件类型渲染就行。事件类型在ChatEvent里固定了几种public static ChatEvent token(String text) { /* 流式 token */ } public static ChatEvent reasoning(String text) { /* DeepSeek 这类的思考过程 */ } public static ChatEvent toolCall(ListToolCallRef c) { /* 模型决定调哪些工具 */ } public static ChatEvent toolResult(ListToolResultRef r) { /* 工具执行完返回了什么 */ }前两个 Spring AI 默认就在流里给了不用额外操心。麻烦的是后两个——尤其是tool_result。Spring AI 的ToolCallAdvisor把streamToolCallResponses(true)打开之后含toolCalls的中间ChatResponse会透传出来所以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); // 从 history 末尾捞 ToolResponseMessage if (!refs.isEmpty()) sink.tryEmitNext(ChatEvent.toolResult(refs)); } catch (RuntimeException e) { log.warn(emit tool_result failed: {}, e.getMessage()); // 观测不能影响主流程 } return result; } }装配的时候把它喂给ToolCallAdvisor然后把 sink 跟主流Flux合一下Sinks.ManyChatEvent toolEventSink Sinks.many().unicast().onBackpressureBuffer(); ToolCallingManager observable new ObservableToolCallingManager( ToolCallingManager.builder().build(), toolEventSink); ChatClient.create(chatModel).prompt().user(req.query()) .advisors(ToolCallAdvisor.builder() .toolCallingManager(observable) .streamToolCallResponses(true) // 含 toolCalls 的中间响应也透出来 .build()) .stream().chatResponse() .mergeWith(toolEventSink.asFlux()) // 主流 旁路 sink 合并 .map(this::toEvent);前端就能拿到一条完整的事件序列类似这样(Skills和mcp在spring ai里面也是工具触发都可以显示)event: reasoning {text: 让我先查一下...} event: tool_call [{id: c1, name: Bash, args: ls skills/}] event: tool_result [{id: c1, name: Bash, result: chart-maker\npdf-extractor}] event: token {text: 找到了两个 skill} event: token {text: 我用 chart-maker...}前端展示效果如下这样更像智能体了常见的 QAQSpring AI 自带的 Starter 没覆盖到我想用的模型怎么扩展两条路。简单的一条是套一层 OpenAI 兼容格式——大部分国产模型千问、智谱、豆包、DeepSeek 自家也是都提供 OpenAI 兼容端点直接复用spring-ai-starter-model-openai把base-url改一下就能跑。如果是私有协议或者要做特殊的请求/响应改写那就自己实现ChatModel加StreamingChatModel。我们项目里就有一个签名是这样public class MyModelChatModel implements ChatModel, StreamingChatModel { Override public ChatResponse call(Prompt prompt) { ... } Override public FluxChatResponse stream(Prompt prompt) { ... } }两个方法填进去剩下ChatClient那一整套advisor、tool call、memory就都能复用不用动其它代码。Q怎么根据请求参数动态切换模型我们这边的做法是写一个ModelRouter按modelName前缀路由到不同的 Beanpublic ChatModel resolve(String modelName) { String lower modelName null ? : modelName.toLowerCase(); if (lower.startsWith(claude)) return anthropicChatModel; if (lower.startsWith(deepseek-chat)) return deepSeekChatModel; if (lower.startsWith(qwen) || lower.startsWith(glm) || lower.startsWith(doubao) || lower.startsWith(openai-compatible)) return myModelChatModel; return openAiChatModel; // 默认走 OpenAI }然后/chat/stream入口拿请求里的modelName解析一下就行ChatModel chatModel modelRouter.resolve(req.modelName()); ChatClient.create(chatModel).prompt().user(req.query())...每个 provider 自己的Bean配置照常写路由这层只是个switch。前端切模型 改请求体一个字段服务端不重启。QSpring AI 能上生产吗可以。1.0 GA 已经发了很长时间已经相当稳定。开始Java没有成熟的Agent框架用的Langchain。现在我们也从Langchain迁移回Spring Ai了。这套也是直接跑在生产上的。生态这一年长得很快——Anthropic、OpenAI、DeepSeek、Google、Ollama、各家国产兼容端点基本都有 starterMCP、向量库、observability 也都接上了社区还有 spring-ai-community 那一摞 utils 可以挑着用。Spring AI 2.0 马上也快 GA 了可以期待一下。

相关文章:

Java + Spring实现Hermes Agent之龙虾、Skills、Mcp和沙箱代码执行环境思路

一、记忆管理:短期 长期融合 Spring AI 自带的 InMemoryChatMemoryRepository 进程一重启就清空了,做 Agent 显然不够用。我们参考了 JavaClaw 和 Claude Code 的做法,把记忆分成两层,都落到同一个 workspace 目录下&#xff1a…...

反诈渗透测试实战:绕过人的决策链而非系统漏洞

1. 这不是黑客炫技,而是一次真实的反诈防线压力测试 “我们刚上线的反诈预警弹窗,被内部员工用三分钟绕过了。” 这句话是我在某地市反诈中心做驻场支持时,接到的第一通电话。不是红蓝对抗演练通知,不是安全培训课件里的假设场景…...

如何用Flut Renamer高效管理文件:跨平台批量重命名完整指南

如何用Flut Renamer高效管理文件:跨平台批量重命名完整指南 【免费下载链接】renamer Flut Renamer - A bulk file renamer written in flutter (dart). Available on Linux, Windows, Android, iOS and macOS. 项目地址: https://gitcode.com/gh_mirrors/ren/ren…...

Android HTTPS抓包原理与HTTPCanary证书配置全解

1. 这不是“绕过”,而是理解Android HTTPS抓包的底层逻辑HTTPCanary 是 Android 平台上少有的、真正能稳定抓取 HTTPS 流量的本地代理工具。但几乎所有新手在首次使用时都会卡在同一个地方:明明安装了 HTTPCanary 自带的证书,App 依然拒绝建立…...

VSCode R语言扩展:终极完整指南 - 从零构建专业数据分析环境

VSCode R语言扩展:终极完整指南 - 从零构建专业数据分析环境 【免费下载链接】vscode-R R Extension for Visual Studio Code 项目地址: https://gitcode.com/gh_mirrors/vs/vscode-R 想要在VSCode中高效进行R语言开发吗?vscode-R扩展为您提供了完…...

当 Agent 的输出需要符合特定格式规范

当 Agent 的输出需要符合特定格式规范:从混乱到可控的Prompt工程与结构化交互全解一、引言 (Introduction)钩子 (The Hook) 想象一个场景:你在训练一个医疗辅助诊断Agent,告诉它“把刚才的问诊结果整理成标准的HL7 FHIR Bundle”,…...

达梦数据库-数据库主备集群更改实例目录及相关目录步骤-记录总结

1达梦数据库-数据库主备集群更改实例目录及相关目录步骤-记录总结 1.1常见需求 当前数据库实例所在磁盘性能较差或空间不足,需格式化性能较好空间足的新磁盘并挂载,挂载到原目录或者新目录,然后把数据库实例目录移动到新磁盘。 1.2流程步骤…...

LangGraph 与 Streamlit 集成:实时展示多智能体执行状态

1. 标题选项 核心关键词:LangGraph、Streamlit、多智能体、实时可观测性、执行状态可视化 《从0到1:LangGraph + Streamlit 打造可观测的多智能体实时运行面板》 《多智能体开发不再黑盒!手把手教你用Streamlit可视化LangGraph执行全流程》 《LangGraph实战:集成Streamlit实…...

.NET Windows Desktop Runtime终极指南:如何彻底解决Windows桌面应用部署难题?

.NET Windows Desktop Runtime终极指南:如何彻底解决Windows桌面应用部署难题? 【免费下载链接】windowsdesktop 项目地址: https://gitcode.com/gh_mirrors/wi/windowsdesktop 在Windows桌面应用开发领域,最令人头疼的问题往往不是代…...

如何在Photoshop中完美处理WebP格式:WebPShop完全指南

如何在Photoshop中完美处理WebP格式:WebPShop完全指南 【免费下载链接】WebPShop Photoshop plug-in for opening and saving WebP images 项目地址: https://gitcode.com/gh_mirrors/we/WebPShop 还在为Photoshop无法直接处理WebP格式而烦恼吗?W…...

BOM 物料清单科普

BOM Bill of Materials 物料清单科普PLM、ERP、MES、SAP、数字孪生中的 BOM 全链路应用目录 前言 从"天天对 BOM"的经典场景切入,抛出核心问题一、BOM 的本质 还原 BOM 的真实定义,破除"BOM 物料清单"的误解二、全景图谱 完整 BOM …...

STL到STEP格式转换:跨越制造业数字鸿沟的工程化解决方案

STL到STEP格式转换:跨越制造业数字鸿沟的工程化解决方案 【免费下载链接】stltostp Convert stl files to STEP brep files 项目地址: https://gitcode.com/gh_mirrors/st/stltostp 在现代化制造与设计流程中,3D数据格式的互操作性已成为制约生产…...

Win11Debloat终极指南:如何快速清理Windows 11系统,提升电脑性能

Win11Debloat终极指南:如何快速清理Windows 11系统,提升电脑性能 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other change…...

如何轻松转换B站缓存视频:m4s-converter终极实用指南

如何轻松转换B站缓存视频:m4s-converter终极实用指南 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经在B站缓存了喜欢的视…...

嵌入式开发 10 大经典硬件 BUG + 定位解决(15 年工程师踩坑实录)

一、引言 嵌入式系统是 "硬件 软件" 的紧密结合体,据统计,嵌入式开发中约 30% 的问题最终根源是硬件设计或焊接缺陷。与软件 BUG 不同,硬件 BUG 具有以下特点: 隐蔽性强:很多问题只在特定温度、电压或电磁…...

NVIDIA Profile Inspector深度教程:解锁显卡隐藏设置的终极指南

NVIDIA Profile Inspector深度教程:解锁显卡隐藏设置的终极指南 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector NVIDIA Profile Inspector是一款功能强大的显卡性能调优工具,专为…...

为 OpenClaw 配置 Taotoken 以实现稳定可靠的 Agent 工作流

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 为 OpenClaw 配置 Taotoken 以实现稳定可靠的 Agent 工作流 对于使用 OpenClaw 框架构建智能体(Agent)的开…...

如何告别黄牛票:大麦网自动化抢票神器终极指南

如何告别黄牛票:大麦网自动化抢票神器终极指南 【免费下载链接】DamaiHelper 大麦网演唱会演出抢票脚本。 项目地址: https://gitcode.com/gh_mirrors/dama/DamaiHelper 还在为抢不到心仪的演唱会门票而烦恼吗?面对秒光的票源和黄牛的高价&#x…...

电信数智版Wireshark 3.7.1:深度支持TLCP与国密算法的协议分析利器

1. 为什么国密协议分析不能只靠“标准Wireshark”——从一次TLCP握手失败说起去年底帮某政务云平台做等保三级复测时,客户网络里跑着一套自研的国密SSL网关,所有前端HTTPS流量都经它转换为TLCP协议(Transport Layer Cryptography Protocol&am…...

Appium 2.10.1环境搭建实战:JDK21+Android SDK34全链路排障指南

1. 这不是“装个软件”——Appium最新版环境搭建的真实水深 很多人点开“Appium环境搭建”教程,以为就是下载几个安装包、点几下下一步,顶多配个PATH变量就完事了。我去年带三个新人做自动化测试时,也这么想。结果光是让一台干净的Windows 1…...

终极iOS设备激活解锁解决方案:Applera1n完全指南

终极iOS设备激活解锁解决方案:Applera1n完全指南 【免费下载链接】applera1n icloud bypass for ios 15-16 项目地址: https://gitcode.com/gh_mirrors/ap/applera1n 你是否曾经遇到过二手iPhone或iPad无法激活的困境?当你满怀期待地拿到一台设备…...

VisualGGPK2终极指南:轻松编辑《流放之路》游戏资源文件

VisualGGPK2终极指南:轻松编辑《流放之路》游戏资源文件 【免费下载链接】VisualGGPK2 Library for Content.ggpk of PathOfExile (Rewrite of libggpk) 项目地址: https://gitcode.com/gh_mirrors/vi/VisualGGPK2 VisualGGPK2是一款专为《流放之路》(Path o…...

面试官最爱问的“反转字符串”,为什么能看出你是不是高手?

面试官最爱问的“反转字符串”,为什么能看出你是不是高手? 很多人第一次看到“反转字符串(Reverse String)”这道题时,都会有一种感觉: 就这? 不就是把 "hello" 变成 "olleh" 吗? 结果真正面试时。 有人写了 3 行。 有人写了 30 行。 还有人直…...

使用taotoken后github actions自动化任务中的api调用稳定性观察

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用taotoken后github actions自动化任务中的api调用稳定性观察 1. 背景与迁移动机 在持续集成与自动化开发流程中,Gi…...

如何在Windows电脑上安装安卓应用:APK安装器完整教程

如何在Windows电脑上安装安卓应用:APK安装器完整教程 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer APK安装器是一款专为Windows系统设计的安卓应用安装工…...

如何用roop-unleashed三分钟制作专业级AI换脸视频:零门槛人脸替换终极指南

如何用roop-unleashed三分钟制作专业级AI换脸视频:零门槛人脸替换终极指南 【免费下载链接】roop-unleashed Evolved Fork of roop with Web Server and lots of additions 项目地址: https://gitcode.com/gh_mirrors/ro/roop-unleashed 还在为复杂的AI换脸工…...

LSLib终极指南:轻松解锁《神界原罪》和《博德之门3》MOD制作之门

LSLib终极指南:轻松解锁《神界原罪》和《博德之门3》MOD制作之门 【免费下载链接】lslib Tools for manipulating Divinity Original Sin and Baldurs Gate 3 files 项目地址: https://gitcode.com/gh_mirrors/ls/lslib LSLib是一款专门为《神界原罪》系列和…...

Sketch MeaXure:现代化设计标注解决方案如何革命性提升团队协作效率与开发质量

Sketch MeaXure:现代化设计标注解决方案如何革命性提升团队协作效率与开发质量 【免费下载链接】sketch-meaxure 项目地址: https://gitcode.com/gh_mirrors/sk/sketch-meaxure 执行摘要 Sketch MeaXure作为基于TypeScript重构的Sketch插件,为企…...

联想刃7000K BIOS隐藏选项终极解锁指南:3步开启完整高级权限

联想刃7000K BIOS隐藏选项终极解锁指南:3步开启完整高级权限 【免费下载链接】Lenovo-7000k-Unlock-BIOS Lenovo联想刃7000k2021-3060版解锁BIOS隐藏选项并提升为Admin权限 项目地址: https://gitcode.com/gh_mirrors/le/Lenovo-7000k-Unlock-BIOS 想要充分发…...

Godot 4.0桌面应用开发实战:跨平台GUI工程化落地指南

1. 这不是游戏引擎的“副业”,而是桌面开发的新路径很多人第一次看到“用Godot做桌面应用”时,下意识会皱眉:一个标榜“2D/3D游戏开发”的引擎,去碰文件管理器、RSS阅读器、本地笔记工具这类传统桌面软件?是不是大炮打…...