从零开始构建上下文层
本文深入拆解了我们是如何构建那一层上下文的:它包含什么、我们如何组织它,以及那些让智能体从"偶尔答对"变成"稳定给出准确答案"的关键模式。
AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | PLC在线仿真器 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo
在 Gorgias,我们构建了一个内部 AI 智能体,能够跨各个部门——财务、产品、销售和客户成功——回答业务问题,方法是针对我们的数据仓库生成 SQL 查询。最难的部分其实不是智能体本身,而是给智能体足够的上下文,让它正确且一致地回答问题。
本文深入拆解了我们是如何构建那一层上下文的:它包含什么、我们如何组织它,以及那些让智能体从"偶尔答对"变成"稳定给出准确答案"的关键模式。
1、什么是上下文层?
上下文层,就是你的 AI 应用为了产出好答案而需要的全部信息的集合。它包括后端表、销售通话记录、内部文档、产品事件数据、分析师评论、业务定义——总之,就是一个有经验的员工在回答问题时所会调取的一切。
这个定义刻意做得宽泛,因为重点本就在于来源的包容性。挑战不在于定义它,而在于让所有这些信息公开可用、保持最新、并且能被智能体检索。
2、拼凑碎片
在 Gorgias,我们的智能体需要处理的数据横跨多种来源和格式:
- 后端数据:支撑产品的 PostgreSQL 表
- Notion 页面:多个团队维护的文档
- Gong 通话记录:CSM 与销售通话的录音
- 前端事件:用户交互追踪数据
- GitHub PR 历史:了解谁合并了什么
- Linear:项目进度与状态
- 第三方工具:HubSpot、SpotDraft、Customer.io 等
结构化和半结构化数据已经在 BigQuery 中管理。缺失的那块——也是潜力巨大的一块——是非结构化文本内容:通话记录、合同、文档页面。为此,我们评估了三种方案。
方案 1:独立的向量数据库
对于 Gong 记录、Notion 页面这类开放文本,专门的向量数据库(Pinecone、Weaviate 等)可以让我们对内容进行分块、向量化并检索。但这等于要在现有数据栈之外再维护一整套独立系统。

方案 2:直接的 MCP 连接
有些工具提供 MCP(Model Context Protocol,模型上下文协议)服务器,智能体可以直接调用它们来检索相关内容。但在实践中,这在我们这里行不通。我们对检索质量和成本都没有控制权,而不同服务商之间的一致性差异,使得它在规模化时难以落地。

方案 3:以 BigQuery 作为统一层
这是我们最终选择的方案。BigQuery 最近原生支持了向量检索,这意味着我们可以把向量直接存在 BigQuery 表里,无需离开数据仓库就能做相似度搜索。(文档在此。)
下面是一张带有向量列的表的样子——ml_generate_embedding_result 列存储了 full_transcript 的向量表示:

然后我就可以在那张表上跑向量检索,拿到与问题最相近的 10 条通话记录:
-- Find the 10 transcripts most relevant to pricing objections
SELECT distance, base.call_id, base.full_transcript
FROM VECTOR_SEARCH(
(SELECT * FROM `gorgias-growth-production.context_layer.gong_transcripts_embeddings`),
'ml_generate_embedding_result',
(SELECT ml_generate_embedding_result FROM ML.GENERATE_EMBEDDING(
MODEL `{{embedding_model}}`,
(SELECT 'pricing objections budget concerns cost too expensive' AS content)
)),
top_k => 10
)
这个方案让我们两全其美。我们用 Airbyte 的预置连接器或自研同步工具(Bizon)从每个工具摄取内容,再用 dbt 做转换、分块,并在需要时向量化。结果是:来自 Postgres 表的结构化数据与来自 Gong、Notion 的非结构化文本,都以同一种格式——BigQuery 表——存在一起,智能体可以一致地查询它们。

于是问题变成了:智能体如何搞懂将近一百张表?
3、帮助智能体导航
如果你把一个 LLM 指向一个 BigQuery 项目、抛给它一个业务问题,它会开始采样表、读 schema、翻 INFORMATION_SCHEMA。即便上传了 dbt 文档,这种做法也慢、贵、且不可靠。
我们的目标是提供一个单一入口:一张表,让智能体在决定查询哪些表之前,就能看到所有可用内容。
我们构建了 ctx__model_metadata——一张 BigQuery 表,每一行描述一个智能体可用的表。每一行包含:
- 表描述与带类型的列描述
- 表的负责人(owner)
has_semantic_search:一个标志,指示该表是否含有用于向量检索的向量model_sql:生成该表的实际 dbt 代码(用于回答"这个指标是怎么算出来的?")upstream_models/downstream_models:转换层中的依赖关系

这让智能体能快速、结构化地访问一切。但光有元数据并不能告诉它何时、如何使用每张表。所以我们又加了两个关键字段。
when_to_use
用一两句话说明这张表所服务的具体使用场景。这里的精确性很重要——智能体用它来判断哪些表与问题相关。下面是 dim_gorgias_employees 的例子:
Use this table to find Gorgias employee details, understand the
organizational structure, or look up contact information. It
combines data from Rippling (employee records), Calendly (scheduling),
and HubSpot (CRM user data) to provide a complete view of employees,
including names, emails, departments, teams, locations, manager relationships,
and scheduling links.
how_to_use
这是智能体学会生成正确 SQL 的地方。该字段包含通用说明(默认过滤条件、连接键),而最重要的是示例查询——每一条都把自然语言问题与它对应的 SQL 配对起来。问题与查询并置,能教会智能体:哪类问题该关注哪些列。
### Best Practices
- Always filter by `status = 'ACTIVE'` to get current employees only
- Use `email` as the primary identifier for joining with other tables
### Example Queries
**Find Employee by Name:**
SELECT email, first_name, last_name, department, team, calendly_link
FROM ref("dim_gorgias_employees")
WHERE LOWER(first_name) LIKE '%john%'
OR LOWER(last_name) LIKE '%doe%'
**Find Employee by Email:**
SELECT *
FROM ref("dim_gorgias_employees")
WHERE email = 'john.doe@gorgias.com'
如果有足够素材,我们建议每张表配 10–15 条示例查询。聚焦只用这一张表(无连接)的查询,跨表模式在其他地方处理。
最终的 ctx__model_metadata 长这样:

4、我们如何构建 ctx__model_metadata
这里有个"套娃"式的问题:ctx__model_metadata 包含的是关于表的元数据,而那些元数据本身并不存在于任何一张表里。下面是我们怎么解决的。
事实来源是 dbt 里的 .yml 文件,我们本来就在那里定义表描述、列描述和负责人。我们给它扩展了三个字段:一个标志(include_in_context_layer)、when_to_use 和 how_to_use:
models:
- name: dim_gorgias_employees
description: |
Consolidates data from Rippling (employee records)...
meta:
owner: owner@gorgias.com
include_in_context_layer: true
context_layer_info:
when_to_use: |
Use this table when...
how_to_use: |
### Search Techniques
**1. Find Employee by Name**
...
columns:
- name: id
description: The unique identifier for each employee...
- name: first_name
description: The first name of the Gorgias employee...
生成 ctx__model_metadata 的流水线如下:
- 运行
dbt parse,产出一份干净的 manifest,包含所有 yml 内容、SQL 代码和模型依赖。 - 过滤出带有
include_in_context_layer: true的模型。 - 提取列描述、负责人、表描述、
when_to_use、how_to_use。 - 查询
INFORMATION_SCHEMA获取列类型。 - 分批创建并填充
ctx__model_metadata(这一步是必要的,因为我们要把所有内容 union 进 insert 语句,可能触达 BigQuery 100 万字符的查询上限)。
我们最初尝试把它实现成一个自定义 dbt materialization,但在 dbt run 内部很难并行化。于是我们改成一个 Python 脚本,以并行方式做同样的事,大幅加速了流程。这个脚本在 CI 里每次 merge 时运行,让 ctx__model_metadata 始终与最新的模型变更保持同步。
5、超越表
表元数据能让你走得出奇地远,但大多数真实的业务问题都很复杂。它们需要组合多张表、理解业务上下文,并处理那些不存在于任何 schema 中的注意事项。when_to_use 和 how_to_use 字段嵌入了其中一部分知识,但还不足以支撑稳定准确。
我们需要一种方式,让有经验的人——分析师、业务干系人——能给智能体提供更丰富的指令。这就是 ctx__instructions:一张结构化的、可检索的指令表,智能体在访问 ctx__model_metadata 之前会先查阅它。
5.1 为什么不用一个大 prompt?
朴素的做法是写一个囊括所有部门指标、业务上下文和注意事项的庞大 markdown 文件。我们试过。它失败有三个原因:
- 无法维护。 在一个巨大的文本文件里查找和更新信息非常痛苦。
- 上下文窗口噪声。 智能体每问一个问题都要吞下整个文件,把无关信息塞满上下文,推高成本。
- 智能体无处导航。 一切都是扁平的。
显然的修正是把文件分块、向量化,再通过向量检索取回相关片段。但这也有自己的问题:
- 检索不透明。 很难调试为什么某些块被取回、某些没有。比较向量并不直观。
- 分块脆弱。 有些信息必须待在一起。找到合适的块大小是召回率与精确率之间的权衡。
- 关系丢失。 分块没有层级。相关上下文之间的连接消失了。
这引出了一个关键洞察:当你无法掌控文本时,向量检索和分块才是正确的工具。 一段 Gong 通话记录庞大、非结构化,你无法重塑它。但当你亲自编写上下文时,你可以结构化它,让检索、维护和调试都大幅变好。
5.2 渐进式披露(Progressive Disclosure)
我们借用了 Anthropic Claude Code 技能系统的一个模式。思路是:与其一开始就全量加载,不如先让智能体只拿到指令主题的名称和描述。如果某个主题相关,智能体就加载它;若那个主题又引用了子主题,智能体可以再加载那些——按需、递归地进行。
我们围绕 Gorgias 的部门(产品、财务、市场进入、客户成功)来组织顶层指令。这与人们提问的方式一致——每个部门有自己的词汇、自己的看数据方式、自己关切的事。
一个关键的设计选择: 围绕提问者而非数据维护者来组织指令。多个部门会查询相同的底层表,但他们以不同方式问不同侧面。一个问"AI Agent 性能"的产品经理,和一个问"AI Agent 收入"的财务分析师,打的是重叠的数据,但他们需要的上下文不同。

5.3 主题指令的结构
每条主题指令有五个部分:
1. Front Matter——元数据,包括负责人、指令组、类型、描述和路径。
---
active: true
owner: owner@gorgias.com
instruction_group: product
instruction_sub_group: ai_agent
instruction_type: subtopic
description: >
Covers:
- AI Agent configuration (Knowledge, Guidance, Support Actions)
- AI Agent usage (Ticket, Automated Interaction, Execution)
- AI Agent feedback and quality metrics
- AI Agent LLM costs
Do NOT use this topic for:
- Other products outside of AI Agent
- General ticket information not specific to AI Agent
path: product/ai_agent
---
2. Context——解释业务领域的开放文本。
--- context ---
# AI Agent
AI Agent is Gorgias's flagship automation product that autonomously handles
customer conversations using advanced language models. It serves two functions:
- **AI Support Agent** (`ai_agent_ticket_type = 'support'`): Automates support
inquiries, replacing legacy tools (Flows, Autoresponders, Quick Responses).
- **Shopping Assistant** (`ai_agent_ticket_type = 'sales'`): Drives sales through
product recommendations and discount offers.
3. Datasources——哪些表与该主题相关,以及为什么。
--- datasources ---
| Table | Use Case |
|-------|----------|
| `dbt_product.dim_tickets` | Ticket-level information for AI Agent tickets |
| `dbt_product.dim_ai_agents` | AI Agent configuration at shop level |
| `dbt_product.fct_ai_agent_prompt_flow` | Execution-level pipeline run tracking |
4. SQL Examples——展示如何在该领域回答常见问题的"问题—查询"对。
--- sql ---
## AI Agent Contribution to Automate ARR
The percentage of total Automate ARR from AI Agent-enabled merchants.
WITH automate_arr_table AS (
SELECT
date_observed,
gorgias_account_id,
automate_arr,
CASE
WHEN is_ai_agent_support_enabled THEN automate_arr
ELSE 0
END AS ai_agent_arr
FROM ref("accounts_history")
WHERE status = 'customer'
AND automate_subscription_status = 'current_subscriber'
AND automate_arr > 0
)
SELECT
date_observed,
SUM(automate_arr) AS total_automate_arr,
SUM(ai_agent_arr) AS ai_agent_contribution_arr,
SAFE_DIVIDE(SUM(ai_agent_arr), SUM(automate_arr)) AS ai_agent_arr_pct
FROM automate_arr_table
WHERE date_observed >= CURRENT_DATE() - INTERVAL 7 DAY
GROUP BY date_observed
ORDER BY date_observed
5. Subtopics——智能体若需深入可加载的子主题。
--- subtopics ---
### ai_agent_configurations - Feature Enablement and Configurations
Covers AI Agent enablement, trials, and configuration including Knowledge,
channel settings, and agent personalization.
### shopping_assistant - Shopping Assistant
Covers Shopping Assistant configuration, engagement features, and Quick Replies.
### ai_agent_quality - Quality Metrics
Covers merchant feedback, shopper feedback, CSAT scores, and quality rates.
这种可组合结构意味着,任何一条指令都可以按需做到很浅或很深。当一条指令变得过大,就把它拆成子节点。
为了填充 ctx__instructions,一个 Python 脚本解析每个指令文件的 front matter,把元数据和内容一并插入表中。

5.4 技能指令:处理复杂的多步问题
主题指令覆盖业务上下文,帮助智能体回答直白的问题。但有些问题需要多步推理:按顺序从几张表取数、套用业务规则、并以特定方式格式化输出。对于这些,智能体需要的不仅仅是上下文——它需要一个操作手册。
我们称之为技能指令(skill instructions),同样借用了 Anthropic Claude Code 的术语,理由也一样:它们教智能体如何做一件具体的事。与主题指令(通过层级导航被发现)不同,技能对智能体是直接可用的。当用户的问题命中某个技能覆盖范围时,智能体就检索并遵循它。
技能指令拥有与主题指令相同的五个部分,外加三个额外部分:
5.5 Steps
智能体应遵循的有序动作。每一步都包含上下文和 SQL,引导智能体走完流程。
--- step ---
### Step 2: Get Most Recent ARR Movement(s)
Query the most recent movement per product for the customer.
**CRITICAL: Do NOT filter by `change_type` in this query.** Capture ALL
movement types and return the most recent per product. When a user asks about
"contractions," they need to see the full picture - a product churn on the
same day as a contraction would be missed if you filter by change_type.
WITH ranked_movements AS (
SELECT
pm.date_observed,
pm.billing_customer_id,
a.gorgias_subdomain,
...
)
5.6 Output Format
智能体必须产出的精确结构,确保无论谁来问,答案都一致。
--- format ---
**[Customer Subdomain] - ARR Movement Diagnosis**
**Most Recent Movement:** [Date]
- Type: [Movement Type]
- ARR Change: [+/-$X,XXX]
- Product: [product name]
**What Happened:**
[Brief description]
**Driver(s):**
1. [Driver 1]: [details]
2. [Driver 2]: [details]
5.7 Checks
智能体在给出答案前运行的检查清单——确认查询已执行、格式正确、没有遗漏步骤。
--- checks ---
- [ ] Followed the output format exactly
- [ ] Always included the Most Recent Movement Report
- [ ] Identified customer(s) correctly by subdomain or billing_customer_id
- [ ] Included movement type and ARR delta for each product
步骤、格式与检查三者结合,把智能体从"尽力而为"变成了"遵循这套流程"。一致性上的差别是巨大的。
6、全貌
下面是它们如何拼在一起。上下文层有清晰的层级:
- 表是原子单位——数据本身,在
ctx__model_metadata中描述。 - 主题指令把业务问题连接到正确的表,按部门组织、通过渐进式披露可导航。
- 技能指令为复杂的多步任务提供分步操作手册,同时引用主题与表。

每次有人提问,智能体都遵循这个模式:
- 检查问题是否匹配某个技能。如果是,加载并遵循它。
- 否则,查阅
ctx__instructions来识别相关主题。加载它(必要时连同子主题)。 - 用该主题的 datasources 和智能体自己的判断,在
ctx__model_metadata中识别相关表。 - 读取这些表的
when_to_use、how_to_use和列描述。 - 生成并执行 SQL 查询。
- 组织答案。

7、开始前我们想告诉你的事
先为你被查询最多的表写 when_to_use 和 how_to_use。记录约 100 张表花了不小的功夫。我们按查询频率排优先级,并从那里迭代。如果你从零开始,先从 10–15 张核心表入手,再随着你看到人们实际问的问题逐步扩展。
做好持续迭代指令的心理准备。 智能体的每一次错误回答,都是一个信号:某条指令缺失或不清晰。智能体错误与指令改进之间的反馈循环,是最该做对的过程——也是下一篇文章的主题。
渐进式披露是单一最大的改进。 从扁平的单一 prompt 转向层级化、按需加载的指令,减少了噪声、降低了成本,并让智能体的可靠性大幅提升。如果你从本文只取一个想法,就取这一个。
原文链接: Building a context layer from the ground up
汇智网翻译整理,转载请标明出处