From 9e456d053e2107f3017f625d433a8fbc31da7ba6 Mon Sep 17 00:00:00 2001 From: centdix Date: Mon, 15 Jun 2026 12:29:22 +0200 Subject: [PATCH] feat(ai-chat): wire llms.txt docs lookup tools into global chat mode Add list_docs_pages/read_docs_page to globalTools and a docs system-prompt section so the workspace assistant can answer product questions from windmill.dev docs and cite canonical URLs. Co-Authored-By: Claude Opus 4.8 (1M context) --- ai_evals/cases/global.yaml | 24 +++++++++++++++++++ .../copilot/chat/global/core.test.ts | 19 +++++++++++++++ .../components/copilot/chat/global/core.ts | 9 +++++++ 3 files changed, 52 insertions(+) diff --git a/ai_evals/cases/global.yaml b/ai_evals/cases/global.yaml index 28839b0594..50281778e0 100644 --- a/ai_evals/cases/global.yaml +++ b/ai_evals/cases/global.yaml @@ -870,3 +870,27 @@ judgeChecklist: - fetches the logs for the requested job id - explains the failure from the returned logs (connection refused to the upstream API) + +- id: global-test29-docs-lookup-scheduling + prompt: |- + How do I schedule a script to run every Monday at 9am in Windmill? + runtime: + maxTurns: 6 + validate: + draftCountExactly: 0 + toolExpect: + requiredToolsUsed: + - list_docs_pages + - read_docs_page + forbiddenToolsUsed: + - deploy_workspace_item + - delete_workspace_item + - write_script + # Answering a product-docs question produces no draft, so the global judge + # (which only sees the drafts artifact) would score it empty — validate the + # docs-lookup contract via tool use: browse the index first, then read a page. + skipJudge: true + judgeChecklist: + - lists the documentation pages before reading one + - reads the scheduling documentation page rather than answering from memory + - cites the canonical windmill.dev/docs scheduling URL in the answer diff --git a/frontend/src/lib/components/copilot/chat/global/core.test.ts b/frontend/src/lib/components/copilot/chat/global/core.test.ts index c3efe3f35d..db2475ee53 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.test.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.test.ts @@ -2020,6 +2020,25 @@ describe('session-only preview tools gating', () => { }) }) +describe('documentation lookup tools', () => { + it('exposes list_docs_pages and read_docs_page in both the session and non-session tool sets', () => { + // The docs tools are read-only references, not session-preview tools, so + // they must be present regardless of `sessionPreview`. + for (const sessionPreview of [false, true]) { + const names = globalToolsFor({ sessionPreview }).map((t) => t.def.function.name) + expect(names).toContain('list_docs_pages') + expect(names).toContain('read_docs_page') + } + }) + + it('instructs the model to look up product docs and cite the Source page URL', () => { + const content = prepareGlobalSystemMessage().content as string + expect(content).toContain('list_docs_pages') + expect(content).toContain('read_docs_page') + expect(content).toContain('Source page') + }) +}) + describe('prepareGlobalUserMessage', () => { it('injects the active editor reference without contents', () => { __resetUserDraftForTesting() diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index 3960368a6e..fa5736fa34 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -74,6 +74,7 @@ import { } from '../shared' import type { ContextElement } from '../context' import { getDatatableTools } from '../datatableTools' +import { listDocsPagesTool, readDocsPageTool } from '../docs/core' import { UserDraft, type UserDraftMeta } from '$lib/userDraft.svelte' import { emptySchema } from '$lib/utils' import { inferArgs } from '$lib/infer' @@ -639,6 +640,12 @@ Rules: : '' } +Documentation: +- To answer questions about Windmill product features, concepts, or syntax, look them up in the official docs with list_docs_pages and read_docs_page rather than relying on memory. +- Call list_docs_pages FIRST to see the full index of documentation pages with their titles, URLs and descriptions. Then call read_docs_page on the 1 to 3 most relevant pages. If read_docs_page returns a list of section headings instead of the full page, call it again with the same path and a \`section\` argument to read the relevant section. +- Base documentation answers ONLY on what you read. Do not invent features, flags, syntax, or behavior you did not see in the docs. +- When you cite a docs page, use the exact "Source page" URL printed at the top of each read_docs_page result — never reconstruct a URL from a link inside the page body. If the docs do not cover the question, say so rather than inventing an answer. + Flows: - read_workspace_item returns compact flow JSON. Inline script bodies appear as "inline_script.". - Use read_flow_module_code and set_flow_module_code for inline script bodies. @@ -1469,6 +1476,8 @@ export const globalTools: Tool<{}>[] = [ } }, createSearchHubScriptsTool(false), + listDocsPagesTool, + readDocsPageTool, { def: createToolDef( askUserQuestionSchema,