From b6833b9f61de355c7223bc100035bd44144f35ee Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:58:33 +0800 Subject: [PATCH] fix(model): recognize named chat templates in summaries Count retained template content rather than only the default key; keep names-only inventories distinct. Fixes #6569 --- docs/model-artifacts.md | 4 +- src/domain/analysisView/modelArtifactView.ts | 6 +- tests/acceptance/modelArtifactCli.test.ts | 59 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/model-artifacts.md b/docs/model-artifacts.md index bd52410375..d3398e0ee5 100644 --- a/docs/model-artifacts.md +++ b/docs/model-artifacts.md @@ -79,7 +79,9 @@ GGUF supports zero extents and scalar shapes, with at most four dimensions; known quantized types still require complete blocks in the first dimension. MCP defaults to a compact summary with counts, selected short headline metadata, -chat-template presence and derived totals. The full type histogram and all rows +chat-template presence (default or named template content) and derived totals. +A names-only `tokenizer.chat_templates` inventory does not establish template +content. The full type histogram and all rows remain in complete Evidence. Headline metadata uses short keys/types and scalar values; long or compound values remain available through explicit metadata queries. Use the returned `parent_evidence_id`: diff --git a/src/domain/analysisView/modelArtifactView.ts b/src/domain/analysisView/modelArtifactView.ts index 1e75e2e07e..4fb33bba3f 100644 --- a/src/domain/analysisView/modelArtifactView.ts +++ b/src/domain/analysisView/modelArtifactView.ts @@ -100,7 +100,11 @@ const summarizeModel = ( }) => row, ), chat_template_present: metadata.some( - (row) => row.key === "tokenizer.chat_template", + (row) => + row.type === "STRING" && + typeof row.value === "string" && + (row.key === "tokenizer.chat_template" || + row.key.startsWith("tokenizer.chat_template.")), ), }; }; diff --git a/tests/acceptance/modelArtifactCli.test.ts b/tests/acceptance/modelArtifactCli.test.ts index ac2450fd6e..0b807b73f7 100644 --- a/tests/acceptance/modelArtifactCli.test.ts +++ b/tests/acceptance/modelArtifactCli.test.ts @@ -19,6 +19,65 @@ import { ggufFixture, safetensorsFixture } from "../fixtures/model/model.js"; import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; import { cliTest } from "../support/cli/cliFixture.js"; +it.each(["named", "default", "inventory-only", "absent"] as const)( + "reports actual chat-template presence in summaries and page context (%s)", + async (kind) => { + const root = await createTestTempDirectory("rea-model-templates-"); + const path = join(root, "model.gguf"); + const { text, u32, u64 } = ggufFixture(); + const names = { + key: "tokenizer.chat_templates", + type: 9, + value: Buffer.concat([u32(8), u64(1n), text("tool_use")]), + }; + const template = { + key: + kind === "default" + ? "tokenizer.chat_template" + : "tokenizer.chat_template.tool_use", + type: 8, + value: text("{{ messages }}"), + }; + const metadata = + kind === "named" + ? [names, template] + : kind === "default" + ? [template] + : kind === "inventory-only" + ? [names] + : []; + await writeFile(path, ggufFixture({ metadata }).bytes); + const { call } = await connectLocalToolsMcp(); + const response = await call("inspect_model_artifact", { path }); + expect(response.isError, JSON.stringify(response)).not.toBe(true); + const summary = modelArtifactSummaryViewResultSchema.parse( + parseEvidence(response.structuredContent).normalized_result, + ); + const present = kind === "named" || kind === "default"; + expect(summary.summary.chat_template_present).toBe(present); + const pageResponse = await call("inspect_analysis_view", { + source: { + kind: "retained-evidence", + evidence_id: summary.parent_evidence_id, + }, + view: { + kind: "model-artifact", + collection: "metadata", + fields: ["key", "value"], + }, + }); + expect(pageResponse.isError, JSON.stringify(pageResponse)).not.toBe(true); + const page = analysisViewResultSchema.parse( + parseEvidence(pageResponse.structuredContent).normalized_result, + ); + if (page.kind !== "model-artifact") throw new Error("Incorrect model view"); + expect(page.model.chat_template_present).toBe(present); + expect( + page.items.map((row) => ("key" in row ? row.key : undefined)), + ).toEqual(metadata.map((row) => row.key)); + }, +); + it("keeps generated headline keys and imported compound metadata out of compact summaries", async () => { const root = await createTestTempDirectory("rea-model-headlines-"); const path = join(root, "model");