Coze Studio 源码分析(一)后端架构及数据流分析

目录

  • [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 宏观架构图

graph TB subgraph L7["第7层:客户端层 Client Layer"] direction LR A["Web Frontend
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 依赖倒置原则

graph BT A[Domain Layer
领域接口定义] 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()
}

中间件执行顺序

graph LR A[HTTP Request] --> B[ContextCache] B --> C[RequestInspector] C --> D[SetHost] D --> E[SetLogID] E --> F[CORS] F --> G[AccessLog] G --> H[OpenapiAuth] H --> I[SessionAuth] I --> J[I18n] J --> K[Handler] K --> L[Response] style A fill:#e3f2fd style K fill:#c8e6c9 style L fill:#fff9c4

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
}

服务依赖简要关系图

graph TD subgraph "Infrastructure 基础设施" I1[DB/Redis/OSS/ES] end subgraph "Basic Services 基础服务" B1[ModelMgr] B2[Connector] B3[User] B4[Upload] end subgraph "Primary Services 主要服务" P1[Plugin] P2[Memory] P3[Knowledge] P4[Workflow] end subgraph "Complex Services 复杂服务" C1[SingleAgent] C2[App] C3[Conversation] end I1 --> B1 I1 --> B2 I1 --> B3 I1 --> B4 B1 --> P1 B2 --> P2 B3 --> P3 B4 --> P4 P1 --> C1 P2 --> C1 P3 --> C2 P4 --> C2 C1 --> C3 C2 --> C3 style I1 fill:#fff9c4 style P1 fill:#c8e6c9 style P2 fill:#c8e6c9 style P3 fill:#c8e6c9 style P4 fill:#c8e6c9 style C1 fill:#f8bbd0 style C2 fill:#f8bbd0 style C3 fill:#f8bbd0

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 架构

graph TB subgraph "Workflow Domain" A[Service Interface] B[Service Implementation] C[Entity/VO] subgraph "Internal 内部实现" D[Schema - 数据流定义] E[Compose - 编排引擎] F[Nodes - 节点实现] G[Execute - 执行引擎] H[Repository - 仓储] end end A --> B B --> D B --> E B --> H D --> E E --> F E --> G F --> G style D fill:#e1f5ff style E fill:#fff4e1 style F fill:#e8f5e9 style G fill:#f3e5f5

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 生命周期

sequenceDiagram participant Client participant Handler participant AppService participant DomainService participant Compose participant Eino participant Node Client->>Handler: POST /api/workflow/execute Handler->>AppService: Execute(config, input) AppService->>DomainService: SyncExecute(config, input) DomainService->>DomainService: Get Workflow Entity DomainService->>DomainService: Parse Canvas to Schema DomainService->>Compose: NewWorkflow(schema) Compose->>Compose: AddNodes Compose->>Compose: Compile Compose-->>DomainService: Return Runner DomainService->>Eino: Runner.Invoke(ctx, input) loop For each node Eino->>Node: Execute(input) Node-->>Eino: output Eino->>Eino: Emit Event end Eino-->>DomainService: Final Output DomainService-->>AppService: WorkflowExecution AppService-->>Handler: Result Handler-->>Client: HTTP Response

6.2 Plugin 模块

6.2.1 Plugin 架构

graph TB subgraph "Plugin Domain" A[Plugin Service] B[Plugin Entity] C[Plugin Repository] subgraph "Plugin Types" D1[HTTP Plugin] D2[Code Plugin] D3[System Plugin] end A --> B A --> C A --> D1 A --> D2 A --> D3 end subgraph "Workflow Integration" E[Plugin Node] F[Tool Wrapper] end A --> E E --> F style A fill:#e1f5ff style E fill:#fff4e1

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 流程

sequenceDiagram participant LLMNode participant KnowledgeSVC participant Embedding participant VectorDB participant Reranker LLMNode->>KnowledgeSVC: Query(question) KnowledgeSVC->>Embedding: Embed(question) Embedding-->>KnowledgeSVC: vector KnowledgeSVC->>VectorDB: Search(vector, topK) VectorDB-->>KnowledgeSVC: candidates KnowledgeSVC->>Reranker: Rerank(candidates, question) Reranker-->>KnowledgeSVC: top results KnowledgeSVC-->>LLMNode: context + references LLMNode->>LLMNode: Build Prompt with context

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 完整数据流图

graph TB subgraph "1. HTTP 请求入口(`backend/api/handler/coze/workflow_service.go`)" A1["POST /v1/workflow/run
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, &parameters)
        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)
}

关键流程

  1. ✅ API 认证和用户识别
  2. ✅ 参数解析(JSON 字符串 → map)
  3. ✅ Workflow 元数据获取
  4. ✅ 发布状态检查
  5. ✅ 权限校验
  6. ✅ 执行配置构建
  7. ✅ 支持同步/异步模式
  8. ✅ 调用 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
}

关键流程总结

graph TB A[NewWorkflow] --> B[sc.Init 初始化] B --> C[创建 Workflow 实例] C --> D[设置 streamRun/requireCheckpoint] D --> E[第一轮: AddCompositeNode] E --> F[第二轮: AddNode 普通节点] F --> G[addNodeInternal] G --> H[resolveDependencies 解析依赖] H --> I["node_builder.New() 创建实例"] I --> J{Configs 实现 NodeBuilder?} J -->|是| K[调用 Build 方法] J -->|否| L[特殊类型处理] K --> M[toNode 包装成 Lambda] L --> M M --> N[AddLambdaNode 添加到图] N --> O[AddInput 连接数据流] F --> P[Compile 编译] P --> Q[生成 Runner] style I fill:#ffe0e0 style K fill:#e1f5ff style P fill:#e8f5e9

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: 事件流

sequenceDiagram participant Runner participant Callback participant EventChan participant Handler Runner->>Callback: OnStart(workflow_123) Callback->>EventChan: WorkflowStart Runner->>Callback: OnStart(entry) Callback->>EventChan: NodeStart(entry) Runner->>Callback: OnEnd(entry) Callback->>EventChan: NodeSuccess(entry) Runner->>Callback: OnStart(llm_1) Callback->>EventChan: NodeStart(llm_1) Note over Runner: Call LLM API... Runner->>Callback: OnEnd(llm_1, tokens) Callback->>EventChan: NodeSuccess(llm_1) Runner->>Callback: OnStart(exit) Callback->>EventChan: NodeStart(exit) Runner->>Callback: OnEnd(exit) Callback->>EventChan: NodeSuccess(exit) Runner->>Callback: OnEnd(workflow_123) Callback->>EventChan: WorkflowSuccess EventChan->>Handler: lastEvent

事件处理代码

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 插件(其实已经开发出来了,只是还没来得及以文章的方式来与大家见面,可以先给大家预览下二次开发后的效果在文章开头)

相关推荐
袁庭新3 天前
2025年10月总结
人工智能·aigc·coze
后端小肥肠4 天前
别再找提示词了!n8n+Coze+Sora2:扔个链接,AI自动反推,爆款视频直存本地!
aigc·agent·coze
HYI6 天前
提示词工程化实践:我如何构建可复用的Suno工作流系统
aigc·coze
大模型真好玩7 天前
低代码Agent开发框架使用指南(七)—Coze 数据库详解
人工智能·agent·coze
后端小肥肠8 天前
Coze+n8n实战:公众号文章从仿写到草稿箱,2分钟全搞定,你只需提交链接!
aigc·agent·coze
重铸码农荣光16 天前
从天气查询到低代码 AI 应用:Coze 平台让人人都能开发智能体
aigc·coze
大模型真好玩18 天前
低代码Agent开发框架使用指南(六)—Coze 变量与长期记忆
人工智能·coze·mcp
小龙报20 天前
《赋能AI解锁Coze智能体搭建核心技能(1)--- 初识coze》
人工智能·语言模型·数据分析·交互·文心一言·机器翻译·coze
大模型真好玩25 天前
低代码Agent开发框架使用指南(五)—Coze消息卡片详解
人工智能·coze·mcp