Files
AgentLab/backend/app/api/copilot_context.py
T
ThearchyHelios 4c0e83bc80 目录视图接缝 data/catalog_view.py(细案 01 做法 5,总方案 X7):读目录的四处统一经它,「开目录 / 关目录 / 只用已核实的项 / 目录快照」的对照才做得了
- CatalogView{mode: live | snapshot | off, statuses: all | reviewed, snapshot}:缺省 live + all,行为和以前一样;reviewed 只留已核实、已确认的项(驳回的项原样留着交给各处,照旧经 visible_notes 去掉,冻结快照和 SQL 检查靠驳回的关系编号认出不该现推的关系——为此在 catalog.py 加 restrict_notes,没给 visible_notes 加参数)
- 视图从哪来:运行从 Run.options['catalog_view'] 读(catalog_view.of_run:RunContext.extra 里的发起快照,审批恢复、续跑沿用),ToolContext 多一个 catalog_view,节点(调用工具、llm/agent、多 agent)构造时带上;生成过程从参数读(下一个提交接 answer_ops)
- 四处消费一律经它:运行时 db_schema / db_query(tools/datasource._catalog_of,冻结进表结构快照的也是这一份)、look 表详情(TableKind.load、copilot_context.look_service 收 catalog_view)、自查的 SQL 检查器(sqlcheck.load_checkers 收 catalog_view,改用 catalog.read_catalogs 一次读出)、双盲复算(_Sources 带视图,交给盲算的目录和比较 SQL 用的中文名都按它;核查 agent、反方审稿的 db_schema 也按这次运行的视图)
- 冻结快照:不是缺省视图的在引用 meta 里记下 catalog_view(不进内容哈希,缺省视图不记,快照和以前一字不差),证据面板据此写得出「本次运行目录:关」
- snapshot 模式只定读法(kind=eval_catalog_snapshot 的工件,{sources: {源 id: {tables: {表: {version, notes}}}}}),内容由第 2 波的题集包写;快照不在了照实报错(CatalogSnapshotMissing),不悄悄退回当前目录
- 新增 tests/test_catalog_view.py:四种视图在四处给的是同一份、缺省等于当前目录、运行从 options 读且冻结 meta 记下、lint(消费模块不许直接 read_catalog / read_entry / 查 CatalogNote);去掉 _catalog_of 里的视图回跑两项失败
2026-10-16 10:28:01 +08:00

110 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""助手的数据上下文:system 里只给概览,要看什么由模型自己 look(look 文档第二部分,总方案第五部分)。
**以前。** 每一轮开始前服务端按库的大小定怎么给:小库塞详细摘要,大库先用模型挑表、挑不出就退回全部表名。这一步
完全不看这一轮要做什么——用户说一句「你好」,助手也先花几秒挑表,再带着上万字的表结构去生成;挑表模型照提示说
「不需要」,反而被记成失败、退回列出全部表名(look 文档 §1.3、§1.4)。
**现在。** system 里每个库一行概览(app/look/tables.py),工具清单不再列表名。模型要看表时输出一行 look,生成过程
(api/copilot_gen.py)读到就停下,由查看服务(app/look/service.py)一次给齐:点名直取;只给需求时候选不超过 12 张
全给,否则挑选模型按需求挑;补两跳以内的中间表;全部字段、数据目录、连接条件;遮罩列只给列名和类型。闲聊只调一次
模型,数据类请求一次查看到位。
**各入口看什么**(总方案 §5.3):画布、问数据看表和能力(问数据只列只读能力);发布前修复、升级证据看表,由系统按要修
的 SQL 代为查看(look 文档 D5);应用润色看工作流版本和能力(在 copilot.py 里组装)。
这里提供:look_service(按入口组装查看服务,开流之前调:读目录、读能力登记要用会话)、section(system 里数据源那一节)、
system_look(系统代为查看)、写 SQL 的硬性要求和「什么时候该看」的判断标准。
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from app.look.base import LookResult
from app.look.capabilities import CapabilityKind
from app.look.pick import PICK_MAX_TOKENS, Picker # noqa: F401 - copilot.py 按这个额度取挑选模型的规格
from app.look.service import LookService
from app.look.tables import TableKind, graph_tables
from app.providers.factory import ModelSpec
# ==========================================================================
# 写给模型的文字(不上界面)
# ==========================================================================
SQL_RULES_PROMPT = (
"\n\n写 SQL 的硬性要求:\n"
# 这三条都是真实踩过的:Oracle 上漏 schema 前缀直接 ORA-00942,
# 写惯 MySQL 的模型会顺手写 LIMIT,而一条没有行数限制的查询能拖垮生产库
"- 表名照抄查看结果里的全名(含 schema 前缀),漏掉前缀在 Oracle 上会直接报 ORA-00942\n"
"- 认准方言:Oracle 用 FETCH FIRST n ROWS ONLY,不是 LIMIT\n"
"- 一次只写一条语句;分号拼接会被拒\n"
# 真实踩过:列名是照着业务说法猜的,跑到查库那一步才「no such column」
"- 列名只照查看结果里列出来的写;没看过的表,动笔前先 look,别猜字段名。搭完自查会拿 SQL 里的列名去对结构,"
"对不上会打回来让你改\n"
"- 比率、增幅、占比这类派生计算进口径卡(metrics 节点);查询结果里原样的数,报告撰写节点(report)"
"可以直接用 [[v:Q1.r0.列名]] 引用。别让 llm 节点直接对数字做算术\n"
# 真实运行里报告表格的表头全是英文缩写的原始列名:[[table:]] 原样用查询结果的列名
"- 结果列用中文别名(AS 月份、AS 曝光量),报告里的表格表头、引用直接可读。按上面标的方言加标识符引号:"
"MySQL / MariaDB 用反引号(AS `月份`),PostgreSQL、Oracle、SQLite 用双引号(AS \"月份\"),不要用单引号"
"(那是字符串);只给最外层 SELECT 的结果列起别名,WHERE、JOIN、GROUP BY 里照旧写原列名\n"
)
#: 什么时候该看(look 文档 D4):画布、问数据共用
LOOK_WHEN_PROMPT = """\
什么时候该看:
· 闲聊、解释、改提示词、改流程结构、改不碰 SQL 的节点:不看
· 问数据:查库交给 agent 节点,它运行时会自己查表结构。只有从概览看不出该用哪个库、用户点了表名、用户说了口径时
才看一次,把看到的表名写进 agent 的提示词,运行时少走弯路
· 直接写 SQL 的取数节点、修改已有的 SQL:动笔前看,一次把要用的表都点上
· 用户说了一条数据事实:先看那张表的现有目录,再提目录修改
· 要看就先看,看完再规划,不要先搭节点再回头看;这次对话里已经看过的表直接点名看,不要只给需求"""
#: 系统代为查看时写进记录的需求(界面上显示)
SYSTEM_LOOK_FIX_NEED = "要修的工作流里,查库节点的 SQL 用到的表"
SYSTEM_LOOK_UPGRADE_NEED = "要升级的工作流里,查库节点的 SQL 用到的表"
async def look_service(sources: list[Any], *, spec: ModelSpec | None, graph: Mapping[str, Any] | None = None,
kinds: tuple[str, ...] = ("table", "capability"), answer: bool = False,
catalog_view: Any = None) -> LookService:
"""按入口组装这一轮的查看服务。读目录、读能力登记都用各自的短会话,开流前后都能调。
spec 是助手的模型规格:挑选模型第一次要用时才按它拿(关思考、额度按 look/pick.PICK_MAX_TOKENS)。graph 是现有工作流,概览里列出它查过的表。
answer:问数据(意图为回答),能力只列只读的。catalog_view:表详情里给哪一份数据目录(data/catalog_view.py),不给是当前目录。
"""
picker = Picker(spec)
made: list[Any] = []
if "table" in kinds:
made.append(await TableKind.load(sources, graph=graph, picker=picker, catalog_view=catalog_view))
if "capability" in kinds:
made.append(await CapabilityKind.load(picker=picker, read_only=answer))
return LookService(made)
async def section(looks: LookService) -> str:
"""system 里数据源那一节:各类对象的概览,有数据源时再加写 SQL 的硬性要求。什么都没有时是空串,不占提示词。"""
overview = await looks.overview()
if not overview:
return ""
tables = looks.kinds.get("table")
return "\n\n" + overview + (SQL_RULES_PROMPT if tables is not None and tables.plans else "")
async def system_look(looks: LookService, graph: Mapping[str, Any] | None, sources: list[Any], *,
need: str) -> LookResult | None:
"""系统代为查看(look 文档 D5):图里调用工具节点的 SQL 用到的表,点名查看,不调挑选模型。没有就是 None。
记录、文字和模型主动查看的完全一样,只是 by=system、不占模型的次数;模型仍然可以再看。
"""
targets = [{"kind": "table", "source": source, "name": table}
for source, tables in graph_tables(graph, sources).items() for table in tables]
if not targets or "table" not in looks.kinds:
return None
return await looks.look({"op": "look", "need": need, "targets": targets}, by="system")
__all__ = [
"LOOK_WHEN_PROMPT", "SQL_RULES_PROMPT", "SYSTEM_LOOK_FIX_NEED", "SYSTEM_LOOK_UPGRADE_NEED", "graph_tables",
"look_service", "section", "system_look",
]