coze-loop · 模型、采集与存储
本页覆盖:Trace 模型、采集入口、鉴权限流、存储与保留。每条事实链到固定提交(3a6a2bf0)。回到 coze-loop 总览。
一、Trace / Span 模型、类型、状态、树、token 与成本
- IDL 里的
Trace只有trace_id、tokens、size三个可选字段(idl/thrift/coze/loop/observability/domain/trace.thrift L3-L11)。 - 存储和查询的主对象是
Span,TraceID只是 span 上的字段(backend/modules/observability/domain/trace/entity/loop_span/span.go L156-L162)。 - 没有独立的 Trace 表。span 存在
observability_spans(release/deployment/docker-compose/bootstrap/clickhouse-init/init-sql/observability_spans.sql L1-L36)。 - 对外
OutputSpan带trace_id、span_id、parent_id、span_name、span_type、时间、状态、input、output 和各类标签(idl/thrift/coze/loop/observability/domain/span.thrift L45-L77)。 - 写入用的
InputSpan要求trace_id、span_id、parent_id、started_at_micros、duration、workspace_id、input、output(idl/thrift/coze/loop/observability/domain/span.thrift L80-L96)。 - 后端 span 类型常量有
prompt、model、parser、embedding、memory、plugin、function、graph、remote、loader、transformer、vector_store、vector_retriever、agent、tool、LLMCall(backend/modules/observability/domain/trace/entity/loop_span/span.go L65-L80)。 - 状态只有
success和error两个常量,status_code == 0映射为success(backend/modules/observability/domain/trace/entity/loop_span/span.go L82-L83、backend/modules/observability/domain/trace/entity/loop_span/span.go L386-L390)。 - IDL 的状态枚举里另有
broken(idl/thrift/coze/loop/observability/domain/span.thrift L6-L8)。 - 父子关系靠
parent_id,Coze Loop 列表把parent_id为"0"或空串的 span 当 root(backend/modules/observability/domain/trace/service/trace/span_filter/cozeloop_filter.go L33-L41)。 - 详情页前端按
span_id去重、按开始时间排序,再用parent_id组树(frontend/packages/loop-components/observation-components/src/features/trace-detail/utils/span.tsx L18-L70)。 - 父节点不在当前集合时,该 span 在前端成为根(frontend/packages/loop-components/observation-components/src/features/trace-detail/utils/span.tsx L46-L57)。
GetTrace按trace_id或logid取一组 span,按开始时间升序返回(backend/modules/observability/domain/trace/service/trace_service.go L1255-L1344)。- 后端另有
SearchTraceTree,走GetTraceAll,累计上限 10 万条 span(backend/modules/observability/domain/trace/service/trace_service.go L1350-L1390、backend/modules/observability/domain/trace/service/trace_service.go L59-L62)。 - token 只统计
span_type为LLMCall或model的 span,读取input_tokens和output_tokens标签(backend/modules/observability/domain/trace/entity/loop_span/span.go L885-L929)。 - Trace 级的 token 汇总由
TraceAdvanceInfo提供,只有 input 和 output 两项(idl/thrift/coze/loop/observability/coze.loop.observability.trace.thrift L55-L63)。 - OTel 转换时,若有
input_tokens或output_tokens就补算tokens总和(backend/modules/observability/lib/otel/otel_convert.go L463-L469)。 - 金额成本:无。已在
backend/modules/observability、idl/thrift/coze/loop/observability、release/deployment/docker-compose/conf/observability.yaml搜索price、usd、currency、money、cost,只命中TokenCost和日志耗时。
二、采集:OpenAPI、OTLP、SDK、MQ、collector、批量、限流、鉴权
入口
- OpenAPI JSON 入口是
POST /v1/loop/traces/ingest(idl/thrift/coze/loop/observability/coze.loop.observability.openapi.thrift L232)。 - OTLP 入口是
POST /v1/loop/opentelemetry/v1/traces(idl/thrift/coze/loop/observability/coze.loop.observability.openapi.thrift L233、backend/api/router/coze/loop/apis/coze.loop.apis.go L625-L630)。 - OTLP 先按
Content-Encoding解压,再按Content-Type反序列化 protobuf 或 JSON(backend/modules/observability/application/openapi.go L263-L274、backend/modules/observability/application/openapi.go L367-L388、backend/modules/observability/application/openapi.go L444-L461)。 - OTLP 的
Content-Type只接受包含application/json或application/x-protobuf的值(backend/modules/observability/application/openapi.go L355-L365)。 - OTLP 响应是 protobuf 的
ExportTraceServiceResponse,用PartialSuccess.RejectedSpans报告被拒数量(backend/modules/observability/application/openapi.go L333-L352)。 - 大字段文件用
POST /v1/loop/files/upload上传(idl/thrift/coze/loop/foundation/coze.loop.foundation.openapi.thrift L29、backend/api/router/coze/loop/apis/coze.loop.apis.go L622-L623)。 - 进程内入口
IngestTracesInner没有 HTTP 路由注解(idl/thrift/coze/loop/observability/coze.loop.observability.trace.thrift L631)。
SDK
- 外部 SDK 的源码不在本仓库。仓库内只能看到服务端接收它的入口。
- 平台自己用
cozeloop-go客户端上报自身 span,客户端挂了自定义 exporterMultiSpaceSpanExporter(backend/cmd/main.go L148-L165)。 MultiSpaceSpanExporter.ExportSpans把 span 转成InputSpan,直接调IngestTracesInner(backend/infra/looptracer/exporter.go L34-L86)。MultiSpaceSpanExporter.ExportFiles把大字段通过UploadLoopFileInner上传(backend/infra/looptracer/exporter.go L91-L103、backend/infra/looptracer/exporter.go L105-L150)。- Prompt 调试和 PTaaS 在 span 上设置
call_type(backend/modules/prompt/application/debug.go L66-L96、backend/modules/prompt/application/openapi.go L1021)。
OTel 语义映射
span_type依次读cozeloop.span_type、gen_ai.request.type、gen_ai.operation.name、openinference.span.kind(backend/modules/observability/lib/otel/otel_convert.go L31-L40)。- OTel 的
chat、generate_content、text_completion映射为model,execute_tool映射为tool(backend/modules/observability/lib/otel/consts.go L133-L145)。 - openinference 的
LLM、TOOL、RETRIEVER、AGENT也有映射(backend/modules/observability/lib/otel/consts.go L140-L144)。 - 名为
litellm_request的 span 一律当model(backend/modules/observability/lib/otel/otel_convert.go L444-L447)。 session.id属性被映射成thread_id标签(backend/modules/observability/lib/otel/otel_convert.go L44-L48)。- 支持的属性前缀包括
cozeloop.*、openinference 的llm.*、input.value、spring.ai 的工具属性、litellm 的llm.request.functions(backend/modules/observability/lib/otel/consts.go L12-L77)。
鉴权
/api路由组挂SessionMW,/v1/loop路由组挂PatTokenVerifyMW(backend/api/router/coze/loop/apis/middleware.go L26-L34、backend/api/router/coze/loop/apis/coze.loop.apis.go L22、backend/api/router/coze/loop/apis/coze.loop.apis.go L490)。PatTokenVerifyMW读Authorization头,去掉Bearer前缀后校验 PAT,再取用户信息写入 ctx(backend/api/router/coze/loop/apis/middleware/pat_verify.go L20-L69)。- 写入前调
CheckIngestPermission,动作名是ingestLoopTrace(backend/modules/observability/application/openapi.go L121-L124、backend/modules/observability/infra/rpc/auth/auth.go L118-L120、backend/modules/observability/domain/component/rpc/auth.go L10)。
校验、批量与限流
- OpenAPI 写入要求同一批 span 的
workspace_id相同(backend/modules/observability/application/openapi.go L229-L242)。 - 单批 span 数上限按租户配置,默认配置是 100,缺配置时用常量 500(backend/modules/observability/application/openapi.go L244-L261、release/deployment/docker-compose/conf/observability.yaml L1-L10、backend/modules/observability/application/trace.go L44-L51)。
- OTLP 入口没有 span 数量检查(backend/modules/observability/application/openapi.go L355-L365)。
- 服务端校验
trace_id是 32 位小写十六进制、span_id是 16 位小写十六进制,且都不能全 0(backend/modules/observability/domain/trace/entity/loop_span/span.go L537-L556)。 start_time必须落在当前时间前 24 小时到后 1 小时之间(backend/modules/observability/domain/trace/entity/loop_span/span.go L557-L559)。- 过长字段会被裁剪,被裁字段名写入
system_tags_string.clip_fields(backend/modules/observability/domain/trace/entity/loop_span/span.go L850-L882)。 - 写入口
IngestTraces先跑 ingest processor,再把 span 打成TraceData交给 producer(backend/modules/observability/domain/trace/service/trace_service.go L1627-L1655)。 - producer 把消息发到 topic
trace_ingestion_event,ZSTD 压缩,单条超过 10MB 且含多个 span 时拆开重发(backend/modules/observability/infra/mq/producer/trace_producer.go L24-L25、backend/modules/observability/infra/mq/producer/trace_producer.go L42-L74、backend/modules/observability/infra/mq/producer/trace_producer.go L110)。 - producer 配置是超时 200、重试 3 次(release/deployment/docker-compose/conf/observability.yaml L1-L10)。
- collector 从同一 topic 消费,经
queue/default到clickhouse/default(release/deployment/docker-compose/conf/observability.yaml L330-L355)。 queue/default的批大小是 1000,刷新间隔 1000ms,4 个分片(release/deployment/docker-compose/conf/observability.yaml L340-L346)。- RMQ receiver 反序列化
TraceData,丢弃校验失败的 span(backend/modules/observability/domain/trace/service/collector/receiver/rmqreceiver/rmq_receiver.go L60-L86)。 - ClickHouse exporter 按 TTL 和 workspace 分组后调用
InsertSpans(backend/modules/observability/domain/trace/service/collector/exporter/clickhouseexporter/clickhouse_exporter.go L30-L54)。 - 插入失败重试 3 次,代码注释说明网络重试可能重复写入(backend/modules/observability/infra/repo/ck/spans.go L52-L68)。
- 写入限流:无。
IngestTraces与OtelIngestTraces函数体内没有调用限流方法(backend/modules/observability/application/openapi.go L108-L176、backend/modules/observability/application/openapi.go L263-L353)。 - 限流方法
allowN只被查询限流和标注限流使用(backend/modules/observability/application/openapi.go L1147-L1209)。 - 默认配置里查询限流是每 key 10 QPS(release/deployment/docker-compose/conf/observability.yaml L357-L364)。
call_type在 OpenAPI 与 OTLP 入口由GetCallType决定,开源实现恒返回"Custom"(backend/modules/observability/application/openapi.go L127-L129、backend/modules/observability/infra/span_context_extractor/span_context_extractor.go L19-L21)。- 进程内入口则从 span 的
call_type标签取值并把标签删掉(backend/modules/observability/application/trace.go L504-L566)。 - 权益服务在开源版是
NoopBenefitService,恒返回余量足够、存储 365 天(backend/cmd/main.go L272、backend/infra/external/benefit/noop.go L23-L30)。
三、存储:ClickHouse 表、索引、TTL、对象存储、多租户字段
observability_spans的列包括trace_id、span_id、parent_id、space_id、span_type、span_name、start_time、duration、status_code、input、output、object_storage、logic_delete_date,以及 string/long/float/bool/byte 标签 Map 和 system 标签 Map(release/deployment/docker-compose/bootstrap/clickhouse-init/init-sql/observability_spans.sql L1-L26)。- 引擎是
MergeTree,按toDate(start_time / 1000000)分区,主键和排序键都是start_time(release/deployment/docker-compose/bootstrap/clickhouse-init/init-sql/observability_spans.sql L34-L36)。 trace_id、space_id、span_type、span_name有 bloom filter 索引,input、output有tokenbf_v1索引(release/deployment/docker-compose/bootstrap/clickhouse-init/init-sql/observability_spans.sql L27-L33)。- Helm 的同名建表语句与 Docker 一致(release/deployment/helm-chart/charts/app/bootstrap/init/clickhouse/init-sql/observability_spans.sql L1-L36)。
- 表里没有
thread_id列,thread_id过滤落到tags_string['thread_id'](backend/modules/observability/infra/repo/ck/spans.go L488-L528、backend/modules/observability/infra/repo/ck/spans.go L663-L681)。 - 自定义字段映射到
tags_string、tags_long、tags_float、tags_bool,系统字段映射到system_tags_*(backend/modules/observability/infra/repo/ck/spans.go L488-L520)。 - 标注存在另一张表
observability_annotations,引擎是ReplacingMergeTree(updated_at),排序键是(start_time, id)(release/deployment/docker-compose/bootstrap/clickhouse-init/init-sql/observability_annotations.sql L1-L32)。 - TTL 枚举有 3、7、30、90、180、365 天(backend/modules/observability/domain/trace/entity/loop_span/span.go L95-L102)。
- 天数小于等于 4 映射为
3d,之后依次是 7、30、90、180、365(backend/modules/observability/domain/trace/entity/loop_span/span.go L990-L1003)。 - 写入时按 TTL 填
logic_delete_date,单位微秒(backend/modules/observability/infra/repo/dao/converter/span.go L69-L83)。 - ClickHouse 建表语句里没有
TTL子句。已在release/deployment/docker-compose/bootstrap/clickhouse-init/init-sql/下三个文件搜索TTL,无命中。 - 开源默认配置只给
cozeloop租户的365d配了observability_spans和observability_annotations(release/deployment/docker-compose/conf/observability.yaml L85-L93)。 - 插入表名由租户加 TTL 档位决定,可按 workspace 分片(backend/modules/observability/infra/repo/trace.go L184-L201、backend/modules/observability/infra/repo/trace.go L711-L727)。
- 开源版的存储名固定是
ck(backend/modules/observability/infra/storage/storage.go L1-L26)。 - 租户字段:写入时
space_id是 workspace,读出后把system_tags_string.tenant设为cozeloop(backend/modules/observability/infra/repo/ck/spans.go L83-L88)。 - 对象存储:span 表有
object_storage列,值是 JSON,含input_tos_key、output_tos_key和附件列表(backend/modules/observability/domain/trace/entity/loop_span/span.go L173、backend/modules/observability/domain/trace/entity/loop_span/span.go L195-L213)。 - 查询时
AttrTosProcessor把这些 key 换成签名下载 URL,写入AttrTos(backend/modules/observability/domain/trace/service/trace/span_processor/attr_tos_processor.go L23-L72)。 - 签名 URL 通过 file RPC 生成,TTL 是 5 小时(backend/modules/observability/infra/rpc/file/file.go L26-L49)。
- observability 模块自己不写对象存储。已在
backend/modules/observability搜索UploadFile、PutObject、SignUpload,无命中;上传由 SDK 或looptracer调 file 服务完成。 - 前端用
attr_tos.input_data_url或output_data_url展示大字段(frontend/packages/loop-components/observation-components/src/features/trace-data/components/raw-content.tsx L64-L65)。