mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
feat(ai): prefer high-level tools over raw SQL when rendering charts
Add optional `rows` parameter to render_chart so AI can pass pre-fetched data arrays (e.g. from get_time_stats, member_stats) directly instead of rewriting SQL from scratch. Update chart capability prompts and all locale files to guide AI to check high-level tools first before falling back to SQL. Remove debug console.debug log from agent-core thinkingLevel resolution. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e71956ae3c
commit
56ad4a9578
7 files changed
+60
-35
No files matched your search
@@ -123,14 +123,6 @@ export async function runAgentCore(options: AgentCoreOptions): Promise<AgentCore
|
||||
})()
|
||||
|
||||
const finalThinkingLevel = resolvedThinkingLevel ?? (piModel.reasoning ? undefined : 'off')
|
||||
console.debug('[agent-core] thinkingLevel resolution:', {
|
||||
input: options.thinkingLevel,
|
||||
modelId: piModel.id,
|
||||
modelReasoning: effectiveModel.reasoning,
|
||||
isDefault,
|
||||
resolved: resolvedThinkingLevel,
|
||||
final: finalThinkingLevel,
|
||||
})
|
||||
|
||||
const coreAgent = new PiAgentCore({
|
||||
initialState: {
|
||||
|
||||
@@ -172,7 +172,7 @@ const ZH_PROMPT = `你是 ChatLab 绘图助手。本轮用户希望你在回答
|
||||
6. 工具失败、SQL 修正、schema 探索过程不要写进最终回答;最终只保留原生图表和简短结论,禁止用 chart1.png、chart2.png 等文件名引用图表。
|
||||
7. 调用 render_chart 时,ChatLab 会立即在该位置插入原生图表;需要图表就直接调用工具,不要写 SQL 注释、Markdown 图片或“已生成图表/见上图”这类文本占位。
|
||||
8. 用户要求多张图时,图表应分散出现在相关总结或解释附近;不要把所有图集中生成后再在最后用文字引用。
|
||||
9. 调查数据、筛选对象、确认统计口径时,优先使用可用的普通分析工具;render_chart 只用于最终要展示成图的聚合数据,不要把它用于元数据探测、标量查询、单行日期字符串或纯说明性结果。
|
||||
9. 为图表准备数据时,优先调用高层工具(如 get_time_stats、member_stats)而非自己写 SQL;只有高层工具无法覆盖所需筛选或聚合时才用 sql。render_chart 不要用于元数据探测、标量查询、单行日期字符串或纯说明性结果。
|
||||
10. render_chart 的工具结果会包含数据预览(Data preview);用它分析峰值、低谷和差异,不要为了读取数值重复生成等价图表。
|
||||
11. 图表解释只能描述数据直接支持的趋势、峰值、低谷、差异和同步性,禁止夸张拟人化或无法从图中验证的关系判断。
|
||||
|
||||
@@ -180,8 +180,8 @@ const ZH_PROMPT = `你是 ChatLab 绘图助手。本轮用户希望你在回答
|
||||
1. 调用 get_schema。
|
||||
2. 根据 schema、用户要求、系统当前日期和启动上下文确定统计对象、时间范围、维度、指标和图表类型。
|
||||
3. 如需调查事实、建立话题地图、筛选成员或确认统计口径,先用可用的普通分析工具完成;不要为了调查而生成临时图表。
|
||||
4. 准备图表数据集 SQL:SQL 结果必须直接对应 ChartSpec 需要的维度、指标和 series;必要时在这条数据集 SQL 中完成筛选、聚合和补零。
|
||||
5. 在希望图表出现的位置调用 render_chart,提交图表数据集 SQL 和 ChartSpec v1。
|
||||
4. 准备图表数据——先问自己:哪个可用的高层工具能直接提供这张图所需的数据?get_time_stats 能给时间分布、member_stats 能给成员排名;能用就调用,将 data 数组作为 rows 传入 render_chart。只有在高层工具无法覆盖所需筛选或聚合时,才编写 SQL。
|
||||
5. 在希望图表出现的位置调用 render_chart,提交数据(rows 或 sql)和 ChartSpec v1。
|
||||
6. 图表生成后,用 1-3 句简洁文字解释结论。用户要求多张图时,可以多次调用 render_chart,并把每张图放在最相关的段落附近。
|
||||
|
||||
常用语、口头禅、高频短句类图表必须排除非真人文本:发送者为系统消息、媒体占位(如 [表情包]、[图片]、[语音]、[视频]、[文件]、[链接]、[红包]、[转账])以及撤回/删除提示都不能进入统计。
|
||||
@@ -216,7 +216,7 @@ Hard rules:
|
||||
6. Do not include failed SQL attempts, schema exploration, or retry reasoning in the final answer. Final answer should contain only native charts and a short conclusion; never reference charts with filenames like chart1.png or chart2.png.
|
||||
7. When you call render_chart, ChatLab immediately inserts the native chart at that position. If a chart is needed, call the tool directly; do not write SQL-comment placeholders, Markdown images, or text such as "generated chart" or "see chart above".
|
||||
8. If the user asks for multiple charts, place each chart near the relevant summary or explanation. Do not generate all charts together and then reference them later with text.
|
||||
9. Use available ordinary analysis tools to investigate data, choose subjects, and confirm statistical definitions. Use render_chart only for aggregated data that should be displayed as a chart; do not use it for metadata inspection, scalar queries, single-row date strings, or purely explanatory results.
|
||||
9. When preparing chart data, prefer calling a high-level tool (such as get_time_stats, member_stats) over writing SQL; only use sql when no high-level tool can satisfy the required filters or aggregations. Do not use render_chart for metadata inspection, scalar queries, single-row date strings, or purely explanatory results.
|
||||
10. render_chart tool results include a Data preview; use it to identify peaks, lows, and differences. Do not generate equivalent charts again just to read values.
|
||||
11. Explain only trends, peaks, lows, differences, and synchrony directly supported by chart data. Do not make exaggerated personality or relationship claims.
|
||||
|
||||
@@ -224,8 +224,8 @@ Workflow:
|
||||
1. Call get_schema.
|
||||
2. Derive the target members, time range, dimensions, metrics, and chart type from schema, the user request, system current date, and startup context.
|
||||
3. If you need to investigate facts, build a topic map, select members, or confirm statistical definitions, use available ordinary analysis tools first; do not generate temporary charts for investigation.
|
||||
4. Prepare the chart dataset SQL: its result must directly match the dimensions, metrics, and series required by ChartSpec; when needed, do filtering, aggregation, and zero-filling in this dataset SQL.
|
||||
5. Call render_chart at the position where the chart should appear, with the chart dataset SQL and ChartSpec v1.
|
||||
4. Prepare chart data — ask yourself first: which available high-level tool can directly provide the data this chart needs? get_time_stats covers time distributions; member_stats covers member rankings. If one fits, call it and pass its data array as rows to render_chart. Only write SQL when no high-level tool can cover the required filters or aggregations.
|
||||
5. Call render_chart at the position where the chart should appear, with data (rows or sql) and ChartSpec v1.
|
||||
6. After the chart is generated, explain the key finding in 1-3 concise sentences. If the user asks for multiple charts, call render_chart multiple times and place each chart near its most relevant section.
|
||||
|
||||
For catchphrase, common phrase, or frequent short-text charts, exclude non-human text from the SQL: system-message senders, media placeholders such as [Sticker], [Image], [Photo], [Voice], [Video], [File], [Link], and recall/deletion notices must not be counted.
|
||||
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_time_stats: {
|
||||
desc: 'Get time distribution statistics of chat activity. Suitable for questions like "when is the group most active" or "what time do people usually chat".',
|
||||
desc: 'Get time distribution statistics of chat activity. Suitable for questions like "when is the group most active" or "what time do people usually chat". The returned `data` array can be passed directly as the `rows` parameter of render_chart for visualization.',
|
||||
params: {
|
||||
type: 'Statistics type: hourly (by hour), weekday (by day of week), daily (by date)',
|
||||
},
|
||||
@@ -257,9 +257,10 @@ Returned summaries are brief descriptions of each segment, helping quickly locat
|
||||
params: {},
|
||||
},
|
||||
render_chart: {
|
||||
desc: 'Generate a native ChatLab chart from read-only SQL and ChartSpec v1. Supports bar, line, pie, and heatmap. Do not output HTML, JavaScript, SVG, ECharts options, or rendering code.',
|
||||
desc: 'Generate a native ChatLab chart from ChartSpec v1. Supports bar, line, pie, and heatmap. Provide either `rows` (pre-fetched data array from a tool result, e.g. the `data` field from get_time_stats) or `sql` (read-only SELECT) — prefer `rows` when data is already available; use `sql` only when high-level tools cannot satisfy the need. Do not output HTML, JavaScript, SVG, ECharts options, or rendering code.',
|
||||
params: {
|
||||
sql: 'Read-only SELECT or WITH SELECT SQL. It must return fields referenced by ChartSpec encoding.',
|
||||
rows: 'Pre-fetched data array from a high-level tool result (e.g. the `data` field from get_time_stats). Prefer this over sql when data is already available. Mutually exclusive with sql.',
|
||||
sql: 'Read-only SELECT or WITH SELECT SQL. Use only when high-level tools cannot satisfy the need. Must return fields referenced by ChartSpec encoding.',
|
||||
params: 'Named SQL parameters. Use an empty object when no parameters are needed.',
|
||||
chartSpec: 'ChartSpec v1 with version, type, title, and encoding.',
|
||||
maxRows: 'Maximum chart query rows. Default 1000.',
|
||||
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_time_stats: {
|
||||
desc: 'グループチャットの時間分布統計を取得する。「いつが一番アクティブ?」「みんな何時にチャットしている?」などの質問に適している。',
|
||||
desc: 'グループチャットの時間分布統計を取得する。「いつが一番アクティブ?」「みんな何時にチャットしている?」などの質問に適している。返される `data` 配列は render_chart の `rows` パラメータとして直接使用できる。',
|
||||
params: {
|
||||
type: '統計タイプ:hourly(時間別)、weekday(曜日別)、daily(日別)',
|
||||
},
|
||||
@@ -257,9 +257,10 @@ export default {
|
||||
params: {},
|
||||
},
|
||||
render_chart: {
|
||||
desc: '読み取り専用 SQL と ChartSpec v1 から ChatLab ネイティブチャートを生成します。bar、line、pie、heatmap をサポートします。HTML、JavaScript、SVG、ECharts option、描画コードは出力しないでください。',
|
||||
desc: 'ChartSpec v1 から ChatLab ネイティブチャートを生成します。bar、line、pie、heatmap をサポートします。`rows`(高レベルツールのデータ配列、例:get_time_stats の `data` フィールド)または `sql`(読み取り専用 SELECT)のいずれかを指定します。データがすでに取得済みの場合は `rows` を優先し、高レベルツールで対応できない場合のみ `sql` を使用します。HTML、JavaScript、SVG、ECharts option、描画コードは出力しないでください。',
|
||||
params: {
|
||||
sql: '読み取り専用の SELECT または WITH SELECT SQL。ChartSpec encoding で参照するフィールドを返す必要があります。',
|
||||
rows: '高レベルツールから取得したデータ配列(例:get_time_stats の `data` フィールド)。データが取得済みの場合は sql より優先して使用します。sql とは排他。',
|
||||
sql: '読み取り専用の SELECT または WITH SELECT SQL。高レベルツールで対応できない場合のみ使用します。ChartSpec encoding で参照するフィールドを返す必要があります。',
|
||||
params: 'SQL の名前付きパラメータ。不要な場合は空オブジェクトを渡します。',
|
||||
chartSpec: 'version、type、title、encoding を含む ChartSpec v1。',
|
||||
maxRows: 'チャートクエリの最大行数。デフォルトは 1000。',
|
||||
|
||||
@@ -57,7 +57,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_time_stats: {
|
||||
desc: '获取群聊的时间分布统计。适用于回答"什么时候最活跃"、"大家一般几点聊天"等问题。',
|
||||
desc: '获取群聊的时间分布统计。适用于回答"什么时候最活跃"、"大家一般几点聊天"等问题。返回的 `data` 数组可直接作为 `render_chart` 的 `rows` 参数用于绘图。',
|
||||
params: {
|
||||
type: '统计类型:hourly(按小时)、weekday(按星期)、daily(按日期)',
|
||||
},
|
||||
@@ -248,9 +248,10 @@ export default {
|
||||
params: {},
|
||||
},
|
||||
render_chart: {
|
||||
desc: '根据只读 SQL 和 ChartSpec v1 生成 ChatLab 原生图表。支持 bar、line、pie、heatmap。禁止输出 HTML、JavaScript、SVG、ECharts option 或渲染代码。',
|
||||
desc: '根据 ChartSpec v1 生成 ChatLab 原生图表。支持 bar、line、pie、heatmap。提供 `rows`(来自高层工具的数据数组,如 get_time_stats 的 `data` 字段)或 `sql`(只读 SELECT)二选一;有现成数据时优先用 `rows`,只有高层工具无法满足时才写 `sql`。禁止输出 HTML、JavaScript、SVG、ECharts option 或渲染代码。',
|
||||
params: {
|
||||
sql: '只读 SELECT 或 WITH SELECT SQL。必须返回 ChartSpec encoding 中引用的字段。',
|
||||
rows: '来自高层工具返回的数据数组(如 get_time_stats 的 `data` 字段)。有现成数据时优先使用,与 sql 二选一。',
|
||||
sql: '只读 SELECT 或 WITH SELECT SQL。仅在高层工具无法满足需求时使用,必须返回 ChartSpec encoding 中引用的字段。',
|
||||
params: 'SQL 命名参数对象;不需要参数时传空对象。',
|
||||
chartSpec: 'ChartSpec v1,包含 version、type、title、encoding。',
|
||||
maxRows: '图表查询最大行数,默认 1000。',
|
||||
|
||||
@@ -57,7 +57,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_time_stats: {
|
||||
desc: '取得群聊的時間分佈統計。適用於回答「什麼時候最活躍」、「大家一般幾點聊天」等問題。',
|
||||
desc: '取得群聊的時間分佈統計。適用於回答「什麼時候最活躍」、「大家一般幾點聊天」等問題。返回的 `data` 陣列可直接作為 `render_chart` 的 `rows` 參數用於繪圖。',
|
||||
params: {
|
||||
type: '統計類型:hourly(按小時)、weekday(按星期)、daily(按日期)',
|
||||
},
|
||||
@@ -248,9 +248,10 @@ export default {
|
||||
params: {},
|
||||
},
|
||||
render_chart: {
|
||||
desc: '根據唯讀 SQL 和 ChartSpec v1 生成 ChatLab 原生圖表。支援 bar、line、pie、heatmap。禁止輸出 HTML、JavaScript、SVG、ECharts option 或渲染程式碼。',
|
||||
desc: '根據 ChartSpec v1 生成 ChatLab 原生圖表。支援 bar、line、pie、heatmap。提供 `rows`(來自高層工具的資料陣列,如 get_time_stats 的 `data` 欄位)或 `sql`(唯讀 SELECT)二擇一;有現成資料時優先用 `rows`,僅在高層工具無法滿足需求時才寫 `sql`。禁止輸出 HTML、JavaScript、SVG、ECharts option 或渲染程式碼。',
|
||||
params: {
|
||||
sql: '唯讀 SELECT 或 WITH SELECT SQL。必須返回 ChartSpec encoding 中引用的欄位。',
|
||||
rows: '來自高層工具返回的資料陣列(如 get_time_stats 的 `data` 欄位)。有現成資料時優先使用,與 sql 二擇一。',
|
||||
sql: '唯讀 SELECT 或 WITH SELECT SQL。僅在高層工具無法滿足需求時使用,必須返回 ChartSpec encoding 中引用的欄位。',
|
||||
params: 'SQL 命名參數物件;不需要參數時傳空物件。',
|
||||
chartSpec: 'ChartSpec v1,包含 version、type、title、encoding。',
|
||||
maxRows: '圖表查詢最大行數,預設 1000。',
|
||||
|
||||
@@ -15,13 +15,20 @@ const RE_SECONDS_TIMESTAMP_DIVIDED_AS_MILLISECONDS = /\b(?:\w+\.)?ts\s*\/\s*1000
|
||||
const inputSchema: JsonSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
rows: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Pre-fetched data rows to render directly, skipping SQL execution. Pass the data array from a high-level tool result (e.g. the `data` field from get_time_stats, or member activity rows). Prefer this over sql when data is already available. Mutually exclusive with sql.',
|
||||
items: { type: 'object' },
|
||||
},
|
||||
sql: {
|
||||
type: 'string',
|
||||
description: 'Read-only SELECT or WITH SELECT SQL used to produce chart rows.',
|
||||
description:
|
||||
'Read-only SELECT or WITH SELECT SQL used to produce chart rows. Use only when pre-fetched data is unavailable or custom aggregation is required.',
|
||||
},
|
||||
params: {
|
||||
type: 'object',
|
||||
description: 'Named SQL parameters. Use an empty object when no parameters are needed.',
|
||||
description: 'Named SQL parameters. Only used when sql is provided.',
|
||||
additionalProperties: true,
|
||||
default: {},
|
||||
},
|
||||
@@ -39,7 +46,7 @@ const inputSchema: JsonSchema = {
|
||||
maximum: 5000,
|
||||
},
|
||||
},
|
||||
required: ['sql', 'chartSpec'],
|
||||
required: ['chartSpec'],
|
||||
}
|
||||
|
||||
function normalizeSql(sql: unknown, maxRows: number): string {
|
||||
@@ -69,6 +76,15 @@ function normalizeParams(raw: unknown): Record<string, unknown> {
|
||||
return raw as Record<string, unknown>
|
||||
}
|
||||
|
||||
function normalizeRows(raw: unknown, maxRows: number): { rows: Record<string, unknown>[]; truncated: boolean } {
|
||||
if (!Array.isArray(raw)) throw new Error('rows must be an array')
|
||||
const items = raw.filter(
|
||||
(item): item is Record<string, unknown> => typeof item === 'object' && item !== null && !Array.isArray(item)
|
||||
)
|
||||
const truncated = items.length > maxRows
|
||||
return { rows: truncated ? items.slice(0, maxRows) : items, truncated }
|
||||
}
|
||||
|
||||
function normalizeMaxRows(raw: unknown): number {
|
||||
const value = typeof raw === 'number' ? raw : DEFAULT_MAX_ROWS
|
||||
if (!Number.isFinite(value)) return DEFAULT_MAX_ROWS
|
||||
@@ -103,13 +119,26 @@ function summarizeChartForModel(
|
||||
async function handler(params: Record<string, unknown>, context: ToolExecutionContext): Promise<ToolResult> {
|
||||
if (!context.dataProvider) throw new Error('render_chart requires a data provider')
|
||||
|
||||
const hasSql = typeof params.sql === 'string' && params.sql.trim().length > 0
|
||||
const hasRows = Array.isArray(params.rows)
|
||||
if (!hasSql && !hasRows) throw new Error('render_chart requires either sql or rows')
|
||||
|
||||
const maxRows = normalizeMaxRows(params.maxRows)
|
||||
const sql = normalizeSql(params.sql, maxRows)
|
||||
const sqlParams = normalizeParams(params.params)
|
||||
let rows: Record<string, unknown>[]
|
||||
let truncated: boolean
|
||||
|
||||
if (hasRows) {
|
||||
const normalized = normalizeRows(params.rows, maxRows)
|
||||
rows = normalized.rows
|
||||
truncated = normalized.truncated
|
||||
} else {
|
||||
const sql = normalizeSql(params.sql, maxRows)
|
||||
const sqlParams = normalizeParams(params.params)
|
||||
const fetchedRows = await context.dataProvider.executeParameterizedSql<Record<string, unknown>>(sql, sqlParams)
|
||||
truncated = fetchedRows.length > maxRows
|
||||
rows = truncated ? fetchedRows.slice(0, maxRows) : fetchedRows
|
||||
}
|
||||
|
||||
const fetchedRows = await context.dataProvider.executeParameterizedSql<Record<string, unknown>>(sql, sqlParams)
|
||||
const truncated = fetchedRows.length > maxRows
|
||||
const rows = truncated ? fetchedRows.slice(0, maxRows) : fetchedRows
|
||||
const chart = buildChartPayload(rows, params.chartSpec, { truncated })
|
||||
|
||||
return {
|
||||
@@ -127,7 +156,7 @@ async function handler(params: Record<string, unknown>, context: ToolExecutionCo
|
||||
export const renderChartTool: ToolDefinition = {
|
||||
name: 'render_chart',
|
||||
description:
|
||||
'Generate a native ChatLab chart from read-only SQL plus ChartSpec v1. Use this for flexible bar, line, pie, and heatmap charts. Never output HTML, JavaScript, SVG, ECharts options, or rendering code.',
|
||||
"Generate a native ChatLab chart from ChartSpec v1. Provide either `rows` (pre-fetched data array from a tool result such as get_time_stats or member_stats) or `sql` (read-only SELECT). Prefer `rows` when data is already available — pass the tool's data array directly. Use `sql` only for custom aggregations not covered by existing tools. Never output HTML, JavaScript, SVG, ECharts options, or rendering code.",
|
||||
inputSchema,
|
||||
handler,
|
||||
category: 'analysis',
|
||||
|
||||
Reference in new issue
Block a user