From b7f1a0aab81fd2265944e33ccff375818a3499bc Mon Sep 17 00:00:00 2001 From: Bob Jones <198214227+a1local@users.noreply.github.com> Date: Wed, 20 May 2026 14:09:34 +0800 Subject: [PATCH 1/2] Use generated docs in local circuit prompt --- .../create-local-circuit-prompt.ts | 37 +++++++---- .../create-local-circuit-prompt.test.ts | 64 +++++++++++++++++++ 2 files changed, 90 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..86a7bba 100644 --- a/lib/prompt-templates/create-local-circuit-prompt.ts +++ b/lib/prompt-templates/create-local-circuit-prompt.ts @@ -1,10 +1,17 @@ import { + fp, getFootprintNamesByType, getFootprintSizes, - fp, } from "@tscircuit/footprinter" -async function fetchFileContent(url: string): Promise { +const COMPONENT_TYPES_DOC_URL = + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md" +const GENERATED_AI_DOCS_URL = "https://docs.tscircuit.com/ai.txt" + +async function fetchFileContent( + url: string, + { optional = false }: { optional?: boolean } = {}, +): Promise { try { const response = await fetch(url) if (!response.ok) { @@ -14,11 +21,21 @@ async function fetchFileContent(url: string): Promise { } return await response.text() } catch (error) { + if (optional) return "" console.error("Error fetching file content:", error) throw error } } +function cleanMarkdownDoc(markdown: string) { + return markdown + .split("\n") + .filter((line) => !line.startsWith("#")) + .join("\n") + .replace(/\n\n+/g, "\n\n") + .trim() +} + export const createLocalCircuitPrompt = async () => { const footprintNamesByType = getFootprintNamesByType() const footprintSizes = getFootprintSizes() @@ -33,22 +50,20 @@ export const createLocalCircuitPrompt = async () => { "", ) - const propsDoc = - (await fetchFileContent( - "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md", - )) || "" + const [propsDoc, generatedAiDocs] = await Promise.all([ + fetchFileContent(COMPONENT_TYPES_DOC_URL), + fetchFileContent(GENERATED_AI_DOCS_URL, { optional: true }), + ]) - const cleanedPropsDoc = propsDoc - .split("\n") - .filter((line) => !line.startsWith("#")) - .join("\n") - .replace(/\n\n+/g, "\n\n") + const cleanedPropsDoc = cleanMarkdownDoc(propsDoc) + const cleanedGeneratedAiDocs = cleanMarkdownDoc(generatedAiDocs) 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 +${cleanedGeneratedAiDocs ? `## Auto-generated tscircuit docs\n\nThese generated docs reflect the current tscircuit APIs and examples:\n\n${cleanedGeneratedAiDocs}\n\n` : ""} ## 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..a8bbac4 --- /dev/null +++ b/tests/prompt-templates/create-local-circuit-prompt.test.ts @@ -0,0 +1,64 @@ +import { afterEach, describe, expect, it } from "bun:test" +import { createLocalCircuitPrompt } from "../../lib/prompt-templates/create-local-circuit-prompt" + +const originalFetch = globalThis.fetch + +afterEach(() => { + globalThis.fetch = originalFetch +}) + +describe("createLocalCircuitPrompt", () => { + it("includes generated AI docs in the system prompt", async () => { + const fetchCalls: string[] = [] + + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = String(input) + fetchCalls.push(url) + + if (url === "https://docs.tscircuit.com/ai.txt") { + return new Response( + "# AI docs\nUse for intentional removable connections.", + ) + } + + return new Response( + "# Component Types\n accepts resistance and footprint props.", + ) + }) as typeof fetch + + const prompt = await createLocalCircuitPrompt() + + expect(fetchCalls).toContain("https://docs.tscircuit.com/ai.txt") + expect(fetchCalls).toContain( + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md", + ) + expect(prompt).toContain("## Auto-generated tscircuit docs") + expect(prompt).toContain( + "Use for intentional removable connections.", + ) + expect(prompt).toContain( + " accepts resistance and footprint props.", + ) + }) + + it("falls back when generated AI docs cannot be fetched", async () => { + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = String(input) + + if (url === "https://docs.tscircuit.com/ai.txt") { + return new Response("not found", { + status: 404, + statusText: "Not Found", + }) + } + + return new Response("# Component Types\n is supported.") + }) as typeof fetch + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).not.toContain("## Auto-generated tscircuit docs") + expect(prompt).toContain(" is supported.") + expect(prompt).toContain("## tscircuit API overview") + }) +}) From bacf4004608e3f01ff36d52166c269175c9006fd Mon Sep 17 00:00:00 2001 From: Bob Jones <198214227+a1local@users.noreply.github.com> Date: Thu, 21 May 2026 11:25:24 +0800 Subject: [PATCH 2/2] Make OpenAI-backed tests deterministic --- .../run-ai-with-error-correction.ts | 5 +- lib/tscircuit-coder/tscircuitCoder.ts | 3 +- lib/utils/generate-random-prompts.ts | 3 +- tests/tscircuitCoder.test.ts | 59 +++++++++++++++++-- tests/utils/generate-random-prompts.test.ts | 24 +++++++- 5 files changed, 84 insertions(+), 10 deletions(-) diff --git a/lib/tscircuit-coder/run-ai-with-error-correction.ts b/lib/tscircuit-coder/run-ai-with-error-correction.ts index 16520cd..d631986 100644 --- a/lib/tscircuit-coder/run-ai-with-error-correction.ts +++ b/lib/tscircuit-coder/run-ai-with-error-correction.ts @@ -1,8 +1,8 @@ -import { askAiWithPreviousAttempts } from "../ask-ai/ask-ai-with-previous-attempts" +import { getPrimarySourceCodeFromVfs } from "lib/utils/get-primary-source-code-from-vfs" import { saveAttemptLog } from "lib/utils/save-attempt" import type OpenAI from "openai" +import { askAiWithPreviousAttempts } from "../ask-ai/ask-ai-with-previous-attempts" import { evaluateTscircuitCode } from "../utils/evaluate-tscircuit-code" -import { getPrimarySourceCodeFromVfs } from "lib/utils/get-primary-source-code-from-vfs" const createAttemptFile = ({ fileName, @@ -136,5 +136,6 @@ export const runAiWithErrorCorrection = async ({ promptNumber, previousAttempts, vfs, + openaiClient, }) } diff --git a/lib/tscircuit-coder/tscircuitCoder.ts b/lib/tscircuit-coder/tscircuitCoder.ts index 09cb2a4..15e1ae4 100644 --- a/lib/tscircuit-coder/tscircuitCoder.ts +++ b/lib/tscircuit-coder/tscircuitCoder.ts @@ -1,7 +1,7 @@ import { EventEmitter } from "node:events" +import { createLocalCircuitPrompt } from "lib/prompt-templates/create-local-circuit-prompt" import type { OpenAI } from "openai" import { runAiWithErrorCorrection } from "./run-ai-with-error-correction" -import { createLocalCircuitPrompt } from "lib/prompt-templates/create-local-circuit-prompt" export interface TscircuitCoderEvents { streamedChunk: string @@ -70,6 +70,7 @@ export class TscircuitCoderImpl extends EventEmitter implements TscircuitCoder { onStream, onVfsChanged, vfs: this.vfs, + openaiClient: this.openaiClient, }) if (result.code) { const filepath = `prompt-${promptNumber}-attempt-final.tsx` diff --git a/lib/utils/generate-random-prompts.ts b/lib/utils/generate-random-prompts.ts index d6cd984..f4ee171 100644 --- a/lib/utils/generate-random-prompts.ts +++ b/lib/utils/generate-random-prompts.ts @@ -2,8 +2,9 @@ import { openai } from "lib/ai/openai" export const generateRandomPrompts = async ( numberOfPrompts: number, + openaiClient: typeof openai = openai, ): Promise => { - const completion = await openai.chat.completions.create({ + const completion = await openaiClient.chat.completions.create({ model: "gpt-4o-mini", max_tokens: 2048, diff --git a/tests/tscircuitCoder.test.ts b/tests/tscircuitCoder.test.ts index d66c022..25b55b2 100644 --- a/tests/tscircuitCoder.test.ts +++ b/tests/tscircuitCoder.test.ts @@ -1,11 +1,62 @@ -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" +const circuitForPrompt = (prompt: string) => { + const includeTransistor = /transistor|tssop20/i.test(prompt) + const includeChip = /tssop20/i.test(prompt) + + return ` +export const GeneratedCircuit = () => ( + + + + + ${ + includeTransistor + ? '' + : "" + } + ${ + includeChip + ? '' + : "" + } + + + + +) +`.trim() +} + +const createMockOpenAi = () => ({ + chat: { + completions: { + create: async function* ({ + messages, + }: { messages: { content: string }[] }) { + const prompt = messages[1]?.content ?? "" + const response = `\`\`\`tsx\n${circuitForPrompt(prompt)}\n\`\`\`` + yield { choices: [{ delta: { content: response } }] } + }, + }, + }, +}) + test("TscircuitCoder submitPrompt streams and updates vfs", async () => { const streamedChunks: string[] = [] let vfsUpdated = false - const tscircuitCoder = createTscircuitCoder() + const tscircuitCoder = createTscircuitCoder(createMockOpenAi() as any) tscircuitCoder.on("streamedChunk", (chunk: string) => { streamedChunks.push(chunk) }) @@ -21,14 +72,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..480a9f4 100644 --- a/tests/utils/generate-random-prompts.test.ts +++ b/tests/utils/generate-random-prompts.test.ts @@ -1,9 +1,29 @@ -import { describe, it, expect } from "bun:test" +import { describe, expect, it } from "bun:test" import { generateRandomPrompts } from "../../lib/utils/generate-random-prompts" describe("generateRandomPrompts", () => { it("should return an array of prompts", async () => { - const prompts = await generateRandomPrompts(3) + const openaiClient = { + chat: { + completions: { + create: async () => ({ + choices: [ + { + message: { + content: [ + "1. Create a bridge rectifier with smoothing capacitor", + "2. Design a low-pass RC filter", + "3. Build a transistor switch for an LED", + ].join("\n"), + }, + }, + ], + }), + }, + }, + } + + const prompts = await generateRandomPrompts(3, openaiClient as any) expect(Array.isArray(prompts)).toBe(true) expect(prompts.length).toBe(3)