# agentscope-java-lab **Repository Path**: dazhuang/agentscope-java-lab ## Basic Information - **Project Name**: agentscope-java-lab - **Description**: agentscope-java-lab - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-28 - **Last Updated**: 2026-10-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AgentScope Java Lab 对照微信公众号 **AgentScope Java 系列文章**(10 篇合集)做的一套逐篇可验证工程。 每一篇文章对应一个独立 Maven 模块,每个模块都能单独跑起来、打印出可观察的结果。 ## 这套代码的特点:不接真实大模型也能跑 你说了"先不接真实模型",所以 `lab-common` 里实现了一个 **脚本化模型** `ScriptedChatModel`: - 它实现的是框架真正的 `io.agentscope.core.model.Model` 接口,和 `DashScopeChatModel` 平级; - 回复按 `ChatResponse` 分片流式吐出,由框架的 `ReasoningContext` 负责累积; - 工具调用的入参同时写入 `input` 和 `content`(JSON 字符串),因为**框架的参数校验读的是 `content`**。 所以 **ReAct 循环、工具执行、记忆、Session、Hook、沙箱全部走框架真实代码路径**, 只有"token 从哪来"这一步被替换成了脚本。这样每个结论都是真跑出来的,不是模拟的。 想换成真实模型,只要把 `.model(...)` 换掉即可,其余代码一行不用改: ```xml io.agentscope agentscope-extensions-model-dashscope 2.0.4 ``` > 2.0 把模型拆成了独立扩展模块,`agentscope-core` 里不再内置各家模型实现。 ```java .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) ``` ## 环境说明(重要) AgentScope Java **强制要求 JDK 17+**,而你本机只有 JDK 8。为了不破坏你现有的 JDK 8 环境(`JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202`,还有 Maven 3.3.3), 我单独下载了一套工具链放在项目外面,**只给这套工程用**: | 工具 | 版本 | 位置 | 说明 | |-----|------|------|------| | JDK | Temurin 17.0.20.1+1 | `D:\jd-work\tools\jdk-17.0.20.1+1` | 免安装解压版 | | Maven | 3.9.16 | `D:\jd-work\tools\apache-maven-3.9.16` | 你的 3.3.3 太老,跑不了 JDK 17 构建 | **你的全局 `JAVA_HOME` 和 PATH 完全没被修改。** `build.bat` / `run.bat` 内部临时 指定 JDK 17,脚本结束即失效。 依赖从你 Maven 配置里的公司源 `artifactory.jd.com` 拉取(已验证可用, `io.agentscope` 各 artifact 都能解析到)。 ## 快速开始 ```bat cd /d D:\jd-work\agentscope-java-lab build.bat :: 全量编译 + 跑单测 test.bat :: 只跑单测(逐条打印中文用例说明) test.bat DEBUG :: 跑单测 + 看框架日志 run.bat 01 :: 运行第 1 篇 run.bat all :: 依次运行 01~09(离线,不花 token) run.bat 10 :: 接真实模型(需先填 model.properties) run.bat 02 DEBUG :: 第 2 个参数 = 日志级别,看框架内部在干什么 run.bat 10 DEBUG :: 接真实模型时看完整 HTTP 请求体和 SSE 分片 run.bat :: 不带参数会打印模块清单 ``` > `.bat` 脚本内部注释和菜单是纯英文的,这是故意的:cmd.exe 按系统 OEM 代码页 > (中文 Windows 上是 GBK)解析 `.bat` 文件,脚本里写 UTF-8 中文会导致命令解析错乱。 > Java 代码里的中文输出不受影响(父 pom 已强制 fork JVM 并指定 UTF-8)。 ## 怎么测试、怎么看日志 ### 跑测试 ```bat test.bat :: 逐条打印每个用例在验证什么 ``` 输出长这样 —— 测试本身就是一份框架行为说明书: ``` === 验证 ScriptedChatModel(离线脚本化模型)=== [通过] ToolUseBlock 同时带 input 和合法 JSON content [通过] 工具调用不会产生参数校验错误(content 必须是合法 JSON) [通过] toolCall(): 工具真的被执行,且参数正确送达 ... Tests run: 8, Failures: 0, Errors: 0 ``` 失败时会直接给出原因,不用去翻 XML: ``` [失败] toolCall(): 工具真的被执行,且参数正确送达 原因: 工具应被实际执行恰好一次 ==> expected: <1> but was: <0> ``` ### 真实模型测试:用户主记忆 vs Session 短时记忆 `lab-10-live-model` 里有一组**不含任何 mock** 的测试,验证 「一份用户主记忆(跨 Session)+ 每个 Session 独立短时记忆,互不混同」: ```bat cd /d D:\jd-work\agentscope-java-lab test.bat ``` | | 短时记忆 / Session 记忆 | 主记忆 / 用户长时记忆 | |---|---|---| | key | `(userId, sessionId)` 双键 | `userId` | | 载体 | `AgentState.getContext()`(框架内建) | `UserMainMemoryStore` 的 JSON 文件 | | 语义 | 本次聊天流水 | 这个用户的个人知识库 | | 注入方式 | 直接作为消息上下文 | **检索注入**系统提示,不进消息流 | 4 条用例,实测输出: ``` === 验证 用户主记忆 + Session 短时记忆 的隔离(真实模型)=== 使用模型: DeepSeek-V4.1-Flash-Hermes (openai) session-1 回复: 好的,已记下:拿铁换燕麦奶,您乳糖不耐受。 抽取到的事实: [用户乳糖不耐受。, 用户喝拿铁一定要换成燕麦奶。] [通过] session-1 的对话被真实 LLM 抽取成主记忆事实并落盘 session-2 回复: 好的,一杯拿铁,换燕麦奶。 [通过] session-2 短时上下文从零开始,但主记忆跨 session 共享 [通过] session-1 与 session-2 的消息流互不可见(短时记忆隔离) 用户 B 回复: 好的,一杯拿铁按您的习惯来——不过我还不知道您的具体习惯…… [通过] 主记忆按 userId 隔离:用户 B 读不到用户 A 的事实 ``` **哪里体现了"没用 mock"**: - 对话走真实网关; - 主记忆的事实抽取是**真实 LLM 调用**(和 Mem0 内部做的事一样), 上面那两条事实是模型自己从对话里抽出来的,不是我写死的; - session 状态和主记忆都**真实落盘**,测试直接去磁盘验证。 最有说服力的是最后一条:session-2 完全没有 session-1 的上下文(消息数为 0 起步), 却答出了"换燕麦奶";而用户 B 问同样的话,模型明确说"我还不知道您的具体习惯"—— **一正一反**证明了主记忆按 userId 隔离且确实被注入了。 **记忆文件落在哪**:测试跑完会直接打印绝对路径,也可以自己去看: ``` lab-10-live-model/target/memory-demo/ ├── user-main-memory/ <- 用户主记忆,按 userId 一个文件 │ ├── user-alice.json │ └── user-bob.json └── session-state/ <- session 短时记忆,(userId, sessionId) 双层目录 ├── user-alice/ │ ├── session-1/agent_state.json │ └── session-2/agent_state.json └── user-bob/ └── session-1/agent_state.json ``` 这个目录结构本身就把两层记忆的 key 画出来了:主记忆一个 userId 一个文件, session 记忆是 `userId/sessionId/` 两层。注意 alice 和 bob 都有 `session-1`, 互不干扰——这就是双键隔离。放在 `target/` 下,`mvn clean` 会一起清掉。 文件内容: ```json // user-main-memory/user-alice.json { "userId": "user-alice", "facts": [ "用户喝拿铁一定要换成燕麦奶。", "用户乳糖不耐受。" ] } // user-main-memory/user-bob.json —— 空的,没有任何 alice 的事实 { "userId": "user-bob", "facts": [] } ``` > **负向对照**:我把 `fileOf()` 改成所有用户共用一个文件(模拟"主记忆没按 userId 隔离" > 这个 bug),第 4 条用例立刻失败并指出 `用户 B 的主记忆应为空 ==> expected: but was: `。 > 说明这些断言是有效的,不是摆设。 > 没配 `model.properties` 时这 4 条会**整体 skip**(报 `Skipped: 4`),而不是假装通过。 #### 真跑出来的一个坑:把模型幻觉当事实记下来 早先版本把 assistant 的回复也一起送去抽取。结果用户 B 问"按我的习惯来"时, 模型**凭空编出**一句"您的习惯是少冰、燕麦奶、不加糖",抽取器就把这句幻觉 当成 B 的用户事实存进了主记忆——于是 B 的主记忆里莫名出现了 A 的偏好「燕麦奶」。 **这不是记忆隔离失效**(注入侧一直是干净的,断言也一直通过),而是 "用模型的输出去喂用户画像"这个做法本身有问题。5 次运行里中了 2 次,属于偶发。 修法:用户画像只应来自**用户自己的陈述**,所以 `renderTranscript()` 现在 只保留 USER 轮次,抽取提示词也加了"用户没说的绝对不要推测"。 修完 B 的主记忆稳定为 `[]`,并且加了一条断言专门盯这个: ```java assertFalse(String.join(" ", bobFacts).contains(PREFERENCE), "用户 B 的主记忆不该出现 A 的偏好(通常意味着把模型幻觉当事实记下来了)"); ``` 接 Mem0 / ReMe 也要注意同一件事——任何"LLM 抽取事实"的方案都有这个风险。 #### 关于 2.0 的 API 现状(重要) 如果你看过 1.x 或 Python 版文档,会看到这些名字——**在 Java 2.0.4 里对不上**: | 文档里的名字 | Java 2.0.4 实际情况 | |-------------|-------------------| | `LongTermMemoryBase` | 不存在。接口叫 `LongTermMemory`,且已 `@Deprecated(forRemoval=true)` | | `ReMePersonalLongTermMemory` | 不存在。实际叫 `ReMeLongTermMemory` | | `Mem0LongTermMemory` | **存在**,在 `agentscope-extensions-mem0` 里,确实以 userId 为 key | | `InMemoryMemory` | 存在但已废弃,2.0 上下文在 `AgentState` | `Mem0LongTermMemory` / `ReMeLongTermMemory` 的设计和你描述的完全一致(userId 为 key、 检索注入模式),但它们实现的正是那个**待删除**的接口,而且都需要外部服务 (Mem0 云端 key / ReMe 服务端)。2.0 官方对长期记忆的说法是: > Long-term memory is removed; integrate any cross-session persistence at the application layer. 所以本测试按官方建议**在应用层实现**(`UserMainMemoryStore` + `UserMainMemoryMiddleware`), 接口干净、不踩废弃 API、也不依赖外部服务。要换成 Mem0 只需替换 `UserMainMemoryStore` 的实现,Middleware 和 4 条测试都不用改。 ### 看日志 agentscope 只依赖 `slf4j-api`、**不带日志实现**。不配的话所有框架日志都进 NOP logger,一条都看不到(还会刷三行 `SLF4J(W): No SLF4J providers were found.`)。 本工程已经引入 logback 并配好,默认**安静模式**(只打 WARN 以上), 让示例自己的输出保持干净。 要看框架内部,加第二个参数: | 命令 | 看到什么 | |------|---------| | `run.bat 02` | 只有示例输出(默认 WARN) | | `run.bat 02 DEBUG` | 工具注册、并行执行、超时配置…… | | `run.bat 10 DEBUG` | **完整 HTTP 请求体 + SSE 流式分片**,调网关时最有用 | | `run.bat 02 TRACE` | 最详细 | `run.bat 10 DEBUG` 的实际输出(接真实网关时): ``` DEBUG [OpenAIClient] OpenAI streaming request to http://.../v1/chat/completions: {"model":"...","messages":[...],"stream":true,"temperature":0.0,"max_tokens":1024} DEBUG [OpenAIClient] SSE data: {"choices":[{"delta":{"content":"Re"}}]} DEBUG [ModelUtils] Applied retry config: maxAttempts=3, initialBackoff=PT2S ``` 接新网关报错时,先跑这个 —— 请求体和返回原文都在,比猜快得多。 **日志文件**:每次运行都会写一份完整日志(不受安静模式影响的部分)到 ``` %TEMP%\agentscope-lab.log ``` 控制台刷太快、或者想事后翻查时用它。配置在 `lab-common/src/main/resources/logback.xml`,想常开 DEBUG 就改那里的默认值。 ## 模块与文章对应关系 | 模块 | 对应文章 | 验证内容 | 状态 | |------|---------|---------|------| | `lab-01-overview` | 01 Java 开发者终于有了自己的 AI Agent 框架 | 最小 Agent = 名字+提示+模型;memory 自动累积上下文 | 已验证 | | `lab-02-quickstart` | 02 10 分钟写出你的第一个 AI Agent | `@Tool` 注解、Toolkit 注册、模型发起工具调用 | 已验证 | | `lab-03-key-concepts` | 03 六大核心概念 | Message / Model / Tool / **State** / Agent / StateStore 逐个拆解(按 2.0 重写) | 已验证 | | `lab-04-message` | 04 消息系统深度解析 | 多模态 ContentBlock、GenerateReason、ChatUsage | 已验证 | | `lab-05-react-agent` | 05 手把手教你创建 ReAct Agent | 多步工具链(3 轮推理 2 次工具)、maxIters 兜底 | 已验证 | | `lab-06-runtime` | 06 看懂 AgentScope Java 运行框架 | **Middleware** 全链路+分段耗时、改写系统提示、**短路拦截**、ExecutionConfig(按 2.0 重写) | 已验证 | | `lab-07-memory` | 07 双层记忆系统 | 短期上下文增长(2/4/6)、`contextMutable()`、**用 Middleware 实现长期记忆**(按 2.0 重写) | 已验证 | | `lab-08-session-state` | 08 Session 与状态管理 | 自动持久化/恢复、**双键多租户隔离**、生命周期、存储后端对比(按 2.0 重写) | 已验证 | | `lab-09-sandbox` | 09 沙箱隔离 | workspace 读写、**路径穿越被拦截**、WorkspaceManager | 部分验证 | | `lab-10-live-model` | (补充,非文章内容) | **接真实大模型**(openai 协议):基础问答 / 真实工具调用 / 流式输出 | 已验证 | > 合集里还有一篇《AgentScope vs Spring AI Alibaba:怎么选?》是选型对比文,没有配套代码,故未建模块。 ### 关于 lab-10:接真实模型 前 9 个模块用脚本化模型(离线、可复现)。第 10 个模块接**真实大模型**, 用来验证"脚本模型和真实模型走同一条框架代码路径"这个前提真的成立。 **只需要改配置,Java 代码一行不用动:** ``` lab-10-live-model/src/main/resources/model.properties ``` 第 1 步选 `provider`,第 2 步填那一节的参数,然后 `run.bat 10`。 **只接 openai 协议**,也就是 OpenAI Chat Completions 这套事实标准: ``` POST {baseUrl}/chat/completions Header: Authorization: Bearer {apiKey} ``` 绝大多数模型和网关都兼容它 —— DeepSeek / 智谱 GLM / Kimi / MiniMax / 百川 / 阶跃星辰 / 硅基流动 / vLLM / One-API / 公司内部网关。**只要对方文档里出现 `/v1/chat/completions` 就能直接用**,不用改代码。 需要填的就 3 项: | 配置项 | 说明 | |-------|------| | `openai.baseUrl` | 填到 `/v1` 为止,**不要带 `/chat/completions`**(最常见的填错点) | | `openai.modelName` | 照抄服务商文档里的 `model` 字段 | | `openai.apiKey` | 留空则回退读环境变量 `OPENAI_API_KEY` | #### 换其它协议 AgentScope 另外提供 4 种原生协议扩展,本模块**没有引入它们的依赖**—— openai 协议已经够用,少 4 个依赖也省下一堆传递依赖。真要用的话两步: | provider | 依赖 | 原生协议 | |----------|------|---------| | `dashscope` | `agentscope-extensions-model-dashscope` | 阿里云百炼。比 openai 兼容模式多 `enableThinking` / `enableSearch` | | `anthropic` | `agentscope-extensions-model-anthropic` | `POST {baseUrl}/v1/messages`,`x-api-key` | | `gemini` | `agentscope-extensions-model-gemini` | `POST .../{model}:streamGenerateContent?key=`,支持 Vertex AI | | `ollama` | `agentscope-extensions-model-ollama` | `POST {baseUrl}/api/chat`,本地模型不需要 key | 1. 在 `lab-10-live-model/pom.xml` 加上对应依赖(版本用 `${agentscope.version}`) 2. 在 `ModelFactory.create()` 里补一个分支 把 `provider` 改成未引入的协议时,程序会打印上面这两步该怎么做,不会抛堆栈。 **API key 两种填法**:直接写在 `model.properties`,或留空改用环境变量 `OPENAI_API_KEY`。 > **安全**:`model.properties` 含明文 key,已加入 `.gitignore`,不会进版本库。 > 要分享配置就发同目录的 `model.properties.template`(不含 key)。 > `run.bat all` **只跑 01~09**,不含 10 —— 真实模型要花 token,不该被误触发。 > 跑第 10 个模块请显式 `run.bat 10`。 #### 已实测通过(JD 内部网关 + DeepSeek) 三个场景都验证过,其中第 2 个最有说服力:问"北京和深圳气温差多少",模型 1. 在**同一条消息里并行调用**两次 `get_weather`(北京、深圳) 2. 再调 `calculate(a=26.0, b=15.0, op=sub)` 3. 最后答"相差 11℃" 消息链是 `[ASSISTANT] ToolUseBlock+ToolUseBlock → [TOOL] ×2 → [ASSISTANT] ToolUseBlock → [TOOL] → [ASSISTANT] TextBlock`。 **并行工具调用是脚本模型伪造不出来的**——调用时机、顺序、参数全部由模型自主决定, 代码里没有任何 `if/else` 指挥它。 ### 关于 lab-09 的说明 本机**没有安装 Docker**,所以: - **真跑了**:`LocalFilesystem` 的 workspace 读写、虚拟路径、以及 `../` 路径穿越被 `SecurityException` 拦截(这是隔离是否真实有效的关键验证); - **只打印配置、没执行**:`DockerFilesystemSpec` 的容器级隔离。代码片段里的方法名 (`image` / `workspaceRoot` / `cpuCount` / `memorySizeBytes`)我对着 2.0.4 的 源码核对过,装好 Docker 后可直接照搬。注意 2.0 里这个类换了包: `io.agentscope.harness.agent.sandbox.impl.docker.DockerFilesystemSpec`。 ## 版本说明:用的是 2.0.4(最新 GA) 当前锁定 **2.0.4**,是 Maven 上最新的正式版。公司源上可用版本到 `2.1.0-BETA1`, 想试 BETA 只改父 pom 一行: ```xml 2.1.0-BETA1 ``` ### 2.0 相比文章(1.x)的 API 变化 文章系列是基于 1.x 写的,**2.0 有几处破坏性改动**,这套代码已经全部按 2.0 迁移过, 并在注释里标注了新旧对照: | 关注点 | 1.x(文章里的写法) | 2.0(本工程的写法) | |-------|-------------------|-------------------| | 对话历史 | `.memory(new InMemoryMemory())` | Agent 无状态,历史在 `AgentState` | | 读历史 | `agent.getMemory().getMessages()` | `agent.getAgentState().getContext()` | | 持久化 | `SessionManager` + `JsonSession` | `.stateStore(new JsonFileAgentStateStore(dir))` | | 存/读时机 | 手动 `saveSession()` / `loadIfExists()` | 框架按 `(userId, sessionId)` 槽位自动读写 | | 多租户 | 只有单个 `sessionId` | `(userId, sessionId)` 双键分槽 | | 模型实现 | core 内置 | 拆成 `agentscope-extensions-model-*` | | 拦截扩展 | `Hook` + `onEvent()` 事件监听 | `MiddlewareBase` **环绕拦截**,能短路 | | 长期记忆 | `.longTermMemory()` + `LongTermMemoryMode` | **已移除**,改用 Middleware 在应用层实现 | | Docker 沙箱 | `...filesystem.spec.DockerFilesystemSpec` | `...sandbox.impl.docker.DockerFilesystemSpec` | 2.0 **整体移除/废弃了三个包**,对应的模块都做了重写,不是文章原文的照搬: | 包 | 状态 | 影响的模块 | 2.0 的做法 | |---|---|---|---| | `core.session` | 已移除 | 03 概念 6、08 整篇 | `AgentStateStore` 按槽位自动读写 | | `core.hook` | `@Deprecated(forRemoval)` | 06 整篇 | `MiddlewareBase` 环绕拦截 | | `core.memory.LongTermMemory` | `@Deprecated(forRemoval)` | 07 长期记忆部分 | 没有替代品,用 Middleware 在应用层实现 | > **Hook → Middleware 不只是换名字**:Hook 是事件监听(事件发生后被通知), > Middleware 是环绕拦截(`next.apply(input)` 调了才继续)。所以 Middleware 能 > **算分段耗时**、能**短路跳过某个环节**——这两件事 Hook 都做不到。 > 模块 06 把这两点都跑出来给你看。 > **长期记忆在 2.0 没有等价替代**。官方原话是 "Long-term memory is removed; > integrate any cross-session persistence at the application layer"。模块 07 > 用 Middleware 把 1.x 的两个方法映射了过去:`retrieve()` → `onSystemPrompt()`, > `record()` → `onAgent()` 完成回调。 2.0 另外新增了文章没覆盖的能力(本工程暂未展开):事件流(32 种 `AgentEvent`)、 权限系统(`ALLOW` / `ASK` / `DENY` 工具门禁)、中间件(AOP 式拦截)、分布式部署。 ## 踩过的坑(都已在代码注释里标注) 1. **工具参数校验读 `content` 不读 `input`** `ToolExecutor` 调的是 `ToolValidator.validateInput(toolCall.getContent(), ...)`, 自定义模型如果只设了 `input`,会直接报"未找到所需属性"。 2. **工具返回值会被 JSON 序列化** `DefaultToolResultConverter` 对返回值调 `toJson()`,所以一个返回 `String` 的工具, 结果文本是带引号的 `"上海"`。`ScriptedChatModel.toolResultText()` 里统一剥掉了这层引号, 否则引号会被带进下一次工具调用。 3. **Windows 控制台中文乱码** `exec:java` 复用 Maven 自己的 JVM,那时 `System.out` 编码早已按 GBK 定下, 再设 `file.encoding` 也来不及。父 pom 里改用 `exec:exec` fork 新 JVM, 并显式指定 `${java.home}/bin/java`(否则会用到 PATH 上的 JDK 8,报 `UnsupportedClassVersionError: class file version 61.0`)。 4. **`ThinkingBlock` 用 `thinking()` 不是 `text()`** 和 `TextBlock` 不一样,取值也是 `getThinking()`。 5. **`ReActAgent` / `HarnessAgent` 没有公开的 `getToolkit()`** `HarnessAgent` 要看工作区信息走 `getWorkspaceManager()`。 6. **2.0 会校验 role 与 content 的一致性** `USER` 角色的消息里塞 `ToolResultBlock` 会直接抛 `IllegalArgumentException: USER message may only contain text/data/image/audio/video blocks`。 1.x 不校验,所以从 1.x 迁过来的代码可能在这里才暴露出问题。 7. **2.0 的 `call(Msg, RuntimeContext)` 不存在** 带 context 的重载只有 `call(List, ctx)` 和 `call(String, ctx)`。 8. **`WorkspaceManager.getMemoryDir()` 在 2.0 需要传 `RuntimeContext`** 因为记忆目录现在按会话分槽。 9. **`.bat` 脚本里不能写 UTF-8 中文** cmd.exe 用 OEM 代码页(GBK)解析批处理文件,UTF-8 中文会让 `if` / `echo` 整行解析错乱,报一堆"不是内部或外部命令"。脚本一律保持纯 ASCII。 ## 目录结构 ``` agentscope-java-lab/ ├── pom.xml 父 pom:统一 JDK 17、依赖版本、UTF-8 输出 ├── build.bat / run.bat / test.bat 自带 JDK 17 路径,不动你的 JDK 8 环境 ├── lab-common/ ScriptedChatModel(离线脚本化模型)+ Printer + History │ └── src/main/resources/logback.xml 日志配置(默认安静,-Dlab.log 可调) ├── lab-01-overview/ ├── lab-02-quickstart/ ├── lab-03-key-concepts/ ├── lab-04-message/ ├── lab-05-react-agent/ ├── lab-06-runtime/ ├── lab-07-memory/ ├── lab-08-session-state/ ├── lab-09-sandbox/ 依赖 agentscope-harness └── lab-10-live-model/ 接真实模型(openai 协议,配置驱动) └── src/main/resources/ ├── model.properties <- 改这个(含 key,已 gitignore) └── model.properties.template <- 分享用(不含 key) ``` ## 参考 - 框架仓库:https://github.com/agentscope-ai/agentscope-java - 官方文档:https://java.agentscope.io/ - 本地参考源码(2.0.4):`D:\jd-work\tools\as-src-v2`(core)、`D:\jd-work\tools\as-harness-src-v2`(harness)