目录
- [1. 项目概述](#1. 项目概述 "#1-%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%BF%B0")
- [2. 整体架构设计](#2. 整体架构设计 "#2-%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1")
- [3. 核心分层架构](#3. 核心分层架构 "#3-%E6%A0%B8%E5%BF%83%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84")
- [4. 关键技术栈](#4. 关键技术栈 "#4-%E5%85%B3%E9%94%AE%E6%8A%80%E6%9C%AF%E6%A0%88")
- [5. 目录结构分析](#5. 目录结构分析 "#5-%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%E5%88%86%E6%9E%90")
- [6. 核心模块详解](#6. 核心模块详解 "#6-%E6%A0%B8%E5%BF%83%E6%A8%A1%E5%9D%97%E8%AF%A6%E8%A7%A3")
- [7. 数据流分析:简单工作流示例](#7. 数据流分析:简单工作流示例 "#7-%E6%95%B0%E6%8D%AE%E6%B5%81%E5%88%86%E6%9E%90%E7%AE%80%E5%8D%95%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A4%BA%E4%BE%8B")
- [8. 总结与展望](#8. 总结与展望 "#8-%E6%80%BB%E7%BB%93%E4%B8%8E%E5%B1%95%E6%9C%9B")
1. 项目概述
1.1 Coze Studio 简介
Coze Studio 是一个开源的 AI Agent 开发平台,提供从开发到部署的全流程支持。后端采用 Golang 开发,前端使用 React + TypeScript ,整体架构基于微服务和**领域驱动设计(DDD)**原则构建。
核心特性:
- 🎯 提供 AI Agent 开发所需的核心技术:Prompt、RAG、Plugin、Workflow
- 🚀 低代码/无代码的可视化开发工具
- 🔧 完整的模型管理、知识库、MCP(
在 coze-studio-plus 中实现了)、插件系统 - 📊 强大的 Workflow 编排引擎
- 🌐 RESTful API 和 SDK 支持

1.2 技能点
| 技术 | 描述 |
|---|---|
| Golang | 高性能、并发友好、部署简单 |
| Hertz | CloudWeGo 高性能 HTTP 框架 |
| Eino | AI Workflow 运行时引擎,类似于 python 的 LangGraph |
| DDD | 复杂业务逻辑的最佳实践 |
| MySQL | 主数据存储 |
| Redis | 缓存和 Checkpoint 存储 |
| Elasticsearch | 搜索和知识库检索 |
2. 整体架构设计
2.1 宏观架构图
React + TypeScript
Web前端界面"] B["API/SDK Client
API和SDK客户端
第三方系统集成"] end subgraph L6["第6层:接入层 API Layer"] direction LR C["Hertz HTTP Server
:8888
HTTP服务器,接收请求"] D["Middleware Stack
Auth/Log/CORS
中间件栈:认证/日志/跨域"] E["Router
API Routes
路由分发,API路由注册"] end subgraph L5["第5层:应用服务层 Application Layer"] direction LR F1["WorkflowApp
工作流应用服务
编排工作流业务流程"] F2["SingleAgentApp
单智能体应用服务
管理Agent创建和执行"] F3["PluginApp
插件应用服务
管理插件生命周期"] F4["KnowledgeApp
知识库应用服务
管理知识库CRUD"] F5["ConversationApp
对话应用服务
管理对话会话"] end subgraph L4["第4层:领域层 Domain Layer"] direction LR G1["Workflow Domain
工作流领域服务
核心工作流业务逻辑"] G2["Agent Domain
智能体领域服务
Agent核心业务逻辑"] G3["Plugin Domain
插件领域服务
插件核心业务逻辑"] G4["Knowledge Domain
知识库领域服务
知识库核心逻辑"] end subgraph L3["第3层:跨域服务层 Cross Domain Layer"] direction LR H1["Workflow Cross
工作流跨域服务"] H2["Message Cross
消息跨域服务"] H3["Database Cross
数据库跨域服务"] H4["Plugin Cross
插件跨域服务"] end subgraph L2["第2层:基础设施层 Infrastructure Layer"] direction LR I1[("MySQL
关系型数据库")] I2[("Redis
内存数据库")] I3[("Elasticsearch
搜索引擎")] I4["OSS
对象存储"] I5["EventBus
事件总线"] I6["CodeRunner
代码执行器"] end subgraph L1["第1层:外部服务层 External Services"] direction LR J["LLM Providers
OpenAI/Claude/..."] K["Embedding Service
向量嵌入服务"] end %% 客户端到接入层 A --> C B --> C %% 接入层内部流转(顺序执行) C --> D D --> E %% 接入层到应用服务层(路由分发) E --> F1 E --> F2 E --> F3 E --> F4 E --> F5 %% 应用服务层到领域层(主要调用关系,对齐减少交叉) F1 --> G1 F2 --> G2 F3 --> G3 F4 --> G4 F5 --> G1 F5 --> G2 %% 领域层到跨域服务层(虚线表示间接调用) G1 -.-> H1 G2 -.-> H2 G3 -.-> H4 G4 -.-> H3 %% 跨域服务层到基础设施层 H1 --> I1 H2 --> I2 H3 --> I1 H4 --> I1 %% 领域层直接访问基础设施(主要依赖,简化连接) G1 --> I2 G1 --> I5 G4 --> I3 G3 --> I4 G3 --> I6 %% 领域层到外部服务 G1 --> J G2 --> J G4 --> K %% 样式 style L7 fill:#e1f5ff style L6 fill:#fff4e1 style L5 fill:#e8f5e9 style L4 fill:#f3e5f5 style L3 fill:#fce4ec style L2 fill:#fff9c4 style L1 fill:#f5f5f5
2.2 架构特点分析
| 层级/模块 | 职责(简述) | 依赖关系(→ 表示依赖) | 主要联系方式 |
|---|---|---|---|
| API (backend/api) | 暴露 HTTP 接口层,使用 Hertz(Go 的高性能 Web 框架)处理请求、路由和响应组装。 | API → Application API → Types | - 通过 Application 的服务接口调用业务逻辑。 - 使用 Types 定义请求/响应 DTO(数据传输对象)。 - 与前端 IDL 联动,确保 API 契约一致。 |
| Application (backend/application) | 应用层:协调领域层和基础设施,封装业务用例(如 API 聚合、事务管理)。不含核心业务逻辑。 | Application → Domain Application → Infra (Contract) Application → Pkg | - 调用 Domain 的聚合根/服务执行业务。 - 通过 Infra Contract 的接口(如 Repository)访问数据。 - 使用 Pkg 的工具(如依赖注入)组装组件。 |
| Domain (backend/domain) | 领域层:DDD 核心,封装实体、值对象、聚合、领域服务和事件。纯业务逻辑,无技术细节。 | Domain ← (无依赖,独立) Domain → Crossdomain (可选共享) | - 被 Application 调用,提供行为方法(如 Order.Create())。 - 通过领域事件(Domain Event)与 Crossdomain 通信,解耦跨域逻辑。 - 定义 Infra 的合约接口(如 Repository 接口)。 |
| Crossdomain (backend/crossdomain) | 跨领域层:处理多限界上下文(Bounded Context)间的共享模型、事件和规则(如全局用户 ID)。 | Crossdomain → Domain (部分) Crossdomain ← Application (调用) | - 作为 Domain 的补充,提供共享值对象/事件。 - 通过事件总线(Event Bus)与多个 Domain 交互,避免直接耦合。 |
| Infra/contract (backend/infra/contract) | 基础设施合约层:定义抽象接口(如 Repository、Cache 接口),由 Domain 驱动。 | Infra/Contract ← Domain (接口由 Domain 定义) Infra/Contract → Impl | - 为 Domain 提供"门面",隐藏实现细节。 - Application 通过这些接口间接访问 Impl。 - 确保可替换性(e.g., 换数据库)。 |
| Infra/Impl (backend/infra/impl) | 基础设施实现层:具体实现合约,如数据库(GORM/MySQL)、缓存(Redis)、消息队列。 | Infra/Impl → Contract (实现接口) | - 注入到 Application/Domain 中(通过 Pkg 的 DI 容器)。 - 只响应合约调用,不反向影响业务层。 |
| Pkg (backend/pkg) | 共享包层:工具和依赖注入(如 Wire),跨层复用。 | Pkg ← (所有层可选依赖) | - 提供 DI(依赖注入)框架,连接 Application 与 Infra。 - 包含通用工具(如日志、配置),减少重复代码。 |
| Types (backend/types) | 类型定义层:共享数据结构(如枚举、结构体),用于 DTO 和内部类型。 | Types ← API/Application (引用) | - 统一类型系统,避免散乱定义。 - 与 IDL 同步,确保前后端类型一致。 |
| Common (backend/common) | 通用组件层:基础工具(如错误码、常量)。 | Common ← (辅助其他层) | - 被所有层引用,提供底层支持(如日志中间件)。 |
| Docker (backend/docker) | 部署配置层:Dockerfile 和 Compose,用于容器化。 | Docker ← (外部,无直接依赖) | - 打包整个后端服务,支持微服务部署。 - 与整体项目集成,无业务联系。 |
Coze Studio 的 DDD 架构遵循四层洋葱模型(API → Application → Domain → Infra)辅以共享层(Pkg/Types/Crossdomain),数据流为:外部 HTTP 请求经 API 层路由/验证后传入 Application 协调用例与事务,调用 Domain 层纯业务实体/聚合/事件处理核心逻辑(如代理创建),Domain 通过合约接口(如 Repository)驱动 Infra 层实现持久化(OceanBase/ES)、缓存(Cache)与事件分发(EventBus),处理结果经 Crossdomain 共享跨域数据后逆向回流至 API 响应前端,同时 Pkg 注入依赖与 Types 统一类型确保全链路一致性。
2.2.2 DDD 实践
领域驱动设计核心要素:
- 实体(Entity):具有唯一标识的领域对象
- 值对象(Value Object):无标识的不可变对象
- 聚合根(Aggregate Root):管理一组相关对象的生命周期
- 领域服务(Domain Service):跨实体的业务逻辑
- 仓储(Repository):数据访问抽象层
- 应用服务(Application Service):用例协调者
2.2.3 依赖倒置原则
领域接口定义] B[Application Layer
依赖接口] C[Infrastructure Layer
实现接口] D[Cross Domain
依赖接口] B -->|依赖| A C -.->|实现| A D -->|依赖| A style A fill:#f9f,stroke:#333,stroke-width:4px
优势:
- ✅ 核心业务逻辑不依赖基础设施
- ✅ 易于测试(可 Mock)
- ✅ 灵活替换底层实现
3. 核心分层架构
3.1 各层详细职责
3.1.1 API 层(/backend/api)
bash
api/
├── handler/ # HTTP 请求处理器
│ └── coze/ # 具体业务 Handler
├── middleware/ # 中间件(认证、日志、CORS)
├── model/ # API 数据模型(DTO)
└── router/ # 路由注册
核心职责:
- 🔸 接收 HTTP 请求,解析参数
- 🔸 调用 Application Service
- 🔸 返回统一格式的响应
- 🔸 参数校验和错误处理
关键代码示例 (main.go):
go
func main() {
ctx := context.Background()
// 1. 加载环境变量
loadEnv()
// 2. 初始化所有服务(依赖注入)
application.Init(ctx)
// 3. 启动 HTTP 服务器
startHttpServer()
}
func startHttpServer() {
s := server.Default(opts...)
// 中间件链(顺序很重要!)
s.Use(middleware.ContextCacheMW()) // 上下文缓存
s.Use(middleware.RequestInspectorMW()) // 请求检查
s.Use(middleware.SetHostMW()) // 设置主机信息
s.Use(middleware.SetLogIDMW()) // 日志ID
s.Use(corsHandler) // CORS
s.Use(middleware.AccessLogMW()) // 访问日志
s.Use(middleware.OpenapiAuthMW()) // OpenAPI 认证
s.Use(middleware.SessionAuthMW()) // Session 认证
s.Use(middleware.I18nMW()) // 国际化
router.GeneratedRegister(s)
s.Spin()
}
中间件执行顺序:
3.1.2 应用服务层(/backend/application)
bash
── application/ # 应用层,组合领域对象和基础设施实现
├── app/ # 应用服务
├── conversation/ # 会话应用服务
├── knowledge/ # 知识应用服务
├── memory/ # 内存应用服务
├── modelmgr/ # 模型管理应用服务
├── plugin/ # 插件应用服务
├── prompt/ # 提示词应用服务
├── search/ # 搜索应用服务
├── singleagent/ # 单一代理应用服务
├── user/ # 用户应用服务
└── workflow/ # 工作流应用服务
核心职责:
- 🔸 编排多个 Domain Service 完成业务用例
- 🔸 事务管理
- 🔸 权限检查
- 🔸 事件发布
服务初始化 (application.go):
go
func Init(ctx context.Context) (err error) {
// 1. 初始化上下文缓存
ctx = ctxcache.Init(ctx)
// 2. 初始化基础设施
infra, err := appinfra.Init(ctx)
// 3. 初始化事件总线(依赖基础设施)
eventbus := initEventBus(infra)
// 4. 初始化基础服务(依赖基础设施和事件总线)
basicServices, err := initBasicServices(ctx, infra, eventbus)
// 5. 初始化主服务(依赖基础服务)
primaryServices, err := initPrimaryServices(ctx, basicServices)
// 6. 初始化复杂服务(依赖主服务)
complexServices, err := initComplexServices(ctx, primaryServices)
// 7. 配置跨域服务
crossconnector.SetDefaultSVC(connectorImpl.InitDomainService(basicServices.connectorSVC.DomainSVC))
crossdatabase.SetDefaultSVC(databaseImpl.InitDomainService(primaryServices.memorySVC.DatabaseDomainSVC))
crossknowledge.SetDefaultSVC(knowledgeImpl.InitDomainService(primaryServices.knowledgeSVC.DomainSVC))
crossplugin.SetDefaultSVC(pluginImpl.InitDomainService(primaryServices.pluginSVC.DomainSVC, infra.OSS))
...
return nil
}
服务依赖简要关系图:
3.1.3 领域层(/backend/domain)
bash
── domain/ # 领域层,包含核心业务逻辑
├── agent/ # 代理领域逻辑
├── app/ # 应用领域逻辑
├── conversation/ # 会话领域逻辑
├── knowledge/ # 知识领域逻辑
├── memory/ # 内存领域逻辑
├── modelmgr/ # 模型管理领域逻辑
├── plugin/ # 插件领域逻辑
├── prompt/ # 提示词领域逻辑
├── search/ # 搜索领域逻辑
├── user/ # 用户领域逻辑
└── workflow/ # 工作流领域逻辑
领域模型核心:
Entity 示例 (domain/workflow/entity/workflow.go):
go
// Workflow 实体(聚合根)
type Workflow struct {
ID int64
SpaceID int64
AppID *int64
Name string
Description string
Mode WorkflowMode // CHATFLOW | WORKFLOW
Canvas string // JSON schema
Version string
Status Status
CreatedAt time.Time
UpdatedAt time.Time
}
// 业务方法
func (w *Workflow) GetBasic() *BasicInfo { ... }
func (w *Workflow) GetVersion() string { ... }
Domain Service 接口 (domain/workflow/interface.go):
go
type Service interface {
// 元数据管理
Create(ctx context.Context, meta *vo.MetaCreate) (int64, error)
Get(ctx context.Context, policy *vo.GetPolicy) (*entity.Workflow, error)
Delete(ctx context.Context, policy *vo.DeletePolicy) ([]int64, error)
UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpdate) error
// 执行相关
Executable // 嵌入执行接口
AsTool // 作为工具使用
// Workflow 特有逻辑
Publish(ctx context.Context, policy *vo.PublishPolicy) error
WorkflowSchemaCheck(ctx context.Context, wf *entity.Workflow, checks []workflow.CheckType) ([]*workflow.CheckResult, error)
// 会话相关
ChatFlowRole
Conversation
}
3.1.4 跨域服务层(/backend/crossdomain)
设计目的:
- 解决循环依赖问题
- 提供统一的跨领域调用接口
- 隔离不同领域的实现细节
bash
├── crossdomain/ # 跨领域防腐层
│ ├── contract/ # 跨领域接口定义
│ ├── impl/ # 跨领域接口实现
│ └── workflow/ # 工作流跨领域实现
#示例
crossdomain/
├── workflow/
│ ├── contract.go # 接口定义
│ ├── impl/ # 接口实现(适配器)
│ └── model/ # 跨域数据模型
├── knowledge/
├── plugin/
└── ...
使用模式:
注意 :以下代码为伪代码示例,用于说明跨域调用的概念模式。实际代码中的函数名和方法名可能不同。
go
// 伪代码示例:在 Plugin Domain 中调用 Workflow
import crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/workflow"
func (s *PluginService) UseInWorkflow(ctx context.Context, wfID int64) error {
// 通过 crossdomain 接口调用(伪代码)
wf, err := crossworkflow.GetDefaultSVC().GetWorkflow(ctx, wfID)
if err != nil {
return err
}
// ...
}
真实代码示例 (来自 backend/domain/agent/singleagent/internal/agentflow/node_tool_workflow.go):
go
// 真实代码:在 Agent Domain 中调用 Workflow
import crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/workflow"
// 获取 Workflow 作为工具使用
func newWorkflowTools(ctx context.Context, conf *workflowConfig) ([]workflow.ToolFromWorkflow, map[string]struct{}, error) {
var policies []*vo.GetPolicy
...
workflowTools, err := crossworkflow.DefaultSVC().WorkflowAsModelTool(ctx, policies)
...
return workflowTools, toolsReturnDirectly, err
}
3.1.5 基础设施层(/backend/infra)
bash
infra/
├── orm/ # ORM 封装
├── rdb/ # 关系数据库
├── cache/ # Redis 缓存
├── es/ # Elasticsearch
├── storage/ # 对象存储(OSS)
├── eventbus/ # 事件总线
├── embedding/ # 向量嵌入
├── coderunner/ # 代码执行沙箱
├── checkpoint/ # 检查点存储
└── ...
核心职责:
- 🔸 封装技术实现细节(数据库、缓存、消息队列等)
- 🔸 提供统一的接口抽象,不依赖具体实现
- 🔸 实现依赖倒置,领域层只依赖接口
代码示例:
1. Elasticsearch 使用示例
application->domain->infra
go
//coze-studio/backend/application/application.go
func initComplexServices(ctx context.Context, p *primaryServices) (*complexServices, error) {
...
searchSVC, err := search.InitService(ctx, p.toSearchServiceComponents(singleAgentSVC, appSVC))
if err != nil {
return nil, err
}
...
}
//coze-studio/backend/application/search/init.go
func InitService(ctx context.Context, s *ServiceComponents) (*SearchApplicationService, error) {
searchDomainSVC := search.NewDomainService(ctx, s.ESClient)
...
return SearchSVC, nil
}
//coze-studio/backend/domain/search/service/search.go
// 领域服务通过依赖注入获取 ES 客户端
func NewDomainService(ctx context.Context, e es.Client) Search {
return &searchImpl{
esClient: e,
}
}
// 使用 ES 进行全文检索
func (s *searchImpl) SearchProjects(ctx context.Context, req *SearchRequest) error {
searchReq := &es.Request{
Query: &es.Query{
Bool: &es.BoolQuery{
Must: []es.Query{
es.NewEqualQuery("space_id", strconv.FormatInt(req.SpaceID, 10)),
},
},
},
}
result, err := s.esClient.Search(ctx, "project_index", searchReq)
if err != nil {
return err
}
// 处理搜索结果...
return nil
}
//coze-studio/backend/infra/es/es.go
type Client interface {
...
Search(ctx context.Context, index string, req *Request) (*Response, error)
...
}
//最后会根据实例化的 es 去调用
impl/es
es_impl.go
es7.go
es8.go
...
2. Storage(对象存储)使用示例 (来自 backend/domain/plugin/service/exec_tool.go):
go
type toolExecutor struct {
// 通过依赖注入获取存储服务
oss storage.Storage
}
// 上传文件到对象存储
func (t *toolExecutor) uploadFile(ctx context.Context, content []byte, objectKey string) error {
return t.oss.PutObject(ctx, objectKey, content)
}
// 获取文件的预签名URL
func (t *toolExecutor) getFileUrl(ctx context.Context, objectKey string) (string, error) {
return t.oss.GetObjectUrl(ctx, objectKey, storage.WithExpiration(time.Hour))
}
3. Cache(Redis)使用示例 (来自 backend/domain/workflow/internal/nodes/llm/llm.go):
go
// 使用上下文缓存(基于 Redis)
import "github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
// 存储数据到缓存
ctxcache.Store(ctx, "raw_output_key", outputData)
ctxcache.Store(ctx, "chat_history_key", messages)
// 从缓存读取数据
rawOutput, found := ctxcache.Get[string](ctx, "raw_output_key")
if found {
// 使用缓存的数据
}
4. EventBus(事件总线)接口定义 (来自 backend/infra/eventbus/eventbus.go):
go
// 事件生产者接口
type Producer interface {
Send(ctx context.Context, body []byte, opts ...SendOpt) error
BatchSend(ctx context.Context, bodyArr [][]byte, opts ...SendOpt) error
}
// 事件消费者接口
type ConsumerHandler interface {
HandleMessage(ctx context.Context, msg *Message) error
}
// 使用示例:发布事件
producer.Send(ctx, eventData, eventbus.WithTopic("workflow_events"))
// 使用示例:消费事件
eventbus.GetDefaultSVC().RegisterConsumer(
"server", "workflow_events", "group1",
&MyHandler{},
)
4. 关键技术栈
4.1 核心框架
4.1.1 Hertz - HTTP 框架
- 🚀 CloudWeGo 生态,性能优异
- 🔧 中间件机制完善
- 📦 代码生成支持(基于 IDL)
4.1.2 Eino - AI Workflow 引擎
Eino 是字节开源的 Go 语言编写的终极大型语言模型(LLM)应用开发框架,它从开源社区中的诸多优秀 LLM 应用开发框架,如 LangChain 和 LlamaIndex 等获取灵感,同时借鉴前沿研究成果与实际应用,提供了一个强调简洁性、可扩展性、可靠性与有效性,且更符合 Go 语言编程惯例的 LLM 应用开发框架。
Github: github.com/cloudwego/e...
核心概念:
- Runnable: 可执行单元的抽象
- Compose: 组合模式,构建复杂 Workflow
- Schema: 数据流定义
- Checkpoint: 状态持久化,支持中断恢复
使用示例:
go
import "github.com/cloudwego/eino/compose"
// 创建 Workflow
wf := compose.NewWorkflow[map[string]any, map[string]any](
compose.WithGenLocalState(GenState()),
)
// 添加节点
wf.AddLambdaNode("llm_node", func(ctx context.Context, input map[string]any) (map[string]any, error) {
// 调用 LLM
return llmService.Chat(ctx, input)
})
// 编译运行
runner, _ := wf.Compile(ctx, compose.WithCheckPointStore(store))
output, _ := runner.Invoke(ctx, input)
4.2 数据存储
| 存储 | 用途 | 示例 |
|---|---|---|
| MySQL | 主数据存储 | Workflow 元数据、用户信息 |
| Redis | 缓存 + Checkpoint | 执行状态、临时数据 |
| Elasticsearch | 全文检索 | 资源搜索、知识库检索 |
| OSS | 对象存储 | 文件上传、图片、模型文件 |
5. 目录结构分析
5.1 完整目录树
csharp
├── backend/ # 后端服务
│ ├── api/ # API 处理器和路由
│ │ ├── handler/ # 处理器
│ │ ├── internal/ # 内部工具
│ │ ├── middleware/ # 中间件组件
│ │ ├── model/ # API 模型定义
│ │ └── router/ # 路由定义
│ ├── application/ # 应用层,组合领域对象和基础设施实现
│ │ ├── app/ # 应用服务
│ │ ├── conversation/ # 会话应用服务
│ │ ├── knowledge/ # 知识应用服务
│ │ ├── memory/ # 内存应用服务
│ │ ├── modelmgr/ # 模型管理应用服务
│ │ ├── plugin/ # 插件应用服务
│ │ ├── prompt/ # 提示词应用服务
│ │ ├── search/ # 搜索应用服务
│ │ ├── singleagent/ # 单一代理应用服务
│ │ ├── user/ # 用户应用服务
│ │ └── workflow/ # 工作流应用服务
│ ├── conf/ # 配置文件
│ │ ├── model/ # 模型配置
│ │ ├── plugin/ # 插件配置
│ │ └── prompt/ # 提示词配置
│ ├── crossdomain/ # 跨领域防腐层
│ │ ├── contract/ # 跨领域接口定义
│ │ ├── impl/ # 跨领域接口实现
│ │ └── workflow/ # 工作流跨领域实现
│ ├── domain/ # 领域层,包含核心业务逻辑
│ │ ├── agent/ # 代理领域逻辑
│ │ ├── app/ # 应用领域逻辑
│ │ ├── conversation/ # 会话领域逻辑
│ │ ├── knowledge/ # 知识领域逻辑
│ │ ├── memory/ # 内存领域逻辑
│ │ ├── modelmgr/ # 模型管理领域逻辑
│ │ ├── plugin/ # 插件领域逻辑
│ │ ├── prompt/ # 提示词领域逻辑
│ │ ├── search/ # 搜索领域逻辑
│ │ ├── user/ # 用户领域逻辑
│ │ └── workflow/ # 工作流领域逻辑
│ ├── infra/ # 基础设施实现层
│ │ ├── contract/ # 基础设施接口定义
│ │ └── impl/ # 基础设施接口实现
│ ├── pkg/ # 无外部依赖的工具方法
│ │ ├── ctxcache/ # 上下文缓存工具
│ │ ├── errorx/ # 错误处理工具
│ │ ├── goutil/ # Go 语言工具
│ │ ├── logs/ # 日志工具
│ │ └── safego/ # 安全 Go 工具
│ └── types/ # 类型定义
│ ├── consts/ # 常量定义
│ ├── ddl/ # 数据定义语言
│ └── errno/ # 错误码定义
├── frontend/ # 前端应用
...
├── idl/ # 接口定义语言文件
5.2 目录职责矩阵
| 目录 | 职责 | 依赖方向 |
|---|---|---|
api |
HTTP 请求处理 | → application |
application |
业务流程编排 | → domain |
domain |
核心业务逻辑 | → crossdomain |
crossdomain |
跨域服务抽象 | → domain (interface) |
infra |
技术实现细节 | ← domain (实现接口) |
pkg |
通用工具 | 被所有层使用 |
6. 核心模块详解
6.1.1 Workflow 架构
6.1.2 核心组件
Schema(数据流定义)
职责:定义 Workflow 的结构和数据流
go
// backend/domain/workflow/internal/schema/workflow_schema.go WorkflowSchema 定义 Workflow 的完整结构
type WorkflowSchema struct {
Nodes []*NodeSchema `json:"nodes"`
Connections []*Connection `json:"connections"`
Hierarchy map[vo.NodeKey]vo.NodeKey `json:"hierarchy,omitempty"` // child node key-> parent node key
Branches map[vo.NodeKey]*BranchSchema `json:"branches,omitempty"`
GeneratedNodes []vo.NodeKey `json:"generated_nodes,omitempty"` // generated nodes for the nodes in batch mode
nodeMap map[vo.NodeKey]*NodeSchema // won't serialize this
compositeNodes []*CompositeNode // won't serialize this
requireCheckPoint bool // won't serialize this
requireStreaming bool
historyRounds int64
}
// Connection 定义节点之间的连接关系
type Connection struct {
FromNode vo.NodeKey `json:"from_node"`
ToNode vo.NodeKey `json:"to_node"`
FromPort *string `json:"from_port,omitempty"`
}
Compose(编排引擎)
职责:将 Schema 编译成可执行的 Workflow
go
//backend/domain/workflow/internal/compose/workflow.go NewWorkflow 创建 Workflow 实例
func NewWorkflow(ctx context.Context, sc *schema.WorkflowSchema, opts ...WorkflowOption) (*Workflow, error) {
sc.Init() // 初始化 Schema,构建 nodeMap 等内部结构
wf := &Workflow{
workflow: compose.NewWorkflow[map[string]any, map[string]any](
compose.WithGenLocalState(GenState()),
),
hierarchy: sc.Hierarchy,
connections: sc.Connections,
schema: sc,
}
wf.streamRun = sc.RequireStreaming()
wf.requireCheckpoint = sc.RequireCheckpoint()
// 处理选项
wfOpts := &workflowOptions{}
for _, opt := range opts {
opt(wfOpts)
}
// 添加所有复合节点(包含子工作流的节点)
compositeNodes := sc.GetCompositeNodes()
for i := range compositeNodes {
cNode := compositeNodes[i]
if err := wf.AddCompositeNode(ctx, cNode); err != nil {
return nil, err
}
}
// 添加所有普通节点
for _, ns := range sc.Nodes {
if err := wf.AddNode(ctx, ns); err != nil {
return nil, err
}
}
// 编译成可执行的 Runner
runner, err := wf.Compile(ctx, wfOpts.compileOpts...)
if err != nil {
return nil, err
}
wf.Runner = runner
return wf, nil
}
Nodes(节点实现)
支持的节点类型:
| 节点类型 | 功能 | 实现路径 |
|---|---|---|
| Entry | 工作流入口 | nodes/entry |
| Exit | 工作流出口 | nodes/exit |
| LLM | 大模型调用 | nodes/llm |
| Plugin | 插件调用 | nodes/plugin |
| Code | 代码执行 | nodes/code |
| Knowledge | 知识库检索 | nodes/knowledge |
| Database | 数据库操作 | nodes/database |
| Selector | 条件分支 | nodes/selector |
| Loop | 循环迭代 | nodes/loop |
| HTTPRequester | HTTP 请求 | nodes/httprequester |
节点构建机制:
go
//backend/domain/workflow/internal/compose/node_builder.go New 从 NodeSchema 实例化实际的节点类型
func New(ctx context.Context, s *schema.NodeSchema,
inner compose.Runnable[map[string]any, map[string]any], // 复合节点的内部工作流
sc *schema.WorkflowSchema, // 节点所在的工作流 Schema
deps *dependencyInfo, // 节点的依赖信息
requireCheckpoint bool,
) (_ *Node, err error) {
// 如果 NodeSchema 的 Configs 实现了 NodeBuilder 接口,使用它来构建节点
nb, ok := s.Configs.(schema.NodeBuilder)
if ok {
opts := []schema.BuildOption{
schema.WithWorkflowSchema(sc),
schema.WithInnerWorkflow(inner),
}
// 构建实际的 InvokableNode 等
n, err := nb.Build(ctx, s, opts...)
if err != nil {
return nil, err
}
// 将 InvokableNode 包装成 NodeRunner,转换为 eino 的 Lambda
return toNode(s, n), nil
}
// 处理特殊节点类型
switch s.Type {
case entity.NodeTypeLambda:
return &Node{Lambda: s.Lambda}, nil
case entity.NodeTypeSubWorkflow:
subWorkflow, err := buildSubWorkflow(ctx, s, requireCheckpoint)
if err != nil {
return nil, err
}
return toNode(s, subWorkflow), nil
default:
panic(fmt.Sprintf("node schema's Configs does not implement NodeBuilder. type: %v", s.Type))
}
}
说明 :LLM 节点通过实现 NodeBuilder 接口来构建,具体实现在 backend/domain/workflow/internal/nodes/llm/llm.go 中。
Execute(执行引擎)
核心功能:
- 🔸 执行上下文管理
- 🔸 事件发送(开始、结束、错误)
- 🔸 流式输出支持
- 🔸 中断和恢复
事件类型 (真实代码来自 backend/domain/workflow/internal/execute/event.go):
go
type EventType string
const (
// 工作流级别事件
WorkflowStart EventType = "workflow_start"
WorkflowSuccess EventType = "workflow_success"
WorkflowFailed EventType = "workflow_failed"
WorkflowCancel EventType = "workflow_cancel"
WorkflowInterrupt EventType = "workflow_interrupt"
WorkflowResume EventType = "workflow_resume"
// 节点级别事件
NodeStart EventType = "node_start"
NodeEnd EventType = "node_end"
NodeEndStreaming EventType = "node_end_streaming" // 绝对结束,所有流式内容发送完毕
NodeError EventType = "node_error"
NodeStreamingInput EventType = "node_streaming_input"
NodeStreamingOutput EventType = "node_streaming_output"
// 工具调用事件
FunctionCall EventType = "function_call"
ToolResponse EventType = "tool_response"
ToolStreamingResponse EventType = "tool_streaming_response"
ToolError EventType = "tool_error"
)
6.1.3 Workflow 生命周期
6.2 Plugin 模块
6.2.1 Plugin 架构
6.2.2 Plugin 调用流程
go
//backend/domain/workflow/internal/nodes/plugin/exec.go Plugin 节点执行(简化版,展示核心逻辑)
func (n *PluginNode) execute(ctx context.Context, input map[string]any) (map[string]any, error) {
// 1. 准备执行请求
req := &model.ExecuteToolRequest{
PluginID: pe.PluginID,
ToolID: pe.ToolID,
Input: input,
// ... 其他配置
}
execOpts := []model.ExecuteToolOpt{
model.WithInvalidRespProcessStrategy(consts.InvalidResponseProcessStrategyOfReturnDefault),
}
if pe.PluginVersion != nil {
execOpts = append(execOpts, model.WithToolVersion(*pe.PluginVersion))
}
// 2. 通过跨域服务调用 Plugin(注意:使用 DefaultSVC() 而不是 GetDefaultSVC())
r, err := crossplugin.DefaultSVC().ExecuteTool(ctx, req, execOpts...)
if err != nil {
// 处理中断事件(如需要 OAuth 授权)
if extra, ok := compose.IsInterruptRerunError(err); ok {
pluginTIE, ok := extra.(*model.ToolInterruptEvent)
if ok {
// 创建工作流中断事件
// ...
}
}
return nil, err
}
// 3. 返回执行结果
return r.Output, nil
}
关键点:
- 使用
crossplugin.DefaultSVC()而不是crossplugin.GetDefaultSVC() - 支持工具版本控制
- 支持中断和恢复机制(如 OAuth 授权)
6.3 Knowledge 模块
6.3.1 RAG 流程
7. 数据流分析:简单工作流示例
7.1 场景描述
我们分析一个最简单的 Workflow:
css
[开始] → [大模型] → [结束]
说明 :本节以 OpenAPI 方式执行 Workflow 为例进行分析。Coze Studio 提供两套 API:
| API 类型 | 路径前缀 | 用途 | 认证方式 |
|---|---|---|---|
| OpenAPI | /v1/ |
对外开放的 API,供第三方调用 | API Key(Personal Access Token) |
| 内部 API | /api/ |
Web 前端使用的内部 API | Session 认证 |
本节重点分析 OpenAPI 的执行流程,因为它更完整地展示了认证、权限校验、版本控制等机制。
Canvas JSON 结构:
json
{
"nodes": [
{
"key": "entry",
"type": "entry",
"outputs": {"user_input": "string"}
},
{
"key": "llm_1",
"type": "llm",
"config": {
"model": "gpt-4",
"prompt": "{{user_input}}"
}
},
{
"key": "exit",
"type": "exit",
"inputs": {"output": "string"}
}
],
"connections": [
{"from": "entry.user_input", "to": "llm_1.input"},
{"from": "llm_1.output", "to": "exit.output"}
]
}
7.2 完整数据流图
workflow_id:123
input:user_input=Hello
外部调用入口"] end subgraph "2. API 层(Hertz Handler)" B1["WorkflowExecuteHandler
OpenAPI入口处理器"] B2["Parse Request Body
解析请求体"] B3["Validate Parameters
参数校验"] end subgraph "3. 应用层(`application/workflow`)" C1["WorkflowApp.Execute/OpenAPIRun
应用服务编排"] C2["Build ExecuteConfig
构建执行配置"] C3["Call Domain Service
调用领域服务"] end subgraph "4. 领域层预处理(`domain/workflow`)" D1["WorkflowService.SyncExecute
领域服务执行入口"] D2["Get Workflow Entity
查询workflow_meta + version"] D3["Parse Canvas JSON
转换为 WorkflowSchema"] D4["WorkflowSchema
节点: entry/llm/exit
连接关系/类型定义"] end subgraph "5. Compose 构建阶段(`domain/workflow/internal/compose`)" E1["compose.NewWorkflow
初始化可执行图"] E2["AddNode: Entry
输出 user_input"] E3["AddNode: LLM
模型+Prompt配置"] E4["AddNode: Exit
输入 output"] E5["Compile to Runner
编译为 Eino Runner"] end subgraph "6. Execute 运行阶段" F1["Runner.Invoke
输入user_input=Hello"] F2["Entry Node Execute
发射 user_input"] F3["LLM Node Execute
构建Prompt→调用LLM→拿到回复"] F4["Exit Node Execute
收集最终输出"] F5["Return Final Output
形成完整响应体"] end subgraph "7. 事件发射(`domain/workflow/internal/execute`)" G1["WorkflowStart Event
工作流开始"] G2["NodeStart: entry"] G3["NodeSuccess: entry"] G4["NodeStart: llm_1"] G5["NodeSuccess: llm_1
附带 token 使用"] G6["NodeStart: exit"] G7["NodeSuccess: exit"] G8["WorkflowSuccess Event
总耗时 / 总 token"] end subgraph "8. HTTP 响应输出" H1["Build WorkflowExecution Entity
构建执行记录 + token info"] H2["Return HTTP Response
返回 code/data"] end A1 --> B1 B1 --> B2 B2 --> B3 B3 --> C1 C1 --> C2 C2 --> C3 C3 --> D1 D1 --> D2 D2 --> D3 D3 --> D4 D4 --> E1 E1 --> E2 E2 --> E3 E3 --> E4 E4 --> E5 E5 --> F1 F1 --> F2 F2 --> F3 F3 --> F4 F4 --> F5 F1 -.->|emit| G1 F2 -.->|emit| G2 F2 -.->|emit| G3 F3 -.->|emit| G4 F3 -.->|emit| G5 F4 -.->|emit| G6 F4 -.->|emit| G7 F5 -.->|emit| G8 F5 --> H1 H1 --> H2 style D4 fill:#e1f5ff style E5 fill:#fff4e1 style F3 fill:#ffe0e0 style G8 fill:#e8f5e9
7.3 详细执行步骤
Step 1: HTTP 请求到达
http
POST /v1/workflow/run HTTP/1.1
Content-Type: application/json
Authorization: Bearer <API_KEY>
{
"workflow_id": "123",
"parameters": "{\"user_input\": \"Hello, AI!\"}"
}
说明:
- OpenAPI 使用
/v1/workflow/run路径 - 需要 API Key 认证
parameters是 JSON 字符串格式
Step 2: API Handler 处理
go
// backend/api/handler/coze/workflow_service.go
// OpenAPIRunFlow .
// @router /v1/workflow/run [POST]
func OpenAPIRunFlow(ctx context.Context, c *app.RequestContext) {
var err error
// 预处理请求体
if err = preprocessWorkflowRequestBody(ctx, c); err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
// 绑定和验证参数
var req workflow.OpenAPIRunFlowRequest
err = c.BindAndValidate(&req)
if err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
// 调用 Application Service
resp, err := appworkflow.SVC.OpenAPIRun(ctx, &req)
if err != nil {
// 处理 Workflow 特定错误
var se vo.WorkflowError
if errors.As(err, &se) {
resp = new(workflow.OpenAPIRunFlowResponse)
resp.Code = int64(se.OpenAPICode())
resp.Msg = ptr.Of(se.Msg())
debugURL := se.DebugURL()
if debugURL != "" {
resp.DebugUrl = ptr.Of(debugURL)
}
c.JSON(consts.StatusOK, resp)
return
}
internalServerErrorResponse(ctx, c, err)
return
}
c.JSON(consts.StatusOK, resp)
}
关键点:
- 使用
OpenAPIRunFlowRequest - 包含完整的错误处理逻辑
- 支持返回 debug URL
Step 3: Application Service 编排
go
// backend/application/workflow/workflow.go
func (w *ApplicationService) OpenAPIRun(ctx context.Context, req *workflow.OpenAPIRunFlowRequest) (
_ *workflow.OpenAPIRunFlowResponse, err error,
) {
// 1. 获取 API 认证信息
apiKeyInfo := ctxutil.GetApiAuthFromCtx(ctx)
userID := apiKeyInfo.UserID
// 2. 解析参数
parameters := make(map[string]any)
if req.Parameters != nil {
err := sonic.UnmarshalString(*req.Parameters, ¶meters)
if err != nil {
return nil, vo.WrapError(errno.ErrInvalidParameter, err)
}
}
// 3. 获取 Workflow 元数据
meta, err := GetWorkflowDomainSVC().Get(ctx, &vo.GetPolicy{
ID: mustParseInt64(req.GetWorkflowID()),
MetaOnly: true,
})
if err != nil {
return nil, err
}
// 4. 检查是否已发布
if meta.LatestPublishedVersion == nil {
return nil, vo.NewError(errno.ErrWorkflowNotPublished)
}
// 5. 权限检查
if err = checkUserSpace(ctx, userID, meta.SpaceID); err != nil {
return nil, err
}
// 6. 构建执行配置
exeCfg := workflowModel.ExecuteConfig{
ID: meta.ID,
From: workflowModel.FromSpecificVersion,
Version: *meta.LatestPublishedVersion,
Operator: userID,
Mode: workflowModel.ExecuteModeRelease,
ConnectorID: apiKeyInfo.ConnectorID,
ConnectorUID: strconv.FormatInt(userID, 10),
InputFailFast: true,
BizType: workflowModel.BizTypeWorkflow,
}
// 7. 判断同步/异步执行
if req.GetIsAsync() {
// 异步执行
exeCfg.SyncPattern = workflowModel.SyncPatternAsync
exeCfg.TaskType = workflowModel.TaskTypeBackground
exeID, err := GetWorkflowDomainSVC().AsyncExecute(ctx, exeCfg, parameters)
if err != nil {
return nil, err
}
return &workflow.OpenAPIRunFlowResponse{
ExecuteID: ptr.Of(strconv.FormatInt(exeID, 10)),
DebugUrl: ptr.Of(debugutil.GetWorkflowDebugURL(ctx, meta.ID, meta.SpaceID, exeID)),
}, nil
}
// 8. 同步执行
exeCfg.SyncPattern = workflowModel.SyncPatternSync
exeCfg.TaskType = workflowModel.TaskTypeForeground
wfExe, tPlan, err := GetWorkflowDomainSVC().SyncExecute(ctx, exeCfg, parameters)
if err != nil {
return nil, err
}
// 9. 构建返回结果
return buildOpenAPIRunResponse(ctx, wfExe, tPlan, meta)
}
关键流程:
- ✅ API 认证和用户识别
- ✅ 参数解析(JSON 字符串 → map)
- ✅ Workflow 元数据获取
- ✅ 发布状态检查
- ✅ 权限校验
- ✅ 执行配置构建
- ✅ 支持同步/异步模式
- ✅ 调用 Domain Service 执行
Step 4: Domain Service 执行
go
// backend/domain/workflow/service/executable_impl.go
func (i *impl) SyncExecute(ctx context.Context, config workflowModel.ExecuteConfig, input map[string]any) (*entity.WorkflowExecution, vo.TerminatePlan, error) {
// 1. 获取 Workflow 实体
wfEntity, _ := i.Get(ctx, &vo.GetPolicy{
ID: config.ID,
QType: config.From,
})
// 2. 解析 Canvas 为 Schema
canvas := &vo.Canvas{}
sonic.UnmarshalString(wfEntity.Canvas, canvas)
workflowSC, _ := adaptor.CanvasToWorkflowSchema(ctx, canvas)
// 3. 创建 Workflow
wf, _ := compose.NewWorkflow(ctx, workflowSC,
compose.WithIDAsName(wfEntity.ID))
// 4. 准备执行上下文
cancelCtx, executeID, opts, lastEventChan, _ := compose.NewWorkflowRunner(
wfEntity.GetBasic(),
workflowSC,
config,
compose.WithInput(inputStr),
).Prepare(ctx)
// 5. 同步执行
startTime := time.Now()
out, err := wf.SyncRun(cancelCtx, input, opts...)
// 6. 等待最后一个事件
lastEvent := <-lastEventChan
// 7. 构建返回结果
return &entity.WorkflowExecution{
ID: executeID,
WorkflowID: wfEntity.ID,
Status: entity.WorkflowSuccess,
Output: ptr.Of(outputStr),
TokenInfo: &entity.TokenUsage{
InputTokens: lastEvent.GetInputTokens(),
OutputTokens: lastEvent.GetOutputTokens(),
},
Duration: lastEvent.Duration,
}, wf.TerminatePlan(), nil
}
Step 5: Compose Layer 构建
go
// backend/domain/workflow/internal/compose/workflow.go (83-158行)
func NewWorkflow(ctx context.Context, sc *schema.WorkflowSchema, opts ...WorkflowOption) (*Workflow, error) {
// 1. 初始化 WorkflowSchema
sc.Init()
// 2. 创建 Workflow 实例
wf := &Workflow{
workflow: compose.NewWorkflow[map[string]any, map[string]any](
compose.WithGenLocalState(GenState()), // 状态生成器
),
hierarchy: sc.Hierarchy,
connections: sc.Connections,
schema: sc,
}
// 3. 设置执行模式
wf.streamRun = sc.RequireStreaming() // 是否需要流式输出
wf.requireCheckpoint = sc.RequireCheckpoint() // 是否需要 Checkpoint
// 4. 处理选项参数
wfOpts := &workflowOptions{}
for _, opt := range opts {
opt(wfOpts)
}
// 5. 检查节点数量限制
if wfOpts.maxNodeCount > 0 {
if sc.NodeCount() > int32(wfOpts.maxNodeCount) {
return nil, fmt.Errorf("node count %d exceeds the limit: %d",
sc.NodeCount(), wfOpts.maxNodeCount)
}
}
if wfOpts.parentRequireCheckpoint {
wf.requireCheckpoint = true
}
// 6. 设置 input/output 类型定义
wf.input = sc.GetNode(entity.EntryNodeKey).OutputTypes
wf.output = sc.GetNode(entity.ExitNodeKey).InputTypes
// 7. 【第一轮】添加复合节点(CompositeNodes)
// 复合节点包含内部子工作流,需要先构建
compositeNodes := sc.GetCompositeNodes()
processedNodeKey := make(map[vo.NodeKey]struct{})
for i := range compositeNodes {
cNode := compositeNodes[i]
if err := wf.AddCompositeNode(ctx, cNode); err != nil {
return nil, err
}
// 标记已处理的节点(父节点和子节点)
processedNodeKey[cNode.Parent.Key] = struct{}{}
for _, child := range cNode.Children {
processedNodeKey[child.Key] = struct{}{}
}
}
// 8. 【第二轮】添加普通节点(Entry, LLM, Exit 等)
// 跳过已经作为复合节点处理的节点
for _, ns := range sc.Nodes {
if _, ok := processedNodeKey[ns.Key]; !ok {
if err := wf.AddNode(ctx, ns); err != nil {
return nil, err
}
}
// 保存 Exit 节点的终止计划
if ns.Type == entity.NodeTypeExit {
wf.terminatePlan = ns.Configs.(*exit.Config).TerminatePlan
}
}
// 9. 编译成可执行的 Runner
var compileOpts []compose.GraphCompileOption
if wf.requireCheckpoint {
compileOpts = append(compileOpts,
compose.WithCheckPointStore(workflow2.GetRepository()))
}
if wfOpts.idAsName {
compileOpts = append(compileOpts,
compose.WithGraphName(strconv.FormatInt(wfOpts.wfID, 10)))
}
r, err := wf.Compile(ctx, compileOpts...)
if err != nil {
return nil, err
}
wf.Runner = r
return wf, nil
}
节点添加流程(AddNode):
go
// backend/domain/workflow/internal/compose/workflow.go (216-280行)
func (w *Workflow) addNodeInternal(ctx context.Context, ns *schema.NodeSchema,
inner *innerWorkflowInfo) (map[vo.NodeKey][]*compose.FieldMapping, error) {
key := ns.Key
// 1. 解析节点依赖关系
deps, err := w.resolveDependencies(key, ns.InputSources)
if err != nil {
return nil, err
}
// 2. 如果是复合节点,合并内部工作流的依赖
if inner != nil {
if err = deps.merge(inner.carryOvers); err != nil {
return nil, err
}
}
var innerWorkflow compose.Runnable[map[string]any, map[string]any]
if inner != nil {
innerWorkflow = inner.inner
}
// 3. 【核心】调用 node_builder.New() 创建节点实例
ins, err := New(ctx, ns, innerWorkflow, w.schema, deps, w.requireCheckpoint)
if err != nil {
return nil, err
}
// 4. 准备节点选项
var opts []compose.GraphAddNodeOpt
opts = append(opts, compose.WithNodeName(string(ns.Key)))
// 5. 添加前置处理器(preHandler)
preHandler := statePreHandler(ns, w.streamRun)
if preHandler != nil {
opts = append(opts, preHandler)
}
// 6. 添加后置处理器(postHandler)
postHandler := statePostHandler(ns, w.streamRun)
if postHandler != nil {
opts = append(opts, postHandler)
}
// 7. 将节点添加到 Workflow 图中
var wNode *compose.WorkflowNode
if ins.Lambda != nil {
wNode = w.AddLambdaNode(string(key), ins.Lambda, opts...)
} else {
return nil, fmt.Errorf("node instance has no Lambda: %s", key)
}
// 8. 处理数组钻取(array drill down)
if err = deps.arrayDrillDown(w.schema.GetAllNodes()); err != nil {
return nil, err
}
// 9. 添加节点的输入连接(建立节点之间的数据流)
for fromNodeKey := range deps.inputsFull {
wNode.AddInput(string(fromNodeKey))
}
for fromNodeKey, fieldMappings := range deps.inputs {
wNode.AddInput(string(fromNodeKey), fieldMappings...)
}
for fromNodeKey := range deps.inputsNoDirectDependencyFull {
wNode.AddInputWithOptions(string(fromNodeKey), nil,
compose.WithNoDirectDependency())
}
for fromNodeKey, fieldMappings := range deps.inputsNoDirectDependency {
wNode.AddInputWithOptions(string(fromNodeKey), fieldMappings,
compose.WithNoDirectDependency())
}
// ... 返回 carryOver 依赖
}
节点实例化(node_builder.New):
go
// backend/domain/workflow/internal/compose/node_builder.go (40-100行)
func New(ctx context.Context, s *schema.NodeSchema,
inner compose.Runnable[map[string]any, map[string]any], // 内部工作流(复合节点用)
sc *schema.WorkflowSchema, // 所属工作流 Schema
deps *dependencyInfo, // 依赖信息
requireCheckpoint bool,
) (_ *Node, err error) {
defer func() {
if panicErr := recover(); panicErr != nil {
err = safego.NewPanicErr(panicErr, debug.Stack())
}
if err != nil {
err = vo.WrapIfNeeded(errno.ErrCreateNodeFail, err,
errorx.KV("node_name", s.Name),
errorx.KV("cause", err.Error()))
}
}()
// 1. 获取完整的数据源信息(如果节点需要)
var fullSources map[string]*schema.SourceInfo
if m := entity.NodeMetaByNodeType(s.Type); m != nil && m.InputSourceAware {
if fullSources, err = GetFullSources(s, sc, deps); err != nil {
return nil, err
}
s.FullSources = fullSources
}
// 2. 【策略模式】检查 NodeSchema.Configs 是否实现了 NodeBuilder 接口
nb, ok := s.Configs.(schema.NodeBuilder)
if ok {
// 2.1 准备构建选项
opts := []schema.BuildOption{
schema.WithWorkflowSchema(sc),
schema.WithInnerWorkflow(inner),
}
// 2.2 调用具体节点的 Build() 方法
// 例如: LLMConfig.Build(), PluginConfig.Build() 等
n, err := nb.Build(ctx, s, opts...)
if err != nil {
return nil, err
}
// 2.3 将节点包装成 Lambda(Eino 统一接口)
return toNode(s, n), nil
}
// 3. 特殊节点类型处理
switch s.Type {
case entity.NodeTypeLambda:
// 直接使用预定义的 Lambda
if s.Lambda == nil {
return nil, fmt.Errorf("lambda is not defined for NodeTypeLambda")
}
return &Node{Lambda: s.Lambda}, nil
case entity.NodeTypeSubWorkflow:
// 构建子工作流
subWorkflow, err := buildSubWorkflow(ctx, s, requireCheckpoint)
if err != nil {
return nil, err
}
return toNode(s, subWorkflow), nil
default:
panic(fmt.Sprintf("node schema's Configs does not implement NodeBuilder. type: %v", s.Type))
}
}
LLM 节点构建示例(Build 方法):
go
// backend/domain/workflow/internal/nodes/llm/llm.go
// LLMConfig 实现了 schema.NodeBuilder 接口
func (c *LLMConfig) Build(ctx context.Context, ns *schema.NodeSchema,
opts ...schema.BuildOption) (schema.InvokableNode, error) {
// 1. 构建 ChatModel
chatModel, err := modelbuilder.NewChatModel(ctx, c.Model, c.ModelConfig)
if err != nil {
return nil, err
}
// 2. 构建 Prompt Template
// 将配置中的 Messages 转换为可执行的模板
// 例如: "{{user_input}}" -> 可替换的模板变量
promptTpl := buildPromptTemplate(c.Messages)
// 3. 创建 LLM Chain
chain := compose.NewChain[map[string]any, *schema.Message]()
chain.AppendPrompt(promptTpl)
chain.AppendChatModel(chatModel)
// 4. 返回 LLMNode(实现了 InvokableNode 接口)
return &LLMNode{
config: c,
chain: chain,
chatModel: chatModel,
}, nil
}
// LLMNode 的 Invoke 方法(运行时执行)
func (n *LLMNode) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
// 1. 执行 Prompt -> ChatModel Chain
// input = {user_input: "Hello, AI!"}
// -> Prompt 渲染 -> ChatModel API 调用
msg, err := n.chain.Invoke(ctx, input)
if err != nil {
return nil, err
}
// 2. 返回输出
// {output: "Hi there! How can I help you?"}
return map[string]any{"output": msg.Content}, nil
}
关键流程总结:
Step 6: Runtime 执行
go
// > - `backend/domain/workflow/internal/compose/workflow.go` → `Workflow.SyncRun()` / `Workflow.Runner.Invoke()`
// > - `github.com/cloudwego/eino/compose/runnable.go` → `runner.Run()`(节点调度与回调触发)
// Eino Framework 内部执行流程
func (r *Runner) Invoke(ctx context.Context, input map[string]any, opts ...Option) (map[string]any, error) {
// 1. Emit WorkflowStart Event
callbacks.OnStart(ctx, &callbacks.RunInfo{
Name: "workflow_123",
Type: "workflow",
})
state := map[string]any{}
// 2. 按拓扑顺序执行节点
for _, node := range r.sortedNodes {
// 2.1 Entry Node
if node.Key == "entry" {
callbacks.OnStart(ctx, &callbacks.RunInfo{Name: "entry"})
state["user_input"] = input["user_input"] // "Hello, AI!"
callbacks.OnEnd(ctx, &callbacks.RunInfo{Name: "entry"})
}
// 2.2 LLM Node
if node.Key == "llm_1" {
callbacks.OnStart(ctx, &callbacks.RunInfo{Name: "llm_1"})
// 获取输入
nodeInput := map[string]any{
"user_input": state["user_input"],
}
// 执行 LLM Lambda
nodeOutput, _ := node.Lambda(ctx, nodeInput)
// nodeOutput = {output: "Hi there! How can I help you?"}
state["llm_1_output"] = nodeOutput["output"]
callbacks.OnEnd(ctx, &callbacks.RunInfo{
Name: "llm_1",
Extra: map[string]any{
"input_tokens": 10,
"output_tokens": 8,
},
})
}
// 2.3 Exit Node
if node.Key == "exit" {
callbacks.OnStart(ctx, &callbacks.RunInfo{Name: "exit"})
output := map[string]any{
"output": state["llm_1_output"],
}
callbacks.OnEnd(ctx, &callbacks.RunInfo{Name: "exit"})
callbacks.OnEnd(ctx, &callbacks.RunInfo{
Name: "workflow_123",
Type: "workflow",
})
return output, nil
}
}
}
Step 7: 事件流
事件处理代码:
go
// backend/domain/workflow/internal/execute/event_handle.go
func handleEvent(ctx context.Context, event *Event, repo workflow.Repository,
sw *schema.StreamWriter[*entity.Message]) (signal terminateSignal, err error) {
switch event.Type {
case WorkflowStart:
// ① WorkflowStart:创建/更新 workflow_execution,标记为 Running,
// 并将"正在运行"状态推送给前端(如果是流式执行)。
case NodeStart:
// ② NodeStart:写入 node_execution(包含输入、节点类型等),用于后续追踪。
nodeExec := &entity.NodeExecution{
ID: event.NodeExecuteID,
ExecuteID: event.RootCtx.RootExecuteID,
NodeID: string(event.NodeKey),
NodeName: event.NodeName,
NodeType: event.NodeType,
Status: entity.NodeRunning,
Input: ptr.Of(mustMarshalToString(event.Input)),
}
if err = repo.CreateNodeExecution(ctx, nodeExec); err != nil {
return noTerminate, fmt.Errorf("failed to create node execution: %v", err)
}
case NodeEnd, NodeEndStreaming:
// ③ NodeEnd:记录节点的执行结果、耗时、token 消耗、输出内容等。
nodeExec := &entity.NodeExecution{
ID: event.NodeExecuteID,
Status: entity.NodeSuccess,
Duration: event.Duration,
TokenInfo: &entity.TokenUsage{
InputTokens: event.GetInputTokens(),
OutputTokens: event.GetOutputTokens(),
},
Extra: event.extra,
}
if event.outputStr != nil {
nodeExec.Output = event.outputStr
} else {
nodeExec.Output = ptr.Of(mustMarshalToString(event.Output))
nodeExec.RawOutput = event.RawOutput
}
if err = repo.UpdateNodeExecution(ctx, nodeExec); err != nil {
return noTerminate, fmt.Errorf("failed to save node execution: %v", err)
}
if event.NodeType == entity.NodeTypeExit && event.SubWorkflowCtx == nil {
// Exit 节点完成,标记"最后一个节点已结束",等待 WorkflowSuccess 事件最终确认。
return lastNodeDone, nil
}
case WorkflowSuccess:
// ④ WorkflowSuccess:根工作流成功完成,等待 Exit 节点处理完后统一收尾。
return workflowSuccess, nil
case WorkflowFailed:
// ⑤ WorkflowFailed:更新 workflow_execution 状态为失败,并将错误信息写入 node_execution。
default:
panic("unimplemented event type: " + event.Type)
}
return noTerminate, nil
}
func HandleExecuteEvent(ctx context.Context, wfExeID int64, eventChan <-chan *Event,
cancelFn, timeoutFn context.CancelFunc, repo workflow.Repository,
sw *schema.StreamWriter[*entity.Message], exeCfg workflowModel.ExecuteConfig) (event *Event) {
handler := func(event *Event) *Event {
signal, err := handleEvent(ctx, event, repo, sw)
if err != nil {
logs.CtxErrorf(ctx, "failed to handle event: %v", err)
}
switch signal {
case workflowSuccess:
// ⑥ WorkflowSuccess:如果 Exit 节点也已完成,就调用 setRootWorkflowSuccess
// 写入 workflow_execution(最终输出、token、耗时等)。
if lastNodeIsDone || exeCfg.Mode == workflowModel.ExecuteModeNodeDebug {
_ = setRootWorkflowSuccess(ctx, event, repo, sw)
return event
}
case lastNodeDone:
// ⑦ Exit 节点完成:若之前已经收到 workflowSuccess,就立即收尾;否则等待。
lastNodeIsDone = true
if wfSuccessEvent != nil {
_ = setRootWorkflowSuccess(ctx, wfSuccessEvent, repo, sw)
return wfSuccessEvent
}
case workflowAbort:
// ⑧ workflowAbort:出现取消/失败,直接返回最后一条事件。
return event
}
return nil
}
...
}
Step 8: 返回结果
go
//`backend/application/workflow/workflow.go` → `ApplicationService.OpenAPIRun`
// 异步执行:只返回 execute_id + debug_url
if req.GetIsAsync() {
exeCfg.SyncPattern = workflowModel.SyncPatternAsync
exeCfg.TaskType = workflowModel.TaskTypeBackground
exeID, err := GetWorkflowDomainSVC().AsyncExecute(ctx, exeCfg, parameters)
if err != nil {
return nil, err
}
return &workflow.OpenAPIRunFlowResponse{
ExecuteID: ptr.Of(strconv.FormatInt(exeID, 10)),
DebugUrl: ptr.Of(debugutil.GetWorkflowDebugURL(ctx, meta.ID, meta.SpaceID, exeID)),
}, nil
}
// 同步执行:返回执行结果 + token + debug_url
exeCfg.SyncPattern = workflowModel.SyncPatternSync
exeCfg.TaskType = workflowModel.TaskTypeForeground
wfExe, tPlan, err := GetWorkflowDomainSVC().SyncExecute(ctx, exeCfg, parameters)
...
return &workflow.OpenAPIRunFlowResponse{
Data: data, // 输出内容(answer 或变量)
ExecuteID: ptr.Of(strconv.FormatInt(wfExe.ID, 10)), // 执行 ID
DebugUrl: ptr.Of(debugutil.GetWorkflowDebugURL(ctx, meta.ID, wfExe.SpaceID, wfExe.ID)),
Token: ptr.Of(wfExe.TokenInfo.InputTokens + wfExe.TokenInfo.OutputTokens),
Cost: ptr.Of("0.00000"),
}, nil
字段含义:
ExecuteID:这次执行的唯一 ID,便于查询历史或调试。Data:工作流输出(当 terminate plan 是ReturnVariables时直接返回变量,否则封装成标准 answer 结构)。Token:累计 token 消耗(输入 + 输出)。DebugUrl:一键跳转调试页,便于复现。Cost:预留的计费字段,目前固定0.00000。- 异步场景不直接返回数据,只提供
execute_id+debug_url,由客户端轮询结果。
8. 总结与展望
在梳理 Coze Studio 后端的过程中可以看到,它已经具备一套相对完善的 DDD 分层和跨域治理能力:
- API 层负责契约与安全
- Application 层掌控用例编排
- Domain 层沉淀业务模型
- Crossdomain / Infra 撑起跨上下文协作和基础设施抽象。
这种"骨架+血液"的分层让我们在阅读代码时始终能找到明确的职责线索,也解释了为什么工作流、插件、知识库这些复杂模块仍能保持可演化性。
整体来看,Coze Studio 的架构基础已经打好,接下来更像是在"好地基"上不断加砖:稳定性、可观测性、跨团队协作能力都会决定它能否支撑规模化的 AI 工作流生产。只要保持代码与文档同频、持续打磨开发者体验,这个平台就有机会成长为开源 AI Agent 体系里的"参考实现"。
后续会分享更多的 coze-studio 源码中值得我们学习的架构和编码思维,并且后续会跟着分享源码的过程中,对coze studio 进行二次开发,比如开发 mcp 插件(其实已经开发出来了,只是还没来得及以文章的方式来与大家见面,可以先给大家预览下二次开发后的效果在文章开头)