chore: 提交剩余开发改动
包含商务通消息映射、会话时间与未读状态、前端消息展示、数据库修复迁移、测试覆盖备份及 Captain 设计文档。
This commit is contained in:
@@ -0,0 +1,819 @@
|
||||
# Captain Eino SKILLS 实施计划
|
||||
|
||||
> 日期:2026-08-04
|
||||
> 状态:计划中
|
||||
> 目标:在不大规模重构现有 Captain/Copilot 调用链的前提下,最小化接入 Eino SKILL 能力;第一版采用“每个 Agent 一个独立 SKILLS 目录”的隔离模型,并通过 GoChat 后端适配层提供 scope、路径安全、审计和灰度控制。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与结论
|
||||
|
||||
当前 Captain/Copilot 还不是完整 Eino ADK Agent 架构,而是:
|
||||
|
||||
```text
|
||||
CaptainTaskService / CopilotService / CaptainAssistantResponseService
|
||||
→ llm.Provider
|
||||
→ ProviderManager / OpenAIProvider / AnthropicProvider / EinoProvider
|
||||
→ 模型 API
|
||||
```
|
||||
|
||||
Eino SKILL 是 ADK `ChatModelAgent` middleware 能力,不能直接挂到现有 `llm.Provider` 调用链。因此首期需要新增一个窄口径 `CaptainAgentRunner` 作为试点路径:
|
||||
|
||||
```text
|
||||
CaptainTaskService 试点入口
|
||||
→ CaptainAgentRunner
|
||||
→ Eino ChatModelAgent
|
||||
→ GoChatSkillBackend
|
||||
→ 每个 Agent 独立 SKILLS 目录
|
||||
```
|
||||
|
||||
首期结论:
|
||||
|
||||
1. **仅做 SKILL 相关接入**,不引入其他 Agent 能力。
|
||||
2. 第一版使用文件目录作为 SKILL 存储:每个 Assistant/Agent 一个独立目录。
|
||||
3. 不能把 Eino filesystem backend 直接作为业务边界;必须包一层 `GoChatSkillBackend`。
|
||||
4. 第一版只支持 inline skill,不支持 `fork` / `fork_with_context`。
|
||||
5. 第一版只读取 `SKILL.md`,不执行 `scripts/`,不开放任意文件系统工具。
|
||||
6. 首选试点入口是 Captain Playground 或 Copilot reply suggestion,不默认进入 auto-reply 自动发送链路。
|
||||
|
||||
---
|
||||
|
||||
## 2. 产品与架构决策
|
||||
|
||||
### 2.1 SKILLS 隔离方式
|
||||
|
||||
第一版采用最简单的隔离方案:**每个 Agent 一个独立 SKILLS 目录**。
|
||||
|
||||
推荐目录结构:
|
||||
|
||||
```text
|
||||
backend/data/captain_skills/
|
||||
account_1/
|
||||
assistant_10/
|
||||
refund-policy/
|
||||
SKILL.md
|
||||
shipping-status/
|
||||
SKILL.md
|
||||
account_1/
|
||||
assistant_11/
|
||||
vip-service/
|
||||
SKILL.md
|
||||
```
|
||||
|
||||
隔离边界:
|
||||
|
||||
- 一个 Assistant 只加载自己的 `BaseDir`。
|
||||
- `BaseDir` 只能由后端根据 `account_id + assistant_id` 计算。
|
||||
- 前端、HTTP request、模型输出都不能直接指定 `BaseDir`。
|
||||
- 第一版只读 `SKILL.md`。
|
||||
- 第一版只支持 inline skill。
|
||||
|
||||
### 2.2 为什么仍需要 GoChatSkillBackend
|
||||
|
||||
即使每个 Agent 一个目录,也不能直接把 Eino filesystem backend 暴露为业务边界。
|
||||
|
||||
必须增加 GoChat 适配层:
|
||||
|
||||
```text
|
||||
CaptainAgentRunner
|
||||
→ context 注入 CaptainAgentScope
|
||||
→ GoChatSkillBackend
|
||||
→ 后端计算 BaseDir
|
||||
→ 路径规范化与越界校验
|
||||
→ List/Get SKILL.md
|
||||
→ 审计日志
|
||||
→ Eino skill middleware
|
||||
```
|
||||
|
||||
不允许:
|
||||
|
||||
```text
|
||||
HTTP request / 模型输出
|
||||
→ 任意 BaseDir
|
||||
→ Eino filesystem skill backend
|
||||
```
|
||||
|
||||
### 2.3 首期能力边界
|
||||
|
||||
首期只做:
|
||||
|
||||
- SKILL 目录隔离。
|
||||
- SKILL metadata discovery。
|
||||
- SKILL content loading。
|
||||
- inline skill tool。
|
||||
- scope 校验。
|
||||
- 路径安全。
|
||||
- 大小限制。
|
||||
- 结构化日志/审计。
|
||||
- 试点入口灰度启用。
|
||||
|
||||
首期不做:
|
||||
|
||||
- 不新增前端 SKILL 管理页面。
|
||||
- 不新增正式 `captain_skills` 数据表。
|
||||
- 不支持 `fork` / `fork_with_context`。
|
||||
- 不执行 `scripts/`。
|
||||
- 不读取 `references/`、`assets/` 下的任意文件。
|
||||
- 不开放 filesystem tools。
|
||||
- 不默认进入 auto-reply 自动发送链路。
|
||||
- 不做数据库版本管理。
|
||||
- 不做多实例目录同步。
|
||||
|
||||
---
|
||||
|
||||
## 3. 实施范围
|
||||
|
||||
### 3.1 本计划首期包含
|
||||
|
||||
- 最小 `CaptainAgentRunner`,用于试点 Eino ADK Agent。
|
||||
- `GoChatSkillBackend`,实现 Eino `skill.Backend`。
|
||||
- 每个 Assistant 一个 SKILLS 目录。
|
||||
- inline skill middleware。
|
||||
- Captain Playground 或 Copilot reply suggestion 灰度试点。
|
||||
- 单元测试覆盖 scope 隔离、路径安全、SKILL 解析、runner 分支。
|
||||
|
||||
### 3.2 文件命名与目录约定
|
||||
|
||||
计划文档路径:
|
||||
|
||||
```text
|
||||
docs/plans/2026-08-04-captain-eino-skills.md
|
||||
```
|
||||
|
||||
运行时 SKILLS 根目录建议:
|
||||
|
||||
```text
|
||||
backend/data/captain_skills
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `backend/data/captain_skills` 是首期 PoC 默认位置。
|
||||
- 生产部署可后续迁移到共享挂载或数据库模型。
|
||||
- 首期不新增环境变量;如必须配置 root path,应走数据库配置中心或后端固定配置,而不是让 request 指定。
|
||||
|
||||
---
|
||||
|
||||
## 4. SKILL 文件格式
|
||||
|
||||
### 4.1 目录结构
|
||||
|
||||
每个 skill 是一个目录,必须包含 `SKILL.md`:
|
||||
|
||||
```text
|
||||
backend/data/captain_skills/account_1/assistant_10/refund-policy/SKILL.md
|
||||
```
|
||||
|
||||
第一版只扫描 Assistant 目录下的第一层子目录:
|
||||
|
||||
```text
|
||||
assistant_10/*/SKILL.md
|
||||
```
|
||||
|
||||
不会递归扫描更深层级。
|
||||
|
||||
### 4.2 SKILL.md Frontmatter
|
||||
|
||||
首期支持的 frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: refund-policy
|
||||
description: 处理退款、退货、补偿和售后政策相关问题
|
||||
context: inline
|
||||
---
|
||||
```
|
||||
|
||||
字段规则:
|
||||
|
||||
- `name` 必填。
|
||||
- `description` 必填。
|
||||
- `context` 可选;为空等价于 `inline`。
|
||||
- `context` 只允许空或 `inline`。
|
||||
- `agent` 不支持;出现时返回 validation error。
|
||||
- `model` 不支持;出现时返回 validation error。
|
||||
|
||||
`name` 规则:
|
||||
|
||||
```text
|
||||
[a-z0-9][a-z0-9-]{0,63}
|
||||
```
|
||||
|
||||
### 4.3 SKILL.md Body
|
||||
|
||||
body 是给模型看的完整操作说明。
|
||||
|
||||
示例:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: refund-policy
|
||||
description: 处理退款、退货、补偿和售后政策相关问题
|
||||
context: inline
|
||||
---
|
||||
|
||||
当客户询问退款时:
|
||||
1. 先确认订单状态。
|
||||
2. 未发货订单可直接退款。
|
||||
3. 已发货订单需引导客户先拒收或退回商品。
|
||||
4. 不确定时要求人工客服确认,不要承诺具体到账时间。
|
||||
```
|
||||
|
||||
内容要求:
|
||||
|
||||
- 使用业务可读的中文说明。
|
||||
- 明确适用场景。
|
||||
- 明确禁止事项。
|
||||
- 不写密钥、内部接口 token、客户隐私样例。
|
||||
- 不包含指令要求模型越权访问系统。
|
||||
|
||||
---
|
||||
|
||||
## 5. 后端设计
|
||||
|
||||
### 5.1 CaptainAgentScope
|
||||
|
||||
新增文件:
|
||||
|
||||
- `backend/internal/service/captain_agent_scope.go`
|
||||
|
||||
定义:
|
||||
|
||||
```go
|
||||
type CaptainAgentScope struct {
|
||||
AccountID uint
|
||||
AssistantID uint
|
||||
InboxID uint
|
||||
ConversationID uint
|
||||
UserID uint
|
||||
Feature string
|
||||
}
|
||||
```
|
||||
|
||||
提供:
|
||||
|
||||
```go
|
||||
func WithCaptainAgentScope(ctx context.Context, scope CaptainAgentScope) context.Context
|
||||
func CaptainAgentScopeFromContext(ctx context.Context) (CaptainAgentScope, bool)
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- `AccountID` 和 `AssistantID` 首期必填。
|
||||
- 缺 scope 时,SkillBackend 必须 fail fast。
|
||||
- 不允许 scope 零值静默降级到全局目录。
|
||||
- 未来如果支持 inbox/team scope,应在这里扩展,不要把权限判断散落在 middleware 内。
|
||||
|
||||
### 5.2 GoChatSkillBackend
|
||||
|
||||
新增文件:
|
||||
|
||||
- `backend/internal/service/captain_skill_backend.go`
|
||||
|
||||
职责:
|
||||
|
||||
- 实现 Eino `skill.Backend`。
|
||||
- 根据 scope 计算 assistant skill directory。
|
||||
- 扫描第一层子目录下的 `SKILL.md`。
|
||||
- 解析 YAML frontmatter。
|
||||
- 返回 metadata 或完整 content。
|
||||
- 做路径安全校验。
|
||||
- 做大小限制。
|
||||
- 记录 skill usage 审计日志。
|
||||
|
||||
Eino 接口:
|
||||
|
||||
```go
|
||||
type Backend interface {
|
||||
List(ctx context.Context) ([]skill.FrontMatter, error)
|
||||
Get(ctx context.Context, name string) (skill.Skill, error)
|
||||
}
|
||||
```
|
||||
|
||||
目录解析:
|
||||
|
||||
```go
|
||||
root := filepath.Join("data", "captain_skills")
|
||||
baseDir := filepath.Join(root, fmt.Sprintf("account_%d", scope.AccountID), fmt.Sprintf("assistant_%d", scope.AssistantID))
|
||||
```
|
||||
|
||||
路径安全要求:
|
||||
|
||||
- `filepath.Clean`。
|
||||
- 如路径存在,使用 `filepath.EvalSymlinks` 校验真实路径仍在 root 下。
|
||||
- 禁止 `..` 逃逸。
|
||||
- 禁止 absolute path 输入参与拼接。
|
||||
- `Get(ctx, name)` 中的 `name` 必须先通过 slug 校验。
|
||||
- symlink 指向 root 外部必须失败。
|
||||
|
||||
大小限制建议:
|
||||
|
||||
- 单个 `SKILL.md` 最大 32KB。
|
||||
- `List` 最多返回 50 个 skills。
|
||||
- 所有 metadata description 合计最大 16KB。
|
||||
|
||||
不存在目录处理:
|
||||
|
||||
- `List`:返回空列表,不报错。
|
||||
- `Get`:返回 not found error。
|
||||
|
||||
解析失败处理:
|
||||
|
||||
- frontmatter 缺 `name` 或 `description`:该 skill 无效。
|
||||
- `List` 遇到无效 skill:记录 warning 并跳过,避免一个坏文件阻塞整个 Assistant。
|
||||
- `Get` 指定到无效 skill:返回明确 validation error。
|
||||
|
||||
### 5.3 Skill 内容构造
|
||||
|
||||
新增私有函数:
|
||||
|
||||
```go
|
||||
func buildGoChatSkillContent(ctx context.Context, sk skill.Skill, rawArgs string) (string, error)
|
||||
```
|
||||
|
||||
输出格式建议:
|
||||
|
||||
```text
|
||||
# {name}
|
||||
|
||||
适用场景:
|
||||
{description}
|
||||
|
||||
操作说明:
|
||||
{body}
|
||||
|
||||
约束:
|
||||
- 只能基于当前账号/助手授权知识回答。
|
||||
- 不确定时要求人工客服确认。
|
||||
- 不要编造订单、物流、退款、支付或账户状态。
|
||||
```
|
||||
|
||||
目的:
|
||||
|
||||
- 保持 skill body 原文。
|
||||
- 追加 GoChat 固定安全边界。
|
||||
- 降低模型把 skill 当成越权工具的风险。
|
||||
|
||||
### 5.4 Skill Tool 描述
|
||||
|
||||
新增私有函数:
|
||||
|
||||
```go
|
||||
func buildGoChatSkillToolDescription(ctx context.Context, skills []skill.FrontMatter) string
|
||||
```
|
||||
|
||||
描述要求:
|
||||
|
||||
- 告诉模型仅在用户问题匹配 skill 适用场景时调用。
|
||||
- 告诉模型不能为了泛泛回答加载所有 skill。
|
||||
- 列出最多 50 个 skill 的 name + description。
|
||||
- 如果没有 skill,描述应明确当前没有可用 skill。
|
||||
|
||||
示例描述要点:
|
||||
|
||||
```text
|
||||
你可以按需加载一个客服处理技能。只有当用户问题明显匹配某个技能描述时才调用。
|
||||
不要批量加载技能。不要猜测不存在的技能名。
|
||||
```
|
||||
|
||||
### 5.5 CaptainAgentRunner
|
||||
|
||||
新增文件:
|
||||
|
||||
- `backend/internal/service/captain_agent_runner.go`
|
||||
- `backend/internal/service/captain_agent_model_adapter.go`
|
||||
|
||||
职责:
|
||||
|
||||
- 作为试点路径构造 Eino ADK `ChatModelAgent`。
|
||||
- 把 GoChat `llm.Provider` 适配成 Eino `model.BaseModel[*schema.Message]`。
|
||||
- 注入 SKILL middleware。
|
||||
- 执行 Agent 并返回最终 answer。
|
||||
|
||||
首期接口:
|
||||
|
||||
```go
|
||||
type CaptainAgentRunRequest struct {
|
||||
Scope CaptainAgentScope
|
||||
Instruction string
|
||||
Messages []llm.ChatMessage
|
||||
}
|
||||
|
||||
type CaptainAgentRunResult struct {
|
||||
Message string
|
||||
Model string
|
||||
}
|
||||
|
||||
type CaptainAgentRunner struct {
|
||||
provider llm.Provider
|
||||
skillRoot string
|
||||
}
|
||||
|
||||
func (r *CaptainAgentRunner) Run(ctx context.Context, req CaptainAgentRunRequest) (*CaptainAgentRunResult, error)
|
||||
```
|
||||
|
||||
首期只做同步 Run,不做 stream。
|
||||
|
||||
### 5.6 Eino model adapter
|
||||
|
||||
新增类型:
|
||||
|
||||
```go
|
||||
type ProviderChatModel struct {
|
||||
provider llm.Provider
|
||||
}
|
||||
```
|
||||
|
||||
实现 Eino `Generate` 的最小能力:
|
||||
|
||||
- Eino messages → GoChat `llm.ChatMessage`。
|
||||
- 调用 `provider.ChatCompletion`。
|
||||
- GoChat response → Eino `schema.Message`。
|
||||
|
||||
首期不支持:
|
||||
|
||||
- tool calling 以外的自定义工具能力。
|
||||
- multimodal。
|
||||
- stream。
|
||||
- model options 全量映射。
|
||||
|
||||
注意:Eino SKILL middleware 本身会注入 `skill` tool,因此 adapter 必须满足 ChatModelAgent 对模型工具调用的最低要求。如果现有 `llm.Provider` 无法承载 Eino tool calling,应改用 Eino 原生 OpenAI-compatible ChatModel 构造模型,或者扩展 adapter 支持 tools option 映射。
|
||||
|
||||
该点是实现前必须验证的技术风险。
|
||||
|
||||
### 5.7 SKILL middleware 装配
|
||||
|
||||
在 `CaptainAgentRunner.Run` 中:
|
||||
|
||||
```go
|
||||
backend := NewGoChatSkillBackend(skillRoot)
|
||||
skillHandler, err := skill.NewMiddleware(ctx, &skill.Config{
|
||||
Backend: backend,
|
||||
CustomToolDescription: buildGoChatSkillToolDescription,
|
||||
BuildContent: buildGoChatSkillContent,
|
||||
})
|
||||
```
|
||||
|
||||
Agent config:
|
||||
|
||||
```go
|
||||
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
|
||||
Name: "captain",
|
||||
Instruction: req.Instruction,
|
||||
Model: chatModel,
|
||||
Handlers: []adk.ChatModelAgentMiddleware{
|
||||
skillHandler,
|
||||
},
|
||||
MaxIterations: 4,
|
||||
})
|
||||
```
|
||||
|
||||
首期 `MaxIterations` 控制在 4,避免模型反复调用 skill。
|
||||
|
||||
### 5.8 试点接入点
|
||||
|
||||
首选接入:Captain Playground 或 Copilot reply suggestion。
|
||||
|
||||
建议修改:
|
||||
|
||||
- `backend/internal/service/captain_task_service.go`
|
||||
- 在 `ReplySuggestion` 内增加 feature flag 分支。
|
||||
- flag 关闭时保持现有逻辑。
|
||||
- flag 开启时走 `CaptainAgentRunner`。
|
||||
|
||||
如果当前方法体较复杂,避免大改:新增私有方法:
|
||||
|
||||
```go
|
||||
func (s *CaptainTaskService) replySuggestionWithAgentRunner(ctx context.Context, accountID uint, req *TaskReplySuggestionRequest) (*TaskReplySuggestionResult, error)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 灰度开关与配置
|
||||
|
||||
首期不做页面配置,使用后端约定和 feature flag:
|
||||
|
||||
- `captain_agent_runner_enabled`
|
||||
- `captain_skill_enabled`
|
||||
|
||||
如果当前 feature flag 体系已有 account feature map,则先挂在 account feature flags;否则使用内部配置常量,试点时通过种子或测试数据打开。
|
||||
|
||||
开关要求:
|
||||
|
||||
- flag 关闭时现有 Captain/Copilot 路径行为不变。
|
||||
- flag 开启但 skill 目录不存在时,runner 仍可执行,只是没有可用 skill。
|
||||
- skill backend 出错时必须返回错误,不允许静默降级为无 skill,除非明确是目录不存在。
|
||||
|
||||
---
|
||||
|
||||
## 7. 审计与可观测性
|
||||
|
||||
### 7.1 Skill list 日志
|
||||
|
||||
`List(ctx)` 记录:
|
||||
|
||||
- `account_id`
|
||||
- `assistant_id`
|
||||
- `conversation_id`
|
||||
- `feature`
|
||||
- `skill_count`
|
||||
- `skipped_invalid_count`
|
||||
- `latency_ms`
|
||||
|
||||
### 7.2 Skill get 日志
|
||||
|
||||
`Get(ctx, name)` 成功时记录:
|
||||
|
||||
- `account_id`
|
||||
- `assistant_id`
|
||||
- `conversation_id`
|
||||
- `feature`
|
||||
- `skill_name`
|
||||
- `skill_path`
|
||||
- `skill_hash`
|
||||
- `skill_mtime`
|
||||
- `content_bytes`
|
||||
- `latency_ms`
|
||||
|
||||
失败时记录:
|
||||
|
||||
- `account_id`
|
||||
- `assistant_id`
|
||||
- `conversation_id`
|
||||
- `skill_name`
|
||||
- `error_type`
|
||||
- `latency_ms`
|
||||
|
||||
日志禁止包含:
|
||||
|
||||
- API key。
|
||||
- Authorization header。
|
||||
- 完整客户消息。
|
||||
- 大段 skill content。
|
||||
|
||||
### 7.3 审计落库
|
||||
|
||||
首期可以先结构化日志,不新增表。
|
||||
|
||||
第二阶段如需要产品级审计,再新增:
|
||||
|
||||
- `captain_skill_usages`
|
||||
|
||||
建议字段:
|
||||
|
||||
- account_id
|
||||
- assistant_id
|
||||
- conversation_id
|
||||
- skill_name
|
||||
- skill_hash
|
||||
- user_id
|
||||
- feature
|
||||
- created_at
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试计划
|
||||
|
||||
所有 Go 命令从 `backend/` 执行,并使用离线缓存:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
GOMODCACHE=/home/yanghao05/go/pkg/mod GOFLAGS=-mod=mod GOPROXY=off go test ./internal/service -run 'CaptainAgent|CaptainSkill|CaptainTask'
|
||||
GOMODCACHE=/home/yanghao05/go/pkg/mod GOFLAGS=-mod=mod GOPROXY=off go test ./internal/... ./pkg/... ./cmd/...
|
||||
GOMODCACHE=/home/yanghao05/go/pkg/mod GOFLAGS=-mod=mod GOPROXY=off go build ./...
|
||||
GOMODCACHE=/home/yanghao05/go/pkg/mod GOFLAGS=-mod=mod GOPROXY=off go vet ./...
|
||||
```
|
||||
|
||||
### 8.1 SkillBackend 单测
|
||||
|
||||
覆盖:
|
||||
|
||||
1. 缺 scope 直接失败。
|
||||
2. scope 指向的目录不存在时 `List` 返回空列表,不 panic。
|
||||
3. `Get` 不存在 skill 时返回 not found。
|
||||
4. 只列出当前 account/assistant 目录下的 skill。
|
||||
5. 不列出其他 assistant 的 skill。
|
||||
6. skill name 非法时 `Get` 失败。
|
||||
7. `../` 路径逃逸失败。
|
||||
8. symlink 指向 root 外部时失败。
|
||||
9. `SKILL.md` 超过大小限制失败。
|
||||
10. `context: fork` 首期失败。
|
||||
11. frontmatter 缺 name / description 时 `Get` 失败。
|
||||
12. `List` 跳过无效 skill 并记录 warning。
|
||||
13. `Get` 成功时返回 content 并记录审计日志。
|
||||
14. skill hash / mtime 计算稳定。
|
||||
|
||||
### 8.2 CaptainAgentRunner 单测
|
||||
|
||||
覆盖:
|
||||
|
||||
1. 构造 runner 并执行无 skill 的简单消息。
|
||||
2. skill middleware 能从当前 assistant 目录加载 skill metadata。
|
||||
3. 模型调用 skill 后能获得 skill content。
|
||||
4. skill backend 错误向上返回,不静默降级。
|
||||
5. feature flag 关闭时原 CaptainTaskService 路径不变。
|
||||
6. feature flag 开启时走 runner。
|
||||
7. `MaxIterations` 限制生效。
|
||||
|
||||
### 8.3 Eino tool calling 技术验证
|
||||
|
||||
必须增加一个最小测试或 spike,验证以下之一成立:
|
||||
|
||||
1. `ProviderChatModel` adapter 能接收 Eino `model.WithTools` option,并把 tool schema 传给当前 `llm.Provider`。
|
||||
2. 或者使用 Eino 原生 OpenAI-compatible ChatModel 构造模型,并能和当前 deepseek-v4-flash API 正常 tool call。
|
||||
|
||||
如果两者都不成立,SKILL middleware 无法真正被模型调用,应暂停实现并先补模型工具调用适配。
|
||||
|
||||
### 8.4 集成验证
|
||||
|
||||
准备测试目录:
|
||||
|
||||
```text
|
||||
backend/data/captain_skills/account_1/assistant_1/refund-policy/SKILL.md
|
||||
```
|
||||
|
||||
示例 `SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: refund-policy
|
||||
description: 处理退款、退货、补偿和售后政策相关问题
|
||||
context: inline
|
||||
---
|
||||
|
||||
当客户询问退款时:
|
||||
1. 先确认订单状态。
|
||||
2. 未发货订单可直接退款。
|
||||
3. 已发货订单需引导客户先拒收或退回商品。
|
||||
4. 不确定时要求人工客服确认,不要承诺具体到账时间。
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
- 开启试点 flag。
|
||||
- 调用 Captain Playground / reply suggestion。
|
||||
- 提问退款相关问题。
|
||||
- 日志中出现 skill list/get trace。
|
||||
- 返回内容遵循 refund policy。
|
||||
- 其他 assistant 不会加载该 skill。
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与控制
|
||||
|
||||
### 9.1 模型过度调用 skill
|
||||
|
||||
风险:模型明明不需要也加载大量 skill。
|
||||
|
||||
控制:
|
||||
|
||||
- `MaxIterations` 首期设为 4。
|
||||
- 每次 run 最多允许调用 skill 2 次。
|
||||
- SkillBackend 限制 metadata 和 content 大小。
|
||||
- Custom tool description 明确要求只在匹配时调用。
|
||||
|
||||
### 9.2 目录隔离被路径绕过
|
||||
|
||||
风险:通过 `../`、软链接、绝对路径读取其他目录。
|
||||
|
||||
控制:
|
||||
|
||||
- 后端计算 BaseDir。
|
||||
- Clean + EvalSymlinks。
|
||||
- slug 校验。
|
||||
- 不允许 request 传 path。
|
||||
- 单元测试覆盖 symlink 和路径穿越。
|
||||
|
||||
### 9.3 多实例目录不一致
|
||||
|
||||
风险:多副本部署时某个实例有 skill,另一个实例没有。
|
||||
|
||||
控制:
|
||||
|
||||
- PoC 阶段接受本地目录。
|
||||
- 生产前迁移到共享存储或数据库 `captain_skills` 表。
|
||||
- 日志记录 skill hash 和 mtime,便于排查实例差异。
|
||||
|
||||
### 9.4 Skill 内容变更不可追踪版本
|
||||
|
||||
风险:同一会话不同时间加载到不同版本 skill。
|
||||
|
||||
控制:
|
||||
|
||||
- 首期审计记录 skill path、mtime、hash。
|
||||
- 第二阶段引入数据库版本和发布状态。
|
||||
|
||||
### 9.5 当前模型适配不支持 tool calling
|
||||
|
||||
风险:Eino SKILL middleware 依赖 tool call;如果模型 adapter 不支持 tools,Agent 无法调用 `skill`。
|
||||
|
||||
控制:
|
||||
|
||||
- 实施前先做 Eino tool calling spike。
|
||||
- 如果当前 `llm.Provider` adapter 不适合,首期 CaptainAgentRunner 可直接构造 Eino 原生 OpenAI-compatible ChatModel。
|
||||
- spike 不通过前,不进入业务接入。
|
||||
|
||||
---
|
||||
|
||||
## 10. 分阶段任务清单
|
||||
|
||||
### 阶段 A:Eino SKILL tool calling spike
|
||||
|
||||
1. 新增最小测试或临时 spike,构造 Eino ChatModelAgent + skill middleware。
|
||||
2. 使用测试 SKILL 目录。
|
||||
3. 验证模型能看到 skill tool。
|
||||
4. 验证模型能调用 skill tool 并获得 content。
|
||||
5. 明确采用 `ProviderChatModel` adapter 还是 Eino 原生 ChatModel。
|
||||
6. spike 结果写入实现记录。
|
||||
|
||||
### 阶段 B:SKILL backend PoC
|
||||
|
||||
1. 新增 `CaptainAgentScope` context helper。
|
||||
2. 新增 `GoChatSkillBackend`。
|
||||
3. 实现 SKILL.md frontmatter parser。
|
||||
4. 实现目录解析与路径安全校验。
|
||||
5. 实现 List/Get。
|
||||
6. 实现 skill content formatter。
|
||||
7. 实现 tool description builder。
|
||||
8. 增加结构化日志。
|
||||
9. 补 SkillBackend 单测。
|
||||
|
||||
### 阶段 C:CaptainAgentRunner PoC
|
||||
|
||||
1. 新增模型 adapter 或 Eino 原生模型构造器。
|
||||
2. 新增 `CaptainAgentRunner`。
|
||||
3. 装配 Eino skill middleware。
|
||||
4. 注入 `CaptainAgentScope`。
|
||||
5. 限制 inline skill 和 MaxIterations。
|
||||
6. 处理 runner final message 提取。
|
||||
7. 补 runner 单测。
|
||||
|
||||
### 阶段 D:试点入口灰度接入
|
||||
|
||||
1. 在 Captain Playground 或 reply suggestion 增加 flag 分支。
|
||||
2. flag 关闭时保持现有逻辑。
|
||||
3. flag 开启时走 runner。
|
||||
4. skill 目录不存在时允许无 skill 执行。
|
||||
5. skill backend 真实错误向上返回。
|
||||
6. 补 service 单测。
|
||||
|
||||
### 阶段 E:验证与收敛
|
||||
|
||||
1. 准备本地测试 SKILLS 目录。
|
||||
2. 跑单元测试和 build/vet。
|
||||
3. 用真实 deepseek-v4-flash provider 做一次手工验证。
|
||||
4. 检查日志没有 API key、Authorization header 和完整客户消息。
|
||||
5. 输出验证报告。
|
||||
|
||||
---
|
||||
|
||||
## 11. 验收标准
|
||||
|
||||
首期完成必须满足:
|
||||
|
||||
1. 现有非 Agent Captain/Copilot 路径在 flag 关闭时行为不变。
|
||||
2. SkillBackend 只能读取当前 account/assistant 目录。
|
||||
3. SkillBackend 无法通过 `../` 或 symlink 逃逸 root。
|
||||
4. SkillBackend 对缺 scope fail fast。
|
||||
5. SkillBackend 对目录不存在返回空列表。
|
||||
6. SkillBackend 对无效 `SKILL.md` 有明确行为:List 跳过,Get 失败。
|
||||
7. CaptainAgentRunner 能在试点入口加载 inline skill。
|
||||
8. 模型能通过 Eino skill middleware 调用 skill 并获得 content。
|
||||
9. Skill 使用有结构化日志或审计记录。
|
||||
10. `go test ./internal/service -run 'CaptainAgent|CaptainSkill|CaptainTask'` 通过。
|
||||
11. `go build ./...` 通过。
|
||||
12. 日志和错误响应不包含 API key、Authorization header 或完整敏感客户消息。
|
||||
|
||||
---
|
||||
|
||||
## 12. 后续演进
|
||||
|
||||
首期跑通后再考虑:
|
||||
|
||||
- 新增 `captain_skills` / `captain_playbooks` 表。
|
||||
- 前端 SKILL 管理页面。
|
||||
- SKILL 版本、发布、回滚。
|
||||
- inbox/team/channel 级 skill scope。
|
||||
- `fork` / `fork_with_context`。
|
||||
- Eino `summarization`。
|
||||
- Eino `reduction`。
|
||||
- Eino `dynamictool/toolsearch`。
|
||||
- streaming ADK Agent。
|
||||
- auto-reply 场景白名单启用。
|
||||
|
||||
---
|
||||
|
||||
## 13. 推荐立即执行顺序
|
||||
|
||||
建议先实现:
|
||||
|
||||
1. Eino SKILL tool calling spike。
|
||||
2. GoChatSkillBackend。
|
||||
3. CaptainAgentRunner PoC。
|
||||
4. 试点入口灰度接入。
|
||||
|
||||
理由:
|
||||
|
||||
- SKILL 依赖 Eino ADK Agent 和 tool calling,必须先验证模型适配是否成立。
|
||||
- 每个 Agent 一个目录足够支持 PoC,但必须有 backend 适配层确保路径和 scope 安全。
|
||||
- 先从 Playground / reply suggestion 试点,避免影响自动回复客户链路。
|
||||
Reference in New Issue
Block a user