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

238 阅读24分钟

目录


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 支持

20251117225049

1.2 技能点

技术描述
Golang高性能、并发友好、部署简单
HertzCloudWeGo 高性能 HTTP 框架
EinoAI Workflow 运行时引擎,类似于 python 的 LangGraph
DDD复杂业务逻辑的最佳实践
MySQL主数据存储
Redis缓存和 Checkpoint 存储
Elasticsearch搜索和知识库检索

2. 整体架构设计

2.1 宏观架构图

graph TB
    subgraph L7["第7层:客户端层 Client Layer"]
        direction LR
        A["Web Frontend<br/>React + TypeScript<br/>Web前端界面"]
        B["API/SDK Client<br/>API和SDK客户端<br/>第三方系统集成"]
    end
    
    subgraph L6["第6层:接入层 API Layer"]
        direction LR
        C["Hertz HTTP Server<br/>:8888<br/>HTTP服务器,接收请求"]
        D["Middleware Stack<br/>Auth/Log/CORS<br/>中间件栈:认证/日志/跨域"]
        E["Router<br/>API Routes<br/>路由分发,API路由注册"]
    end
    
    subgraph L5["第5层:应用服务层 Application Layer"]
        direction LR
        F1["WorkflowApp<br/>工作流应用服务<br/>编排工作流业务流程"]
        F2["SingleAgentApp<br/>单智能体应用服务<br/>管理Agent创建和执行"]
        F3["PluginApp<br/>插件应用服务<br/>管理插件生命周期"]
        F4["KnowledgeApp<br/>知识库应用服务<br/>管理知识库CRUD"]
        F5["ConversationApp<br/>对话应用服务<br/>管理对话会话"]
    end
    
    subgraph L4["第4层:领域层 Domain Layer"]
        direction LR
        G1["Workflow Domain<br/>工作流领域服务<br/>核心工作流业务逻辑"]
        G2["Agent Domain<br/>智能体领域服务<br/>Agent核心业务逻辑"]
        G3["Plugin Domain<br/>插件领域服务<br/>插件核心业务逻辑"]
        G4["Knowledge Domain<br/>知识库领域服务<br/>知识库核心逻辑"]
    end
    
    subgraph L3["第3层:跨域服务层 Cross Domain Layer"]
        direction LR
        H1["Workflow Cross<br/>工作流跨域服务"]
        H2["Message Cross<br/>消息跨域服务"]
        H3["Database Cross<br/>数据库跨域服务"]
        H4["Plugin Cross<br/>插件跨域服务"]
    end
    
    subgraph L2["第2层:基础设施层 Infrastructure Layer"]
        direction LR
        I1[("MySQL<br/>关系型数据库")]
        I2[("Redis<br/>内存数据库")]
        I3[("Elasticsearch<br/>搜索引擎")]
        I4["OSS<br/>对象存储"]
        I5["EventBus<br/>事件总线"]
        I6["CodeRunner<br/>代码执行器"]
    end
    
    subgraph L1["第1层:外部服务层 External Services"]
        direction LR
        J["LLM Providers<br/>OpenAI/Claude/..."]
        K["Embedding Service<br/>向量嵌入服务"]
    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<br/>领域接口定义] 
    B[Application Layer<br/>依赖接口]
    C[Infrastructure Layer<br/>实现接口]
    D[Cross Domain<br/>依赖接口]
    
    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

api/
├── handler/          # HTTP 请求处理器
│   └── coze/         # 具体业务 Handler
├── middleware/       # 中间件(认证、日志、CORS)
├── model/           # API 数据模型(DTO)
└── router/          # 路由注册

核心职责

  • 🔸 接收 HTTP 请求,解析参数
  • 🔸 调用 Application Service
  • 🔸 返回统一格式的响应
  • 🔸 参数校验和错误处理

关键代码示例main.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

── application/      # 应用层,组合领域对象和基础设施实现
   ├── app/          # 应用服务
   ├── conversation/ # 会话应用服务
   ├── knowledge/    # 知识应用服务
   ├── memory/       # 内存应用服务
   ├── modelmgr/     # 模型管理应用服务
   ├── plugin/       # 插件应用服务
   ├── prompt/       # 提示词应用服务
   ├── search/       # 搜索应用服务
   ├── singleagent/  # 单一代理应用服务
   ├── user/         # 用户应用服务
   └── workflow/     # 工作流应用服务

核心职责

  • 🔸 编排多个 Domain Service 完成业务用例
  • 🔸 事务管理
  • 🔸 权限检查
  • 🔸 事件发布

服务初始化application.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

── domain/           # 领域层,包含核心业务逻辑
   ├── agent/        # 代理领域逻辑
   ├── app/          # 应用领域逻辑
   ├── conversation/ # 会话领域逻辑
   ├── knowledge/    # 知识领域逻辑
   ├── memory/       # 内存领域逻辑
   ├── modelmgr/     # 模型管理领域逻辑
   ├── plugin/       # 插件领域逻辑
   ├── prompt/       # 提示词领域逻辑
   ├── search/       # 搜索领域逻辑
   ├── user/         # 用户领域逻辑
   └── workflow/     # 工作流领域逻辑

领域模型核心

Entity 示例domain/workflow/entity/workflow.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):

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

设计目的

  • 解决循环依赖问题
  • 提供统一的跨领域调用接口
  • 隔离不同领域的实现细节
├── crossdomain/      # 跨领域防腐层
│   ├── contract/     # 跨领域接口定义
│   ├── impl/         # 跨领域接口实现
│   └── workflow/     # 工作流跨领域实现

#示例
crossdomain/
├── workflow/
│   ├── contract.go       # 接口定义
│   ├── impl/            # 接口实现(适配器)
│   └── model/           # 跨域数据模型
├── knowledge/
├── plugin/
└── ...


使用模式

注意:以下代码为伪代码示例,用于说明跨域调用的概念模式。实际代码中的函数名和方法名可能不同。

// 伪代码示例:在 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):

// 真实代码:在 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

infra/
├── orm/              # ORM 封装
├── rdb/              # 关系数据库
├── cache/            # Redis 缓存
├── es/               # Elasticsearch
├── storage/          # 对象存储(OSS)
├── eventbus/         # 事件总线
├── embedding/        # 向量嵌入
├── coderunner/       # 代码执行沙箱
├── checkpoint/       # 检查点存储
└── ...

核心职责

  • 🔸 封装技术实现细节(数据库、缓存、消息队列等)
  • 🔸 提供统一的接口抽象,不依赖具体实现
  • 🔸 实现依赖倒置,领域层只依赖接口

代码示例

1. Elasticsearch 使用示例

application->domain->infra

//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):

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

// 使用上下文缓存(基于 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):

// 事件生产者接口
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: 状态持久化,支持中断恢复

使用示例

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 完整目录树

├── 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 目录职责矩阵

目录职责依赖方向
apiHTTP 请求处理→ 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 的结构和数据流

// 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

//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
HTTPRequesterHTTP 请求nodes/httprequester

节点构建机制

//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):

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

//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

[开始][大模型][结束]

说明:本节以 OpenAPI 方式执行 Workflow 为例进行分析。Coze Studio 提供两套 API:

API 类型路径前缀用途认证方式
OpenAPI/v1/对外开放的 API,供第三方调用API Key(Personal Access Token)
内部 API/api/Web 前端使用的内部 APISession 认证

本节重点分析 OpenAPI 的执行流程,因为它更完整地展示了认证、权限校验、版本控制等机制。

Canvas 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<br/>workflow_id:123<br/>input:user_input=Hello<br/>外部调用入口"]
    end
    
    subgraph "2. API 层(Hertz Handler)"
        B1["WorkflowExecuteHandler<br/>OpenAPI入口处理器"]
        B2["Parse Request Body<br/>解析请求体"]
        B3["Validate Parameters<br/>参数校验"]
    end
    
    subgraph "3. 应用层(`application/workflow`)"
        C1["WorkflowApp.Execute/OpenAPIRun<br/>应用服务编排"]
        C2["Build ExecuteConfig<br/>构建执行配置"]
        C3["Call Domain Service<br/>调用领域服务"]
    end
    
    subgraph "4. 领域层预处理(`domain/workflow`)"
        D1["WorkflowService.SyncExecute<br/>领域服务执行入口"]
        D2["Get Workflow Entity<br/>查询workflow_meta + version"]
        D3["Parse Canvas JSON<br/>转换为 WorkflowSchema"]
        D4["WorkflowSchema<br/>节点: entry/llm/exit<br/>连接关系/类型定义"]
    end
    
    subgraph "5. Compose 构建阶段(`domain/workflow/internal/compose`)"
        E1["compose.NewWorkflow<br/>初始化可执行图"]
        E2["AddNode: Entry<br/>输出 user_input"]
        E3["AddNode: LLM<br/>模型+Prompt配置"]
        E4["AddNode: Exit<br/>输入 output"]
        E5["Compile to Runner<br/>编译为 Eino Runner"]
    end
    
    subgraph "6. Execute 运行阶段"
        F1["Runner.Invoke<br/>输入user_input=Hello"]
        F2["Entry Node Execute<br/>发射 user_input"]
        F3["LLM Node Execute<br/>构建Prompt→调用LLM→拿到回复"]
        F4["Exit Node Execute<br/>收集最终输出"]
        F5["Return Final Output<br/>形成完整响应体"]
    end
    
    subgraph "7. 事件发射(`domain/workflow/internal/execute`)"
        G1["WorkflowStart Event<br/>工作流开始"]
        G2["NodeStart: entry"]
        G3["NodeSuccess: entry"]
        G4["NodeStart: llm_1"]
        G5["NodeSuccess: llm_1<br/>附带 token 使用"]
        G6["NodeStart: exit"]
        G7["NodeSuccess: exit"]
        G8["WorkflowSuccess Event<br/>总耗时 / 总 token"]
    end
    
    subgraph "8. HTTP 响应输出"
        H1["Build WorkflowExecution Entity<br/>构建执行记录 + token info"]
        H2["Return HTTP Response<br/>返回 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 请求到达

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 处理

// 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 编排

// 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 执行

// 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 构建

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

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

// 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 方法)

// 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 执行

// > - `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

事件处理代码

// 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: 返回结果

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