@ countly/ai- sdk- core
共享运输、数据模型、成本计算和 计数AI SDK。 。 。 .
这是基础计划 一般不直接安装——每个适配器自动拉入:
@countly/ai-sdk-openai@countly/ai-sdk-anthropic@countly/ai-sdk-mastra@countly/ai-sdk-vercel@countly/ai-sdk-google-genai@countly/ai-sdk-langchain@countly/ai-sdk-cohere@countly/ai-sdk-llamaindex
Core拥有电线合同. 只有适应者决定 他们能观察到的页:1 决定 电线是什么样子的。 。 。 .
两个规则核心执行
四把钥匙上的每个字段都服从其中之一:
- 法治A——层面 为: 永远存在 他们的钥匙。 何时
适配器不能观察到一个,核心写出一个明确的监视器(
"unknown", (中文(简体) ),"not_reported")——从不猜测,从不缺席. 缺少一个层面 因此,只意味着 错误的密钥钥匙已经告诉你了. - B. 欧盟驻科法治团-措施 为: 删除 当他们不能测量的时候 带有 总是存在的字符串标记 这说明为什么。 线上没有布林斯.
两个标记:
| 标记 | 数值 | 州长 |
|---|---|---|
usage_state | reported | not_reported | usage_* 和每个 cost_* |
cost_priced | priced | priced_modality_approx | unpriced_model | no_usage | cost_* |
设计消费者的顺序: (单位:千美元) $0 成本从不 释放为 mean
"我们没有这个模型的价格",则a 0 符号计数从未向
意思是"供货商什么也没告诉我们" 这些案件省略了措施,并确定了
标记。 成本键仅在 cost_priced 实值 priced 或者说
priced_modality_approx。 。 。 .
schema_version = 2
SCHEMA_VERSION 导出并盖章为 整数 每行每行
键,所以消费者可以对它进行测距求真(一个 semver 字符串不能)。 关于它的分支,没有
打开 sdk_version。 。 。 .
它提供的东西
- 统一数据模型 (单位:千美元)
RawExtractionResult) ——开口遥测GenAI相通, 每个字段可选:无法观测到某事的适配器会省略它 核心应用监视器或标记 - 事件构建者 —— 说
[CLY]_llm_interaction(每组一行) 生成, (中文(简体) ),[CLY]_llm_tool_used, (中文(简体) ),[CLY]_llm_tool_usage_parameter, (中文(简体) ),[CLY]_llm_interaction_feedback。所有四个都带着同一个信封 - 隐藏身份(以下)
- 费用计算 47个型号的内置定价,每个型号都超过, 已分解为 脱节 组件
- API 主机分类 —— 说
classifyApiHostType编号 :extractApiHost编号 :describeApiHost - HTTP 运输 ——缓冲
POST /i,一个batch_id单能键( i) 批量生成到重试回路圈外, 请求超时, 限定缓冲,unref冲浪计时器, 死信与重试恢复,onDropped - 完成理性正常化 —— 说
stop | length | tool_calls | content_filter | error | other | not_reported - 提供者正常化 —— 说
normalizeProvider减少"openai.responses"改为"openai"(原始编号保存于provider_metadata.provider_id(中文(简体) ) - 错误分类 —— 说
rate_limit | context_length | content_filter | timeout | auth_error | api_error
隐藏身份
核心是子桶,而不是信任它们 所以这些是铁丝网上的 无论适配器计算什么:
usage_total == usage_input + usage_output
usage_input == usage_input_fresh + usage_cache_read + usage_cache_write + input audio + input image
usage_output == usage_output_text + usage_reasoning + output audio + output image
usage_input 是提供商的 共计 输入数 - 包容性 两者
缓存读取和缓存写出——因此它与提供者的仪表板相协调.
usage_input_fresh 是剩余部分。 原因是一个 产出子集,而不是一个
加上这个.
每一个子桶都夹住 上面已经说过的,所以它
永远不能超过总数。 夹子表示适配器损坏了身份
debug 模式核心记录它的声音.
成本是脱节的
CostResult 拆分为组件,其中 每张纪念币都写满了
一次这样 cost_total 他们的纯数是:
cost_input (仅新投入)+ cost_cache_read + 键 cost_cache_write + 键
cost_output (不包括单价推理)+ cost_reason
cost_cache_saved 被报告为缓存所保存的
新的输入率——不是总和的一部分.
此修补的两个货币错误: 缓存- 写出符牌被开到一个顶端
usage_input已经包含它们(2.25x而不是1.25x) Anthropic),且全部产出计数按产出速率计出 那么,推理 已添加 时间reason_per_mtok夸大每个双子座 思维模式呼叫最多2x.
删除: cost_cached (从未有过生产商) 使用 cost_cache_read 编号 :
cost_cache_saved。 。 。 .
身份字段
| 外地 | 含义 |
|---|---|
event_id | 乌伊,那个 仅限 一行的主要密钥 |
run_id | 转弯/呼叫身份——所有跨家庭范围界定的合并栏目 |
prompt_id | (单位:千美元) 父事实行的外键,而不是一个身份 |
generation_index | 运行中这一代人的0个位置 |
run_latency_total | 转弯的端到端的壁上钟 (= latency_total 单调适配器) |
parent_event_key | 哪个行 prompt_id 点数 : "[CLY]_llm_interaction" 或者说 "run" |
run_id 解决第一对胜: raw.run_id → EventContext.run_id →
EventContext.prompt_id (主办方: getPromptId()– 一个生成的uuid.
API 主机
import { describeApiHost } from "@countly/ai-sdk-core";
const raw = { ...describeApiHost(client.baseURL) };
// → { api_host_type: "gateway", api_host: "ai-gateway.count.ly" }
api_host_type ∈ vendor_direct | azure_openai | gateway | local_infra | unknown。 。 。 . api_host 这是 只有主机名 - 方案、证书、端口、路径
和查询被剥去, 并且永远不会被放出, 因为一个提供者路径是例行的
携带 api 密钥和部署 id.
删除: api_base_url (raw URL-证书-漏出风险).
直接使用
如果您正在构建一个自定义的集成, 而现有适配器没有覆盖:
import {
resolveConfig,
createTransport,
buildAllEvents,
generatePromptId,
describeApiHost,
} from "@countly/ai-sdk-core";
const config = resolveConfig({
appKey: "YOUR_APP_KEY",
url: "https://your-countly-server.com",
});
const transport = createTransport(config);
const raw = {
provider: "my-llm",
model: "my-model",
...describeApiHost("https://llm.internal.example.com/v1"),
usage_input: 100, // TOTAL input, including any cache buckets
usage_output: 50, // TOTAL output, including any reasoning
latency_total: 1200,
status: "success" as const,
};
const events = buildAllEvents(raw, config, {
prompt_id: generatePromptId(), // becomes run_id
sdk_adapter: "custom",
});
transport.enqueue(events);
写适配器时要知道两件事:
- 奥米,永远不要捏造. 离开
model编号 :provider编号 :status缺席和核心 写入"unknown"离开latency_total没有发送0。 。 。 . 离开RawToolCall.status当你只看到模型的时候就不见了 请求 页:1 工具——核心发射"unknown"而不是捏造的成功. saw_generation: false完全压制交互行. 用它来 一个没有生成的跟踪: 工具行仍然发出, 范围由run_id。 。 。 . 这就是"无代相传的痕迹"是如何被执行的.
反馈活动
反馈不是自动收集的——从你的UI的拇指上下处理器中接通.
使用 createFeedbackTracker (也由每个适配器包再出口)
id通过 onPrompt 配置回调 :
import { createFeedbackTracker } from "@countly/ai-sdk-core";
const feedback = createFeedbackTracker(
{ appKey: "YOUR_APP_KEY", url: "https://your-countly-server.com" },
{ sdk_adapter: "openai" }, // stamp which adapter produced the rated interaction
);
// Rate the whole turn (PromptInfo.prompt_id === PromptInfo.run_id):
feedback.track({ prompt_id: info.prompt_id, rating: "thumbs_up", deviceId: userId });
// Rate ONE generation of a multi-step turn:
feedback.track({ prompt_id: info.event_id, run_id: info.run_id, rating: "thumbs_down" });
parent_event_key 记录你干的那两个 rating_kind
(单位:千美元)binary | score | category | other) 由此得出一个数值分数不能再
平均为拇指上升率.
对于需要完全控制的自定义集成,低层
buildFeedbackEvent + 键 createTransport 原始人还可用.
运输限制
| 配置 | 默认 | 为什么它存在 |
|---|---|---|
flushInterval | 10_000 分钟 | 计时器是 unref'd,所以一个CLI或没有服务器的处理器仍然可以退出 |
maxBatchSize | 20 | 冲花被序列化; shutdown() 循环直到缓冲器为空 |
maxBufferSize | 10_000 | sDK不能使用它最古老的装置 |
requestTimeout | 15_000 分钟 | 没有它一个挂起的插座块 flush() 因此 shutdown() 永远 |
textPreviewLength | 200 (最多数) 8192(中文(简体) ) | 预览的 UX 节点 |
paramValueLength | 1024 (最多数) 8192(中文(简体) ) | 摄入安全性最不稳定:一个多 MB 工具参数将整个批次编号为死字 |
onDropped(events, reason) | —— 说 | 一个宿主唯一了解其计数的方法是报告不足(send_failed | buffer_full(中文(简体) ) |
transport.stats() 返回时 { buffered, deadLettered, dropped }。 。 。 .
截角可见 : truncateText 削减 a 代码点 边界( 切片)
按代码单位拆分代对并生成无效的 UTF-8, 附加 …,以及
该行携带一个 *_state 设置为 complete 或者说 truncated。 。 。 .
timestamp 进入 毫秒 财务报告和审定财务报表 hour编号 :dow 为: 世界协调时计算一次
转一行就是字节 他们曾经来自
容器的本地时区,该时区与UTC同行的时代不一致.
完整文档
见 计数 AI SDK 仓库 用于完整线条格式 适配器能力 矩阵, (中文(简体) ), 通过Async LocalStorage、隐私每个用户的可观察性水平(0/1/2) 控件,并计数插件集成(Drill、Funnels、Cohorts、APM、Crash) 解说.
许可证
麻省理工学院