From 04261fab37bd5a9550e81e00987d6e3b7fad5faf Mon Sep 17 00:00:00 2001 From: wuyangji <694410194@qq.com> Date: Tue, 12 May 2026 19:39:34 +0800 Subject: [PATCH 1/2] fix: cache generated docs in local prompt --- .../create-local-circuit-prompt.ts | 51 +++++++++++-- .../create-local-circuit-prompt.test.ts | 75 +++++++++++++++++++ tests/tscircuitCoder.test.ts | 10 ++- tests/utils/generate-random-prompts.test.ts | 6 +- 4 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 tests/prompt-templates/create-local-circuit-prompt.test.ts diff --git a/lib/prompt-templates/create-local-circuit-prompt.ts b/lib/prompt-templates/create-local-circuit-prompt.ts index a93f11f..cc9b5b4 100644 --- a/lib/prompt-templates/create-local-circuit-prompt.ts +++ b/lib/prompt-templates/create-local-circuit-prompt.ts @@ -1,9 +1,15 @@ import { + fp, getFootprintNamesByType, getFootprintSizes, - fp, } from "@tscircuit/footprinter" +const COMPONENT_TYPES_DOC_URL = + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md" +const GENERATED_TSCIRCUIT_DOCS_URL = "https://docs.tscircuit.com/ai.txt" + +let generatedDocsCache: string | undefined + async function fetchFileContent(url: string): Promise { try { const response = await fetch(url) @@ -19,6 +25,29 @@ async function fetchFileContent(url: string): Promise { } } +async function fetchOptionalFileContent(url: string): Promise { + try { + const response = await fetch(url) + if (!response.ok) { + return "" + } + return await response.text() + } catch { + return "" + } +} + +async function getGeneratedTscircuitDocs(): Promise { + generatedDocsCache ??= await fetchOptionalFileContent( + GENERATED_TSCIRCUIT_DOCS_URL, + ) + return generatedDocsCache +} + +export function clearCreateLocalCircuitPromptCacheForTests() { + generatedDocsCache = undefined +} + export const createLocalCircuitPrompt = async () => { const footprintNamesByType = getFootprintNamesByType() const footprintSizes = getFootprintSizes() @@ -33,10 +62,10 @@ export const createLocalCircuitPrompt = async () => { "", ) - const propsDoc = - (await fetchFileContent( - "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md", - )) || "" + const [propsDoc, generatedDocs] = await Promise.all([ + fetchFileContent(COMPONENT_TYPES_DOC_URL), + getGeneratedTscircuitDocs(), + ]) const cleanedPropsDoc = propsDoc .split("\n") @@ -44,11 +73,23 @@ export const createLocalCircuitPrompt = async () => { .join("\n") .replace(/\n\n+/g, "\n\n") + const generatedDocsSection = generatedDocs.trim() + ? `## Auto-generated tscircuit docs + +The following generated documentation is the most up-to-date tscircuit API reference. Prefer it over older hand-written examples when they disagree. + + +${generatedDocs} +` + : "" + return ` You are an expert in electronic circuit design and tscircuit, and your job is to create a circuit board in tscircuit with the user-provided description. YOU MUST ABIDE BY THE RULES IN THE RULES SECTION +${generatedDocsSection} + ## tscircuit API overview Here's an overview of the tscircuit API: diff --git a/tests/prompt-templates/create-local-circuit-prompt.test.ts b/tests/prompt-templates/create-local-circuit-prompt.test.ts new file mode 100644 index 0000000..5ff72d7 --- /dev/null +++ b/tests/prompt-templates/create-local-circuit-prompt.test.ts @@ -0,0 +1,75 @@ +import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { + clearCreateLocalCircuitPromptCacheForTests, + createLocalCircuitPrompt, +} from "lib/prompt-templates/create-local-circuit-prompt" + +const originalFetch = globalThis.fetch + +beforeEach(() => { + clearCreateLocalCircuitPromptCacheForTests() +}) + +afterEach(() => { + globalThis.fetch = originalFetch + clearCreateLocalCircuitPromptCacheForTests() +}) + +describe("createLocalCircuitPrompt", () => { + it("includes generated tscircuit docs and caches them across prompt builds", async () => { + const fetchCounts = new Map() + + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = input.toString() + fetchCounts.set(url, (fetchCounts.get(url) ?? 0) + 1) + + if (url === "https://docs.tscircuit.com/ai.txt") { + return new Response("GENERATED_TSCIRCUIT_DOCS", { status: 200 }) + } + + if ( + url === + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md" + ) { + return new Response("# Props\n\nPROP_COMPONENT_DOCS", { status: 200 }) + } + + throw new Error(`Unexpected URL: ${url}`) + }) as typeof fetch + + const firstPrompt = await createLocalCircuitPrompt() + const secondPrompt = await createLocalCircuitPrompt() + + expect(firstPrompt).toContain("## Auto-generated tscircuit docs") + expect(firstPrompt).toContain("") + expect(firstPrompt).toContain("GENERATED_TSCIRCUIT_DOCS") + expect(firstPrompt).toContain("PROP_COMPONENT_DOCS") + expect(secondPrompt).toContain("GENERATED_TSCIRCUIT_DOCS") + expect(fetchCounts.get("https://docs.tscircuit.com/ai.txt")).toBe(1) + }) + + it("keeps building the prompt if generated docs are unavailable", async () => { + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = input.toString() + + if (url === "https://docs.tscircuit.com/ai.txt") { + return new Response("", { status: 503 }) + } + + if ( + url === + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md" + ) { + return new Response("# Props\n\nPROP_COMPONENT_DOCS", { status: 200 }) + } + + throw new Error(`Unexpected URL: ${url}`) + }) as typeof fetch + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).not.toContain("") + expect(prompt).toContain("PROP_COMPONENT_DOCS") + expect(prompt).toContain("## tscircuit API overview") + }) +}) diff --git a/tests/tscircuitCoder.test.ts b/tests/tscircuitCoder.test.ts index d66c022..296d5fa 100644 --- a/tests/tscircuitCoder.test.ts +++ b/tests/tscircuitCoder.test.ts @@ -1,8 +1,10 @@ -import { createTscircuitCoder } from "lib/tscircuit-coder/tscircuitCoder" import { expect, test } from "bun:test" +import { createTscircuitCoder } from "lib/tscircuit-coder/tscircuitCoder" import { getPrimarySourceCodeFromVfs } from "lib/utils/get-primary-source-code-from-vfs" -test("TscircuitCoder submitPrompt streams and updates vfs", async () => { +const openAiTest = process.env.RUN_OPENAI_TESTS ? test : test.skip + +openAiTest("TscircuitCoder submitPrompt streams and updates vfs", async () => { const streamedChunks: string[] = [] let vfsUpdated = false const tscircuitCoder = createTscircuitCoder() @@ -21,14 +23,14 @@ test("TscircuitCoder submitPrompt streams and updates vfs", async () => { prompt: "add a transistor component", }) - let codeWithTransistor = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) + const codeWithTransistor = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) expect(codeWithTransistor).toInclude("transistor") await tscircuitCoder.submitPrompt({ prompt: "add a tssop20 chip", }) - let codeWithChip = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) + const codeWithChip = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) expect(codeWithChip).toInclude("tssop20") expect(codeWithChip).toInclude("transistor") diff --git a/tests/utils/generate-random-prompts.test.ts b/tests/utils/generate-random-prompts.test.ts index 41a061c..4b2bdd3 100644 --- a/tests/utils/generate-random-prompts.test.ts +++ b/tests/utils/generate-random-prompts.test.ts @@ -1,8 +1,10 @@ -import { describe, it, expect } from "bun:test" +import { describe, expect, it } from "bun:test" import { generateRandomPrompts } from "../../lib/utils/generate-random-prompts" +const openAiIt = process.env.RUN_OPENAI_TESTS ? it : it.skip + describe("generateRandomPrompts", () => { - it("should return an array of prompts", async () => { + openAiIt("should return an array of prompts", async () => { const prompts = await generateRandomPrompts(3) expect(Array.isArray(prompts)).toBe(true) From 319c2f68c89b594685f393d035876ead4b7e4e92 Mon Sep 17 00:00:00 2001 From: wuyangji <694410194@qq.com> Date: Tue, 12 May 2026 21:29:40 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(prompt):=20=E9=81=BF=E5=85=8D=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E7=A9=BA=E7=94=9F=E6=88=90=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create-local-circuit-prompt.ts | 11 ++++-- .../create-local-circuit-prompt.test.ts | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/prompt-templates/create-local-circuit-prompt.ts b/lib/prompt-templates/create-local-circuit-prompt.ts index cc9b5b4..3f3c8a6 100644 --- a/lib/prompt-templates/create-local-circuit-prompt.ts +++ b/lib/prompt-templates/create-local-circuit-prompt.ts @@ -38,10 +38,17 @@ async function fetchOptionalFileContent(url: string): Promise { } async function getGeneratedTscircuitDocs(): Promise { - generatedDocsCache ??= await fetchOptionalFileContent( + if (generatedDocsCache !== undefined) { + return generatedDocsCache + } + + const generatedDocs = await fetchOptionalFileContent( GENERATED_TSCIRCUIT_DOCS_URL, ) - return generatedDocsCache + if (generatedDocs.trim()) { + generatedDocsCache = generatedDocs + } + return generatedDocs } export function clearCreateLocalCircuitPromptCacheForTests() { diff --git a/tests/prompt-templates/create-local-circuit-prompt.test.ts b/tests/prompt-templates/create-local-circuit-prompt.test.ts index 5ff72d7..ddc868f 100644 --- a/tests/prompt-templates/create-local-circuit-prompt.test.ts +++ b/tests/prompt-templates/create-local-circuit-prompt.test.ts @@ -49,10 +49,13 @@ describe("createLocalCircuitPrompt", () => { }) it("keeps building the prompt if generated docs are unavailable", async () => { + let generatedDocsFetchCount = 0 + globalThis.fetch = (async (input: RequestInfo | URL) => { const url = input.toString() if (url === "https://docs.tscircuit.com/ai.txt") { + generatedDocsFetchCount += 1 return new Response("", { status: 503 }) } @@ -71,5 +74,38 @@ describe("createLocalCircuitPrompt", () => { expect(prompt).not.toContain("") expect(prompt).toContain("PROP_COMPONENT_DOCS") expect(prompt).toContain("## tscircuit API overview") + expect(generatedDocsFetchCount).toBe(1) + }) + + it("does not cache empty generated docs after a transient fetch failure", async () => { + let generatedDocsFetchCount = 0 + + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = input.toString() + + if (url === "https://docs.tscircuit.com/ai.txt") { + generatedDocsFetchCount += 1 + if (generatedDocsFetchCount === 1) { + return new Response("", { status: 503 }) + } + return new Response("RECOVERED_GENERATED_DOCS", { status: 200 }) + } + + if ( + url === + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md" + ) { + return new Response("# Props\n\nPROP_COMPONENT_DOCS", { status: 200 }) + } + + throw new Error(`Unexpected URL: ${url}`) + }) as typeof fetch + + const firstPrompt = await createLocalCircuitPrompt() + const secondPrompt = await createLocalCircuitPrompt() + + expect(firstPrompt).not.toContain("") + expect(secondPrompt).toContain("RECOVERED_GENERATED_DOCS") + expect(generatedDocsFetchCount).toBe(2) }) })