# 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)