ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

智能问答知识库,TaoToken 在 embedding 与 chat 两处消耗 Token

智能问答知识库,TaoToken 在 embedding 与 chat 两处消耗 Token 1. 文档变知识库问答真正花 Token 的是 embedding 与 chat 两处先说结论把一堆 PDF、Markdown、Word 变成「能问答的知识库」Token 并不只花在问答那一下而是花在两处。TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_intro作为统一的模型调用入口把这两处调用收敛到同一组 Key 和同一个 Base URL 上配起来省事但也正因为「一个 Key 打天下」两阶段的账很容易糊在一起。腾讯开源的那个「上传文档 → 自动切片 → 向量化 → 检索问答」项目最近被反复提起。它的上手路径很短部署服务、上传文档、在页面上直接提问。但对盯着调用成本的人来说真正值得拆开看的是中间那条链路——文档入库时每一个切片都要过一次 embedding 模型用户提问时检索命中的若干切片会被拼进 Prompt再过一次 chat 模型。前者只有输入 Token后者输入和输出都计费两阶段的量级、触发频率、优化手段完全不同。不少人在这一步会卡住服务要求填 LLM API Key 和 API 地址填完却发现检索没结果或者答案答非所问回头查用量也说不清是哪一阶段烧掉的。本文按「模型调用成本观察者」的视角把 embedding 与 chat 两阶段拆开给出可复现的请求样本、可落地的 Token 统计表以及同一套 Key 在 Claude Code、Codex、CC Switch 里的配置写法。2. Key 与 API 地址填在哪知识库问答服务的两处配置落点知识库问答类服务通常只有一个「模型配置」入口但它内部至少要构造两个客户端一个 embedding 客户端、一个 chat 客户端。两者可以共用同一个 Key也可以分开管理。拿 Key 的入口在官网控制台从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_console 进入后创建 API Key也可以直接打开 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_apikey 创建与轮换 KeyBase URL 统一填https://taotoken.net/api工具配置不带 UTM 参数Key 占位符在代码里写成YOUR_API_KEY不要提交进 Git。用.env把两阶段配置分开写便于后续做用量归因# .env TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_API_KEYYOUR_API_KEY # 两个阶段即使共用 Key也建议分两个变量名方便以后拆 Key EMBEDDING_MODELtext-embedding-3-large CHAT_MODELgpt-4o-miniimport os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], )配置项本身只有两个字段Base URL 和 Key。真正影响体感的是模型名——embedding 模型和 chat 模型必须分别填写且模型名要以控制台模型列表里的实际标识为准。把 embedding 模型名填进 chat 位置通常会得到 404 或参数错误反过来则会因为向量维度对不上导致检索阶段直接报错。3. embedding 请求样本切片、批量与 Token 口径embedding 阶段的成本结构最简单只有输入 Token没有输出 Token。也就是说文档有多少字被送进模型就有多少 Token 被计入与「切了多少块」无关只与「送进去的文本总量」有关——切片策略影响的是检索质量而不是 embedding 的账单除非你因为策略不当而反复重建索引。下面是一个批量向量化的最小可运行样本同时把usage.prompt_tokens打成日志方便后面做统计import hashlib import json from openai import OpenAI client OpenAI( api_keyYOUR_API_KEY, base_urlhttps://taotoken.net/api, ) def content_hash(text: str) - str: return hashlib.sha1(text.encode(utf-8)).hexdigest() def embed_chunks(chunks: list[str], model: str text-embedding-3-large, batch_size: int 32) - list[list[float]]: vectors, seen [], set() for i in range(0, len(chunks), batch_size): batch [c for c in chunks[i:i batch_size] if c.strip()] # 同一次任务内按内容去重减少重复计费 batch [c for c in batch if not (content_hash(c) in seen) or seen.add(content_hash(c))] resp client.embeddings.create(modelmodel, inputbatch) print(json.dumps({ stage: embedding, model: model, batch: len(batch), prompt_tokens: resp.usage.prompt_tokens, total_tokens: resp.usage.total_tokens, }, ensure_asciiFalse)) vectors.extend([d.embedding for d in resp.data]) return vectors几个容易踩的点批量大小控制在 1664 条之间。批量越大吞吐越高但单请求体过大容易触发长度上限失败重试时反而重复计费。按内容哈希去重。同一份文档被重复上传、同一切片在多个文件里重复出现时去重能直接省掉一部分 embedding Token。增量索引。只对新增或修改过的切片调用 embedding未变更的切片直接复用已有向量这是 embedding 阶段最有效的一刀。维度一旦确定就不要换模型。换 embedding 模型意味着整个库的向量都要重算同时向量维度变化会让原来的向量库直接失效。4. chat 请求样本检索结果进 Prompt 之后Token 是怎么膨胀的chat 阶段的账单结构和 embedding 完全不同输入是「系统提示 检索到的上下文 用户问题」输出是模型生成的内容两者都计费而且输出往往比输入贵。对知识库问答来说输入侧最容易失控的地方是上下文注入量。from openai import OpenAI client OpenAI( api_keyYOUR_API_KEY, base_urlhttps://taotoken.net/api, ) SYSTEM_PROMPT 你是知识库问答助手。只依据给定资料回答资料中没有的内容直接说明未检索到不要编造。 def answer(question: str, contexts: list[str], top_k: int 5): ctx contexts[:top_k] user_content ( 以下是检索到的资料片段\n\n \n\n---\n\n.join(ctx) f\n\n请依据上述资料回答问题{question} ) resp client.chat.completions.create( modelgpt-4o-mini, # 以控制台模型列表为准 temperature0.2, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_content}, ], ) usage resp.usage print({ stage: chat, prompt_tokens: usage.prompt_tokens, completion_tokens: usage.completion_tokens, total_tokens: usage.total_tokens, }) return resp.choices[0].message.content这段代码里决定成本的其实是三个参数而不是模型本身top_k检索回几段。从 5 提到 10输入 Token 大致翻倍但答案质量未必线性提升。单个切片长度切片过长会挤占上下文预算切片过短会引入更多条数。输出上限如果业务允许给max_tokens设一个合理上界避免模型在开放式问题上长篇输出。还有一个常被忽略的点多轮对话会累积输入。用户连续追问时如果把历史消息原样带下去输入 Token 会随轮次增长。做法是把检索上下文每轮重新拼装历史只保留最近一到两轮或先做一次摘要压缩再入 Prompt。5. 两阶段 Token 统计表一张表看清钱花在哪想把成本讲清楚必须把两阶段分开计数。下面这张表可以直接照抄成你自己的台账模板其中「示例量级」仅用于说明相对关系实际数值请以你自己跑出来的usage字段为准。阶段触发时机计入 Token 的字段示例量级单次任务主要优化动作embedding文档入库、重建索引仅prompt_tokens1 万个切片 × 约 300 Token ≈ 300 万输入 Token内容去重、增量索引、避免全量重建chat每次用户提问prompt_tokenscompletion_tokenstop_k5每段约 300 Token ≈ 1500 输入 数百输出控制 top_k、压缩上下文、限制输出长度把两阶段写进同一份日志才能算出「每回答一个问题平均花掉多少 Token」import csv from datetime import datetime FIELDS [ts, stage, model, prompt_tokens, completion_tokens, total_tokens, note] def log_usage(stage: str, model: str, prompt: int, completion: int, note: str ): with open(token_ledger.csv, a, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesFIELDS) if f.tell() 0: writer.writeheader() writer.writerow({ ts: datetime.now().isoformat(timespecseconds), stage: stage, model: model, prompt_tokens: prompt, completion_tokens: completion, total_tokens: prompt completion, note: note, })有了这份 CSV就能回答几个关键问题一次全量重跑索引消耗了多少单个问题平均消耗多少哪一天出现异常尖峰是有人上传了大文件还是有人连续追问把「阶段」列做一次分组求和embedding 和 chat 的占比通常会让第一次看到的人有点意外——在文档更新不频繁的知识库里chat 往往才是长期支出的主力而在频繁重建索引的场景里embedding 会阶段性反超。6. 同一套 Key 接到 Claude Code、Codex 与 CC Switch知识库问答服务跑通之后很多人的下一步是用 CLI 工具在终端里直接查文档、改代码。这里可以复用同一套 Key但要注意各工具的配置字段完全不同不要把 Claude Code 的ANTHROPIC_*变量写给 Codex。Claude Codesettings.json在~/.claude/settings.json或项目内的.claude/settings.json里写{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: claude-sonnet-4-5 } }保存后重开终端让环境变量生效。模型名以控制台模型列表为准字段名保持ANTHROPIC_BASE_URL与ANTHROPIC_AUTH_TOKEN不要改动。更细的配置说明可以看 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_cc_doc 。Codexconfig.tomlCodex 走的是另一套字段配置文件一般在~/.codex/config.tomlmodel gpt-5-codex model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat然后在 shell 里导出 Keyexport TAOTOKEN_API_KEYYOUR_API_KEY注意env_key指的是「从哪个环境变量读 Key」不是 Key 本身把ANTHROPIC_*那套变量名搬过来只会得到鉴权失败。CC Switch三件套用 CC Switch 这类切换器管理多套供应商时需要填的其实只有三件套Base URLhttps://taotoken.net/apiAPI KeyYOUR_API_KEY模型名按控制台模型列表选择embedding 与 chat 分别对应不同的模型标识三件套填完保存、激活对应配置然后重启 CLI 工具避免旧的环境变量残留在当前会话里。7. 排障embedding 与 chat 的报错不是一个思路两阶段共用一个 Base URL但报错语义差别很大按下面这张对照表排查会比盲改配置快很多。现象更可能出问题的阶段排查方向401 / 鉴权失败两阶段都可能Key 前后有空格或引号、环境变量未生效、.env未被加载404 / 路径不存在两阶段都可能Base URL 是否被客户端自动补了路径后缀以控制台文档给的完整地址为准索引写入报维度不一致embedding中途更换过 embedding 模型旧向量与新向量维度不同需要重建索引检索为空、问答答非所问embedding 检索切片太碎或太长、相似度阈值过严、top_k 太小上下文超限chat检索片段总量超过模型上下文先降 top_k 再考虑压缩429 / 请求过频两阶段都可能批量向量化并发过高加退避重试把批量调小一点一个实用的定位手法先用一条固定文本单独调 embedding确认usage.prompt_tokens能正常返回再用一条固定问题单独调 chat确认输出正常。两段分别通了再回到知识库服务里排查拼接逻辑问题范围会小很多。8. 成本观察清单与下一步把上面的内容收敛成一份可以贴在项目 README 里的清单Base URL 只有一个https://taotoken.net/apiKey 从控制台创建代码里写YOUR_API_KEYembedding 只计输入 Token优化重点是去重与增量不是切片数量chat 输入输出都计费优化重点是top_k、上下文长度与输出上限两阶段分别打日志落成 CSV按阶段分组求和才知道钱去了哪CLI 工具配置各按各的字段写Claude Code 用ANTHROPIC_*Codex 用config.tomlCC Switch 填三件套。下一步可以按这个顺序动手先在 https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_chat 里把 chat 模型跑一条请求确认返回结构再把知识库服务的两个客户端指向同一个 Base URL用第 3、4 节的样本各发一次请求对比usage字段随后到 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_plan 了解适合长期跑批的套餐形态接着在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_key 创建正式 Key替换掉占位符最后参考 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentkb_qa_cc 把 Claude Code 也接上让「查文档」和「问知识库」共用同一套入口。整条链路跑通后你会得到两样东西一份能复现的两阶段请求样本和一张能解释账单来源的 Token 统计表——对做知识库问答的人来说后者往往比前者更值钱。
返回列表