diff --git a/bun.lock b/bun.lock index 4d940483..298825d8 100644 --- a/bun.lock +++ b/bun.lock @@ -5,15 +5,12 @@ "dependencies": { "@ai-sdk/anthropic": "^3.0.44", "ai": "^6.0.86", - "smithers-orchestrator": "^0.6.0", + "smithers-orchestrator": "^0.9.0", "takopi-smithers": "github:evmts/takopi-smithers", "zod": "^4.3.6", }, }, }, - "patchedDependencies": { - "smithers-orchestrator@0.6.0": "patches/smithers-orchestrator@0.6.0.patch", - }, "packages": { "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.44", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ke1NldgohWJ7sWLqm9Um9TVIOrtg8Y8AecWeB6PgaLt+paTPisAsyNfe8FNOVusuv58ugLBqY/78AkhUmbjXHA=="], @@ -101,6 +98,12 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -133,6 +136,8 @@ "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], @@ -303,7 +308,7 @@ "smithers": ["smithers@0.5.4", "", { "dependencies": { "axios": "^0.21.1" } }, "sha512-mGCYAcVTbkiZSCGorG6pznPncdVBdhc8Z2tA7J6P+Gl2Wk5vB38/gAEdsBNyp8DjDNuw8PdgCnH+Z3JzaqyEcA=="], - "smithers-orchestrator": ["smithers-orchestrator@0.6.0", "", { "dependencies": { "@ai-sdk/anthropic": "^3.0.36", "@mdx-js/esbuild": "^3.1.1", "ai": "^6.0.69", "diff": "^5.2.0", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", "react": "^19.2.4", "react-dom": "^19.2.4", "react-reconciler": "^0.31.0", "zod": "^4.3.6" }, "peerDependencies": { "typescript": "^5" }, "bin": { "smithers": "src/cli/index.ts" } }, "sha512-u4Gc3AK2yYXWeS2hrijzXnLN/d4iewJE8zkOrcLSElDaZSyG27QcabcTAV24tlJAjmR/vkggsTKSEw76OyE0rA=="], + "smithers-orchestrator": ["smithers-orchestrator@0.9.1", "", { "dependencies": { "@ai-sdk/anthropic": "^3.0.36", "@mdx-js/esbuild": "^3.1.1", "@types/react-dom": "^19.2.3", "@types/react-reconciler": "^0.28.9", "ai": "^6.0.69", "diff": "^5.2.0", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", "react": "^19.2.4", "react-dom": "^19.2.4", "react-reconciler": "^0.31.0", "zod": "^4.3.6" }, "peerDependencies": { "typescript": "^5" }, "bin": { "smithers": "src/cli/index.ts" } }, "sha512-36tHyIEW5mxnBwxrCsGk/5pKpwtdPHAiXS+9KKV3NF7RZtrP03v9RHEvg9ShlK+krsSa40OjW29smDgBGg1iPQ=="], "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], diff --git a/ingesters/__tests__/CairoSkillsIngester.test.ts b/ingesters/__tests__/CairoSkillsIngester.test.ts new file mode 100644 index 00000000..70c9722c --- /dev/null +++ b/ingesters/__tests__/CairoSkillsIngester.test.ts @@ -0,0 +1,448 @@ +import axios from 'axios'; +import { afterEach, describe, expect, it, vi } from 'bun:test'; +import { + CairoSkillsIngester, + parseFrontmatter, + parseGitHubUrl, +} from '../src/ingesters/CairoSkillsIngester'; +import { DocumentSource } from '../src/types'; +import { type BookPageDto } from '../src/utils/types'; + +type SkillConfig = { + id: string; + url: string; +}; + +class TestCairoSkillsIngester extends CairoSkillsIngester { + public setSkills(skills: SkillConfig[]): void { + this.skills = skills; + } + + public exposedDownloadAndExtractDocs(): Promise { + return this.downloadAndExtractDocs(); + } + + public exposedCreateChunks( + pages: BookPageDto[], + ): ReturnType { + return this.createChunks(pages); + } + + public exposedCleanupDownloadedFiles(): Promise { + return this.cleanupDownloadedFiles(); + } +} + +const toBase64 = (value: string): string => + Buffer.from(value).toString('base64'); + +describe('CairoSkillsIngester', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('parseGitHubUrl', () => { + it('parses /tree/ directory URLs', () => { + expect( + parseGitHubUrl( + 'https://github.com/feltroidprime/cairo-skills/tree/main/skills/benchmarking-cairo', + ), + ).toEqual({ + owner: 'feltroidprime', + repo: 'cairo-skills', + ref: 'main', + path: 'skills/benchmarking-cairo', + isFile: false, + }); + }); + + it('parses /blob/ URLs with commit refs', () => { + expect( + parseGitHubUrl( + 'https://github.com/keep-starknet-strange/starknet-agentic/blob/3454ca28ac6bd34eda96dc2faa43d89a5126e93c/skills/starknet-defi/SKILL.md', + ), + ).toEqual({ + owner: 'keep-starknet-strange', + repo: 'starknet-agentic', + ref: '3454ca28ac6bd34eda96dc2faa43d89a5126e93c', + path: 'skills/starknet-defi/SKILL.md', + isFile: true, + }); + }); + + it('throws on unsupported urls', () => { + expect(() => parseGitHubUrl('https://example.com/not-github')).toThrow(); + }); + }); + + describe('parseFrontmatter', () => { + it('extracts YAML frontmatter fields and strips the block from content', () => { + const markdown = `--- +name: "Test Skill" +description: "A test description" +keywords: [foo, bar] +--- +# Hello +World`; + + const parsed = parseFrontmatter(markdown); + + expect(parsed.frontmatter).toEqual({ + name: 'Test Skill', + description: 'A test description', + keywords: ['foo', 'bar'], + }); + expect(parsed.content).toBe('# Hello\nWorld'); + }); + + it('returns original content and empty frontmatter without markers', () => { + const markdown = '# No frontmatter\n\nBody'; + const parsed = parseFrontmatter(markdown); + + expect(parsed.frontmatter).toEqual({}); + expect(parsed.content).toBe(markdown); + }); + }); + + describe('downloadAndExtractDocs', () => { + it('returns one page per skill and concatenates directory markdown with SKILL.md first', async () => { + const ingester = new TestCairoSkillsIngester(); + ingester.setSkills([ + { + id: 'dir-skill', + url: 'https://github.com/acme/skills/tree/main/skills/dir-skill', + }, + { + id: 'file-skill', + url: 'https://github.com/acme/skills/blob/1234abcd/skills/file-skill/SKILL.md', + }, + ]); + + vi.spyOn(axios, 'get').mockImplementation(async (url: string | URL) => { + const key = String(url); + const responses: Record = { + 'https://api.github.com/repos/acme/skills/contents/skills/dir-skill?ref=main': + [ + { + type: 'file', + name: 'README.md', + path: 'skills/dir-skill/README.md', + }, + { + type: 'file', + name: 'SKILL.md', + path: 'skills/dir-skill/SKILL.md', + }, + { + type: 'dir', + name: 'extras', + path: 'skills/dir-skill/extras', + }, + ], + 'https://api.github.com/repos/acme/skills/contents/skills/dir-skill/extras?ref=main': + [ + { + type: 'file', + name: 'notes.md', + path: 'skills/dir-skill/extras/notes.md', + }, + ], + 'https://api.github.com/repos/acme/skills/contents/skills/dir-skill/SKILL.md?ref=main': + { + type: 'file', + name: 'SKILL.md', + path: 'skills/dir-skill/SKILL.md', + encoding: 'base64', + content: toBase64('# Skill Main\nMain content'), + }, + 'https://api.github.com/repos/acme/skills/contents/skills/dir-skill/extras/notes.md?ref=main': + { + type: 'file', + name: 'notes.md', + path: 'skills/dir-skill/extras/notes.md', + encoding: 'base64', + content: toBase64('# Notes\nExtra content'), + }, + 'https://api.github.com/repos/acme/skills/contents/skills/dir-skill/README.md?ref=main': + { + type: 'file', + name: 'README.md', + path: 'skills/dir-skill/README.md', + encoding: 'base64', + content: toBase64('# Readme\nReadme content'), + }, + 'https://api.github.com/repos/acme/skills/contents/skills/file-skill/SKILL.md?ref=1234abcd': + { + type: 'file', + name: 'SKILL.md', + path: 'skills/file-skill/SKILL.md', + encoding: 'base64', + content: toBase64('# Single File\nOnly content'), + }, + }; + + if (!(key in responses)) { + throw new Error(`Unexpected URL in test: ${key}`); + } + + return { data: responses[key] } as any; + }); + + const pages = await ingester.exposedDownloadAndExtractDocs(); + + expect(pages).toHaveLength(2); + expect(pages.map((page) => page.name).sort()).toEqual([ + 'dir-skill', + 'file-skill', + ]); + + const directoryPage = pages.find((page) => page.name === 'dir-skill'); + expect(directoryPage).toBeDefined(); + expect( + directoryPage?.content.startsWith('# Skill Main\nMain content'), + ).toBe(true); + }); + + it('fetches markdown files in parallel while preserving order', async () => { + const ingester = new TestCairoSkillsIngester(); + ingester.setSkills([ + { + id: 'parallel-skill', + url: 'https://github.com/acme/skills/tree/main/skills/parallel-skill', + }, + ]); + + let inFlightRequests = 0; + let maxInFlightRequests = 0; + const contentByPath: Record = { + 'skills/parallel-skill/SKILL.md': '# Skill Main\nMain content', + 'skills/parallel-skill/README.md': '# Readme\nReadme content', + 'skills/parallel-skill/notes.md': '# Notes\nNotes content', + }; + + vi.spyOn(axios, 'get').mockImplementation(async (url: string | URL) => { + const key = String(url); + + if ( + key === + 'https://api.github.com/repos/acme/skills/contents/skills/parallel-skill?ref=main' + ) { + return { + data: [ + { + type: 'file', + name: 'README.md', + path: 'skills/parallel-skill/README.md', + }, + { + type: 'file', + name: 'SKILL.md', + path: 'skills/parallel-skill/SKILL.md', + }, + { + type: 'file', + name: 'notes.md', + path: 'skills/parallel-skill/notes.md', + }, + ], + } as any; + } + + const pathMatch = key.match( + /^https:\/\/api\.github\.com\/repos\/acme\/skills\/contents\/(.+)\?ref=main$/, + ); + if (!pathMatch || !pathMatch[1]) { + throw new Error(`Unexpected URL in test: ${key}`); + } + + const path = decodeURIComponent(pathMatch[1]); + const fileContent = contentByPath[path]; + if (!fileContent) { + throw new Error(`Unexpected file path in test: ${path}`); + } + + inFlightRequests += 1; + maxInFlightRequests = Math.max(maxInFlightRequests, inFlightRequests); + await new Promise((resolve) => setTimeout(resolve, 10)); + inFlightRequests -= 1; + + return { + data: { + type: 'file', + name: path.split('/').pop(), + path, + encoding: 'base64', + content: toBase64(fileContent), + }, + } as any; + }); + + const pages = await ingester.exposedDownloadAndExtractDocs(); + + expect(pages).toHaveLength(1); + expect(maxInFlightRequests).toBeGreaterThan(1); + expect(pages[0]?.content).toBe( + [ + '# Skill Main\nMain content', + '# Notes\nNotes content', + '# Readme\nReadme content', + ].join('\n\n'), + ); + }); + + it('stops recursive directory traversal when maximum depth is exhausted', async () => { + const ingester = new TestCairoSkillsIngester(); + ingester.setSkills([ + { + id: 'deep-skill', + url: 'https://github.com/acme/skills/tree/main/skills/deep-skill', + }, + ]); + + const deepPathPrefix = 'skills/deep-skill'; + const tooDeepPath = `${deepPathPrefix}/level-1/level-2/level-3/level-4/level-5/level-6`; + + vi.spyOn(axios, 'get').mockImplementation(async (url: string | URL) => { + const key = String(url); + const responses: Record = { + [`https://api.github.com/repos/acme/skills/contents/${deepPathPrefix}?ref=main`]: + [ + { + type: 'file', + name: 'SKILL.md', + path: `${deepPathPrefix}/SKILL.md`, + }, + { + type: 'dir', + name: 'level-1', + path: `${deepPathPrefix}/level-1`, + }, + ], + [`https://api.github.com/repos/acme/skills/contents/${deepPathPrefix}/level-1?ref=main`]: + [ + { + type: 'dir', + name: 'level-2', + path: `${deepPathPrefix}/level-1/level-2`, + }, + ], + [`https://api.github.com/repos/acme/skills/contents/${deepPathPrefix}/level-1/level-2?ref=main`]: + [ + { + type: 'dir', + name: 'level-3', + path: `${deepPathPrefix}/level-1/level-2/level-3`, + }, + ], + [`https://api.github.com/repos/acme/skills/contents/${deepPathPrefix}/level-1/level-2/level-3?ref=main`]: + [ + { + type: 'dir', + name: 'level-4', + path: `${deepPathPrefix}/level-1/level-2/level-3/level-4`, + }, + ], + [`https://api.github.com/repos/acme/skills/contents/${deepPathPrefix}/level-1/level-2/level-3/level-4?ref=main`]: + [ + { + type: 'dir', + name: 'level-5', + path: `${deepPathPrefix}/level-1/level-2/level-3/level-4/level-5`, + }, + ], + [`https://api.github.com/repos/acme/skills/contents/${deepPathPrefix}/level-1/level-2/level-3/level-4/level-5?ref=main`]: + [ + { + type: 'dir', + name: 'level-6', + path: tooDeepPath, + }, + ], + [`https://api.github.com/repos/acme/skills/contents/${deepPathPrefix}/SKILL.md?ref=main`]: + { + type: 'file', + name: 'SKILL.md', + path: `${deepPathPrefix}/SKILL.md`, + encoding: 'base64', + content: toBase64('# Root Skill\nTop level only'), + }, + }; + + if (!(key in responses)) { + throw new Error(`Unexpected URL in test: ${key}`); + } + + return { data: responses[key] } as any; + }); + + const pages = await ingester.exposedDownloadAndExtractDocs(); + + expect(pages).toHaveLength(1); + expect(pages[0]?.name).toBe('deep-skill'); + expect(pages[0]?.content).toContain('# Root Skill\nTop level only'); + expect(pages[0]?.content).not.toContain('level-6'); + expect(axios.get).not.toHaveBeenCalledWith( + `https://api.github.com/repos/acme/skills/contents/${tooDeepPath}?ref=main`, + expect.anything(), + ); + }); + }); + + describe('createChunks', () => { + it('creates search chunks and one full-doc row per skill with expected metadata', async () => { + const ingester = new TestCairoSkillsIngester(); + ingester.setSkills([ + { + id: 'skill-a', + url: 'https://github.com/acme/skills/tree/main/skills/skill-a', + }, + ]); + + const markdown = `--- +name: "Skill A" +description: "A concise summary" +keywords: [cairo, testing] +--- +# Intro +Line one. + +## Details +Line two.`; + + const chunks = await ingester.exposedCreateChunks([ + { + name: 'skill-a', + content: markdown, + }, + ]); + + const searchChunks = chunks.filter( + (chunk) => chunk.metadata.chunkNumber >= 0, + ); + expect(searchChunks.length).toBeGreaterThan(0); + for (const chunk of searchChunks) { + expect(chunk.metadata.source).toBe(DocumentSource.CAIRO_SKILLS); + expect(chunk.metadata.skillId).toBe('skill-a'); + expect(chunk.metadata.chunkNumber).toBeGreaterThanOrEqual(0); + expect(chunk.metadata.uniqueId).toMatch(/^skill-skill-a-\d+$/); + } + + const fullDocRows = chunks.filter( + (chunk) => chunk.metadata.chunkNumber === -1, + ); + expect(fullDocRows).toHaveLength(1); + expect(fullDocRows[0]?.metadata.uniqueId).toBe('skill-skill-a-full'); + expect(fullDocRows[0]?.pageContent).toBe('Skill A: A concise summary'); + expect(fullDocRows[0]?.pageContent.includes('# Intro')).toBe(false); + expect(fullDocRows[0]?.metadata.fullContent).toBe(markdown); + }); + }); + + describe('cleanupDownloadedFiles', () => { + it('does not throw', async () => { + const ingester = new TestCairoSkillsIngester(); + await ingester.exposedCleanupDownloadedFiles(); + expect(true).toBe(true); + }); + }); +}); diff --git a/ingesters/__tests__/IngesterFactory.test.ts b/ingesters/__tests__/IngesterFactory.test.ts index 487e3c1d..499f4fcb 100644 --- a/ingesters/__tests__/IngesterFactory.test.ts +++ b/ingesters/__tests__/IngesterFactory.test.ts @@ -5,6 +5,7 @@ import { StarknetDocsIngester } from '../src/ingesters/StarknetDocsIngester'; import { StarknetFoundryIngester } from '../src/ingesters/StarknetFoundryIngester'; import { CairoByExampleIngester } from '../src/ingesters/CairoByExampleIngester'; import { OpenZeppelinDocsIngester } from '../src/ingesters/OpenZeppelinDocsIngester'; +import { CairoSkillsIngester } from '../src/ingesters/CairoSkillsIngester'; import { BaseIngester } from '../src/BaseIngester'; import { DocumentSource } from '../src/types'; @@ -54,6 +55,14 @@ describe('IngesterFactory', () => { expect(ingester).toBeInstanceOf(OpenZeppelinDocsIngester); }); + it('should create a CairoSkillsIngester for cairo_skills source', () => { + const ingester = IngesterFactory.createIngester( + DocumentSource.CAIRO_SKILLS, + ); + + expect(ingester).toBeInstanceOf(CairoSkillsIngester); + }); + it('should throw an error for an unknown source', () => { expect(() => { // @ts-ignore - Testing with invalid source @@ -77,6 +86,7 @@ describe('IngesterFactory', () => { DocumentSource.STARKNET_JS, DocumentSource.STARKNET_BLOG, DocumentSource.DOJO_DOCS, + DocumentSource.CAIRO_SKILLS, ]); }); }); diff --git a/ingesters/__tests__/skillsConfig.test.ts b/ingesters/__tests__/skillsConfig.test.ts new file mode 100644 index 00000000..fdc1e215 --- /dev/null +++ b/ingesters/__tests__/skillsConfig.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'bun:test'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +type SkillConfig = { + id: string; + url: string; +}; + +type SkillsConfigFile = { + skills: SkillConfig[]; +}; + +const skillsConfigPath = join(import.meta.dir, '..', 'config', 'skills.json'); + +describe('skills config', () => { + it('should contain at least one skill with valid structure', () => { + const raw = readFileSync(skillsConfigPath, 'utf8'); + const parsed = JSON.parse(raw) as SkillsConfigFile; + + expect(Array.isArray(parsed.skills)).toBe(true); + expect(parsed.skills.length).toBeGreaterThan(0); + + for (const skill of parsed.skills) { + expect(typeof skill.id).toBe('string'); + expect(skill.id.length).toBeGreaterThan(0); + expect(typeof skill.url).toBe('string'); + + const url = new URL(skill.url); + expect(url.protocol).toBe('https:'); + expect(url.hostname).toBe('github.com'); + } + }); + + it('should have unique skill ids', () => { + const raw = readFileSync(skillsConfigPath, 'utf8'); + const parsed = JSON.parse(raw) as SkillsConfigFile; + + const ids = parsed.skills.map((skill) => skill.id); + expect(new Set(ids).size).toBe(ids.length); + }); + + it('should use /tree/ or /blob/ GitHub URL formats', () => { + const raw = readFileSync(skillsConfigPath, 'utf8'); + const parsed = JSON.parse(raw) as SkillsConfigFile; + + for (const skill of parsed.skills) { + expect(skill.url).toMatch(/\/(tree|blob)\//); + } + }); +}); diff --git a/ingesters/config/skills.json b/ingesters/config/skills.json new file mode 100644 index 00000000..0ca453a5 --- /dev/null +++ b/ingesters/config/skills.json @@ -0,0 +1,20 @@ +{ + "skills": [ + { + "id": "benchmarking-cairo", + "url": "https://github.com/feltroidprime/cairo-skills/tree/main/skills/benchmarking-cairo" + }, + { + "id": "cairo-coding", + "url": "https://github.com/feltroidprime/cairo-skills/tree/main/skills/cairo-coding" + }, + { + "id": "avnu", + "url": "https://github.com/avnu-labs/avnu-skill/tree/main/skills/avnu" + }, + { + "id": "starknet-defi", + "url": "https://github.com/keep-starknet-strange/starknet-agentic/blob/3454ca28ac6bd34eda96dc2faa43d89a5126e93c/skills/starknet-defi/SKILL.md" + } + ] +} diff --git a/ingesters/config/sources.json b/ingesters/config/sources.json index af4127a1..70581b2e 100644 --- a/ingesters/config/sources.json +++ b/ingesters/config/sources.json @@ -150,6 +150,21 @@ "urlSuffix": "", "useUrlMapping": true } + }, + "cairo_skills": { + "name": "Cairo Skills", + "description": "Curated Cairo ecosystem skills for all-or-nothing retrieval", + "ingesterClass": "SkillsIngester", + "config": { + "repoOwner": "", + "repoName": "", + "fileExtensions": [".md"], + "chunkSize": 4096, + "chunkOverlap": 512, + "baseUrl": "", + "urlSuffix": "", + "useUrlMapping": false + } } } } diff --git a/ingesters/src/IngesterFactory.ts b/ingesters/src/IngesterFactory.ts index 3305dd3d..64ef5a2e 100644 --- a/ingesters/src/IngesterFactory.ts +++ b/ingesters/src/IngesterFactory.ts @@ -10,6 +10,7 @@ import { ScarbDocsIngester } from './ingesters/ScarbDocsIngester'; import { StarknetJSIngester } from './ingesters/StarknetJSIngester'; import { StarknetBlogIngester } from './ingesters/StarknetBlogIngester'; import { DojoDocsIngester } from './ingesters/DojoDocsIngester'; +import { SkillsIngester } from './ingesters/SkillsIngester'; import { getAvailableSourcesFromConfig, getSourceConfig, @@ -33,6 +34,7 @@ const INGESTER_CLASSES: Record BaseIngester> = { StarknetJSIngester, StarknetBlogIngester, DojoDocsIngester, + SkillsIngester, }; /** diff --git a/ingesters/src/__tests__/IngesterFactory.skills.test.ts b/ingesters/src/__tests__/IngesterFactory.skills.test.ts new file mode 100644 index 00000000..44997cfa --- /dev/null +++ b/ingesters/src/__tests__/IngesterFactory.skills.test.ts @@ -0,0 +1,31 @@ +import { IngesterFactory } from '../IngesterFactory'; +import { DocumentSource } from '../types'; +import { SkillsIngester } from '../ingesters/SkillsIngester'; +import { getSourceConfig } from '../utils/sourceConfig'; + +describe('IngesterFactory cairo_skills wiring', () => { + it('loads cairo_skills source config with SkillsIngester metadata', () => { + const sourceConfig = getSourceConfig(DocumentSource.CAIRO_SKILLS); + + expect(sourceConfig.name).toBe('Cairo Skills'); + expect(sourceConfig.ingesterClass).toBe('SkillsIngester'); + expect(sourceConfig.config).toEqual({ + repoOwner: '', + repoName: '', + fileExtensions: ['.md'], + chunkSize: 4096, + chunkOverlap: 512, + baseUrl: '', + urlSuffix: '', + useUrlMapping: false, + }); + }); + + it('creates a SkillsIngester for cairo_skills', () => { + const ingester = IngesterFactory.createIngester( + DocumentSource.CAIRO_SKILLS, + ); + + expect(ingester).toBeInstanceOf(SkillsIngester); + }); +}); diff --git a/ingesters/src/ingesters/CairoSkillsIngester.ts b/ingesters/src/ingesters/CairoSkillsIngester.ts new file mode 100644 index 00000000..79da0554 --- /dev/null +++ b/ingesters/src/ingesters/CairoSkillsIngester.ts @@ -0,0 +1,530 @@ +import * as fs from 'fs'; +import * as nodePath from 'path'; +import axios from 'axios'; +import { Document } from '@langchain/core/documents'; +import { BaseIngester } from '../BaseIngester'; +import { type BookChunk, DocumentSource } from '../types'; +import { + addSectionWithSizeLimit, + calculateHash, + isInsideCodeBlock, +} from '../utils/contentUtils'; +import { logger } from '../utils/logger'; +import { getRepoPath } from '../utils/paths'; +import { + type BookConfig, + type BookPageDto, + type ParsedSection, +} from '../utils/types'; + +type SkillConfig = { + id: string; + url: string; +}; + +type ParsedGitHubUrl = { + owner: string; + repo: string; + ref: string; + path: string; + isFile: boolean; +}; + +type SkillFrontmatter = { + name?: string; + description?: string; + keywords?: string[]; +}; + +type FrontmatterResult = { + frontmatter: SkillFrontmatter; + content: string; +}; + +type GitHubEntry = { + type: 'file' | 'dir'; + name: string; + path: string; +}; + +type GitHubFileResponse = { + type: 'file'; + name: string; + path: string; + content: string; + encoding: string; +}; + +const GITHUB_HOST = 'github.com'; +const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---\s*\n?/; +const MAX_MARKDOWN_COLLECTION_DEPTH = 5; +const MAX_CONCURRENT_FILE_FETCHES = 5; + +function stripWrappingQuotes(input: string): string { + return input.replace(/^["']|["']$/g, ''); +} + +export function parseGitHubUrl(url: string): ParsedGitHubUrl { + let parsedUrl: URL; + + try { + parsedUrl = new URL(url); + } catch { + throw new Error(`Invalid GitHub URL: ${url}`); + } + + if (parsedUrl.hostname !== GITHUB_HOST) { + throw new Error(`Unsupported GitHub host: ${parsedUrl.hostname}`); + } + + const segments = parsedUrl.pathname.split('/').filter(Boolean); + if (segments.length < 5) { + throw new Error(`Unsupported GitHub URL format: ${url}`); + } + + const owner = segments[0]; + const repo = segments[1]; + const kind = segments[2]; + const ref = segments[3]; + const repoPath = decodeURIComponent(segments.slice(4).join('/')); + + if (!owner || !repo || !ref || !repoPath) { + throw new Error(`Unsupported GitHub URL format: ${url}`); + } + + if (kind === 'tree') { + return { owner, repo, ref, path: repoPath, isFile: false }; + } + + if (kind === 'blob') { + return { owner, repo, ref, path: repoPath, isFile: true }; + } + + throw new Error(`Unsupported GitHub URL format: ${url}`); +} + +export function parseFrontmatter(markdown: string): FrontmatterResult { + // Intentionally narrow parser: we only consume simple scalar `name`, + // `description`, and `keywords` fields used by skill metadata. + // Unsupported YAML constructs are ignored by design. + const match = markdown.match(FRONTMATTER_REGEX); + + if (!match || !match[1]) { + return { frontmatter: {}, content: markdown }; + } + + const frontmatterBlock = match[1]; + const parsedFrontmatter: SkillFrontmatter = {}; + const lines = frontmatterBlock.split('\n'); + let activeListKey: keyof SkillFrontmatter | null = null; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) { + continue; + } + + if ( + activeListKey === 'keywords' && + trimmed.startsWith('- ') && + parsedFrontmatter.keywords + ) { + parsedFrontmatter.keywords.push(stripWrappingQuotes(trimmed.slice(2))); + continue; + } + + const keyValueMatch = trimmed.match(/^([A-Za-z0-9_-]+):\s*(.*)$/); + if (!keyValueMatch || !keyValueMatch[1]) { + activeListKey = null; + continue; + } + + const key = keyValueMatch[1]; + const rawValue = keyValueMatch[2] ?? ''; + + activeListKey = null; + + if (key === 'name' || key === 'description') { + parsedFrontmatter[key] = stripWrappingQuotes(rawValue); + continue; + } + + if (key === 'keywords') { + if (!rawValue) { + parsedFrontmatter.keywords = []; + activeListKey = 'keywords'; + continue; + } + + const arrayMatch = rawValue.match(/^\[(.*)\]$/); + if (arrayMatch) { + const listContent = arrayMatch[1] ?? ''; + parsedFrontmatter.keywords = listContent + .split(',') + .map((item) => stripWrappingQuotes(item.trim())) + .filter(Boolean); + } + } + } + + return { + frontmatter: parsedFrontmatter, + content: markdown.slice(match[0].length), + }; +} + +export class CairoSkillsIngester extends BaseIngester { + protected skills: SkillConfig[]; + + constructor() { + const config: BookConfig = { + repoOwner: 'cairo-skills', + repoName: 'cairo-skills', + fileExtensions: ['.md'], + chunkSize: 4096, + chunkOverlap: 512, + baseUrl: '', + urlSuffix: '', + useUrlMapping: false, + }; + + super(config, DocumentSource.CAIRO_SKILLS); + this.skills = this.loadSkillsConfig(); + } + + protected override async downloadAndExtractDocs(): Promise { + logger.info('Downloading Cairo skills from GitHub'); + + const pages: BookPageDto[] = []; + + for (const skill of this.skills) { + try { + const parsedUrl = parseGitHubUrl(skill.url); + + const markdown = parsedUrl.isFile + ? await this.fetchGitHubFileContent( + parsedUrl.owner, + parsedUrl.repo, + parsedUrl.path, + parsedUrl.ref, + ) + : await this.fetchGitHubDirectoryContent( + parsedUrl.owner, + parsedUrl.repo, + parsedUrl.path, + parsedUrl.ref, + ); + + pages.push({ + name: skill.id, + content: markdown, + }); + } catch (error) { + logger.error( + `Failed to ingest cairo skill '${skill.id}' from ${skill.url}: ${error}`, + ); + } + } + + return pages; + } + + protected override async createChunks( + pages: BookPageDto[], + ): Promise[]> { + logger.info('Creating Cairo skill chunks'); + + const chunks: Document[] = []; + + for (const page of pages) { + const sourceLink = this.getSkillUrl(page.name); + const { frontmatter, content } = parseFrontmatter(page.content); + const sanitizedContent = this.sanitizeCodeBlocks(content); + const sections = this.parsePage(sanitizedContent, true); + const normalizedSections = + sections.length > 0 + ? sections + : [{ title: page.name, content: sanitizedContent }]; + + normalizedSections.forEach((section, index) => { + chunks.push( + new Document({ + pageContent: section.content, + metadata: { + name: page.name, + title: section.title, + chunkNumber: index, + contentHash: calculateHash(section.content), + uniqueId: `skill-${page.name}-${index}`, + sourceLink, + source: DocumentSource.CAIRO_SKILLS, + skillId: page.name, + }, + }), + ); + }); + + const frontmatterName = frontmatter.name?.trim() || page.name; + const description = frontmatter.description?.trim() || ''; + const summaryContent = `${frontmatterName}: ${description}`.trim(); + + chunks.push( + new Document({ + pageContent: summaryContent, + metadata: { + name: page.name, + title: frontmatterName, + chunkNumber: -1, + contentHash: calculateHash(page.content), + uniqueId: `skill-${page.name}-full`, + sourceLink, + source: DocumentSource.CAIRO_SKILLS, + skillId: page.name, + fullContent: page.content, + }, + }), + ); + } + + return chunks; + } + + protected override async cleanupDownloadedFiles(): Promise { + // No temporary files are created for this ingester. + } + + protected override parsePage( + content: string, + split: boolean = false, + ): ParsedSection[] { + if (split) { + return this.splitMarkdownIntoSections(content); + } + + const headerRegex = /^(#{1,2})\s+(.+)$/gm; + const match = headerRegex.exec(content); + if (!match || !match[2]) { + return []; + } + + const sections: ParsedSection[] = []; + addSectionWithSizeLimit(sections, match[2], content, 20000); + return sections; + } + + private loadSkillsConfig(): SkillConfig[] { + const configPath = getRepoPath('ingesters', 'config', 'skills.json'); + const configContent = fs.readFileSync(configPath, 'utf-8'); + const parsedConfig = JSON.parse(configContent) as { + skills?: SkillConfig[]; + }; + + if (!Array.isArray(parsedConfig.skills)) { + throw new Error(`Invalid skills config at ${configPath}`); + } + + return parsedConfig.skills; + } + + private buildGitHubHeaders(): Record { + const token = process.env.GITHUB_TOKEN; + + return { + Accept: 'application/vnd.github+json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }; + } + + private async fetchGitHubContents( + owner: string, + repo: string, + repoPath: string, + ref: string, + ): Promise { + const encodedPath = repoPath + .split('/') + .filter(Boolean) + .map((segment) => encodeURIComponent(segment)) + .join('/'); + const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`; + const response = await axios.get(apiUrl, { + headers: this.buildGitHubHeaders(), + }); + + return response.data as GitHubEntry[] | GitHubFileResponse; + } + + private async fetchGitHubFileContent( + owner: string, + repo: string, + repoPath: string, + ref: string, + ): Promise { + const contentResponse = await this.fetchGitHubContents( + owner, + repo, + repoPath, + ref, + ); + + if ( + Array.isArray(contentResponse) || + contentResponse.type !== 'file' || + contentResponse.encoding !== 'base64' + ) { + throw new Error( + `Expected file response for ${owner}/${repo}/${repoPath}`, + ); + } + + return Buffer.from(contentResponse.content, 'base64').toString('utf-8'); + } + + private async fetchGitHubDirectoryContent( + owner: string, + repo: string, + repoPath: string, + ref: string, + ): Promise { + const markdownFiles = await this.collectMarkdownFiles( + owner, + repo, + repoPath, + ref, + MAX_MARKDOWN_COLLECTION_DEPTH, + ); + + const orderedMarkdownFiles = this.sortMarkdownFiles(markdownFiles); + const markdownContent: string[] = new Array(orderedMarkdownFiles.length); + + for ( + let startIndex = 0; + startIndex < orderedMarkdownFiles.length; + startIndex += MAX_CONCURRENT_FILE_FETCHES + ) { + const batch = orderedMarkdownFiles.slice( + startIndex, + startIndex + MAX_CONCURRENT_FILE_FETCHES, + ); + const batchContents = await Promise.all( + batch.map((markdownFile) => + this.fetchGitHubFileContent(owner, repo, markdownFile, ref), + ), + ); + + batchContents.forEach((content, offset) => { + markdownContent[startIndex + offset] = content; + }); + } + + return markdownContent.join('\n\n'); + } + + private async collectMarkdownFiles( + owner: string, + repo: string, + repoPath: string, + ref: string, + maxDepth: number, + ): Promise { + if (maxDepth < 0) { + logger.warn( + `Skipping markdown collection for ${owner}/${repo}/${repoPath}: maximum directory depth reached`, + ); + return []; + } + + const contents = await this.fetchGitHubContents(owner, repo, repoPath, ref); + if (!Array.isArray(contents)) { + if (contents.path.toLowerCase().endsWith('.md')) { + return [contents.path]; + } + return []; + } + + const markdownFiles: string[] = []; + + for (const item of contents) { + if (item.type === 'file' && item.path.toLowerCase().endsWith('.md')) { + markdownFiles.push(item.path); + continue; + } + + if (item.type === 'dir') { + const nestedMarkdownFiles = await this.collectMarkdownFiles( + owner, + repo, + item.path, + ref, + maxDepth - 1, + ); + markdownFiles.push(...nestedMarkdownFiles); + } + } + + return markdownFiles; + } + + private sortMarkdownFiles(files: string[]): string[] { + return [...files].sort((a, b) => { + const aIsSkill = nodePath.basename(a).toLowerCase() === 'skill.md'; + const bIsSkill = nodePath.basename(b).toLowerCase() === 'skill.md'; + + if (aIsSkill !== bIsSkill) { + return aIsSkill ? -1 : 1; + } + + return a.localeCompare(b); + }); + } + + private getSkillUrl(skillId: string): string { + return this.skills.find((skill) => skill.id === skillId)?.url ?? ''; + } + + private sanitizeCodeBlocks(content: string): string { + const lines = content.split('\n'); + let insideCodeBlock = false; + const sanitized = lines.filter((line) => { + if (line.trim().startsWith('```')) { + insideCodeBlock = !insideCodeBlock; + return true; + } + + if (insideCodeBlock) { + return !line.trim().startsWith('# ') && line.trim() !== '#'; + } + + return true; + }); + + return sanitized.join('\n'); + } + + private splitMarkdownIntoSections(content: string): ParsedSection[] { + const headerRegex = /^(#{1,2})\s+(.+)$/gm; + const sections: ParsedSection[] = []; + let lastIndex = 0; + let lastTitle = ''; + let match: RegExpExecArray | null; + + while ((match = headerRegex.exec(content)) !== null) { + if (!isInsideCodeBlock(content, match.index)) { + if (lastIndex < match.index) { + const sectionContent = content.slice(lastIndex, match.index).trim(); + addSectionWithSizeLimit(sections, lastTitle, sectionContent, 20000); + } + + lastTitle = match[2] ?? ''; + lastIndex = match.index; + } + } + + if (lastIndex < content.length) { + const sectionContent = content.slice(lastIndex).trim(); + addSectionWithSizeLimit(sections, lastTitle, sectionContent, 20000); + } + + return sections; + } +} diff --git a/ingesters/src/ingesters/SkillsIngester.ts b/ingesters/src/ingesters/SkillsIngester.ts new file mode 100644 index 00000000..a3ce1718 --- /dev/null +++ b/ingesters/src/ingesters/SkillsIngester.ts @@ -0,0 +1 @@ +export { CairoSkillsIngester as SkillsIngester } from './CairoSkillsIngester'; diff --git a/ingesters/src/types/index.ts b/ingesters/src/types/index.ts index 016e5aa3..b6cb1c6a 100644 --- a/ingesters/src/types/index.ts +++ b/ingesters/src/types/index.ts @@ -18,6 +18,7 @@ export enum DocumentSource { STARKNET_JS = 'starknet_js', STARKNET_BLOG = 'starknet_blog', DOJO_DOCS = 'dojo_docs', + CAIRO_SKILLS = 'cairo_skills', } export type BookChunk = { @@ -28,4 +29,6 @@ export type BookChunk = { uniqueId: string; sourceLink: string; source: DocumentSource; + skillId?: string; + fullContent?: string; }; diff --git a/package.json b/package.json index 23eb28a4..c5fb463a 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "dependencies": { "@ai-sdk/anthropic": "^3.0.44", "ai": "^6.0.86", - "smithers-orchestrator": "^0.6.0", - "takopi-smithers": "github:evmts/takopi-smithers", + "smithers-orchestrator": "^0.9.0", "zod": "^4.3.6" }, "patchedDependencies": { diff --git a/python/optimizers/results/optimized_rag.json b/python/optimizers/results/optimized_rag.json index e3fae647..679acf7f 100644 --- a/python/optimizers/results/optimized_rag.json +++ b/python/optimizers/results/optimized_rag.json @@ -4,7 +4,7 @@ "train": [], "demos": [], "signature": { - "instructions": "You are an assistant specialized in analyzing queries related to the Cairo programming language, Starknet blockchain protocol, and associated tools including development environments, testing frameworks, and standard libraries like OpenZeppelin for Cairo. Your core task is to process a given query (and optional chat history) to determine if it pertains to Cairo/Starknet topics. Relevant topics include: contract development and lifecycle (e.g., declaration via DECLARE transaction to submit contract classes and generate class hash; deployment via DEPLOY to instantiate contracts on-chain; invocation via INVOKE to interact with external functions; account deployment via DEPLOY_ACCOUNT for account contracts); transaction types and protocol aspects; data structures (e.g., 1D arrays, spans, fixed-size arrays from corelib; Cairo supports basic arrays via corelib, but 2D or nested arrays like Array> are not explicitly covered in standard docs and may require searching for collections or practical examples); type conversions (e.g., ContractAddress is a felt252-based type convertible to u256 via TryInto or Into traits, as felt252 fits within u256; general scalar conversions like felt252 to u256); token operations in contracts (e.g., ERC20-like transfers, debits via transfer_from with approvals, charges potentially via minting, though implementations draw from standard patterns without external libs; for ERC1155 tokens, include minting with acceptance checks, URI setting, and role-based access like MINTER_ROLE, DEFAULT_ADMIN_ROLE using OpenZeppelin patterns); access control and roles (e.g., granting roles like MINTER_ROLE, URI_SETTER_ROLE, UPGRADER_ROLE in constructors; asserting roles in functions like mint_to_winner or role_provision); account abstraction features (e.g., session keys for authentication in smart contracts, often implemented in OpenZeppelin account contracts); testing with frameworks (e.g., snforge from Starknet-foundry for security-focused test cases, modular setups with reusable deployment and role assignment helpers, role encoding, error handling for assertion failures, unauthorized access, successful minting/role provisioning); project setup and tooling (e.g., initializing projects with Scarb for Cairo contract structure, environment setup for Starknet development); and related ecosystem elements like fees, accounts, corelib traits (e.g., integer conversions, array construction), and standard implementations (e.g., OpenZeppelin Cairo contracts for ERC20/ERC1155 traits, access control initialization and granting).\n\nStarknet contract lifecycle typically includes declaration, deployment, invocation, and account deployment, but does not involve steps like IPFS publishing. Focus on factual domain elements: Cairo language basics (syntax, data structures, traits), Starknet protocol (transactions, addresses, fees), practical examples (e.g., code snippets for session key authentication, ERC1155 minting with data spans, role provisioning), testing practices (e.g., snforge test cases for access control enforcement, edge conditions like invalid roles or amounts; use modular coding with setup functions for contract deployment, role assignment, and helpers for address management/role encoding; include demos for role assertion failures, successful operations, unauthorized attempts), project initialization (e.g., Scarb commands for new projects, dependency management for OpenZeppelin or corelib). If the query is unrelated (e.g., general OS troubleshooting like WSL networking issues, non-Cairo programming, or meta-questions like \"What can you do?\" without Cairo/Starknet context), do not generate search queries or resources—instead, output empty lists and include a brief note in the analysis section stating it's off-topic and unrelated to Cairo/Starknet.\n\nFor relevant Cairo/Starknet queries, follow this process:\n1. Analyze the query: Break it down into 1-2 sentences summarizing key components (e.g., specific concepts like transaction types, steps in contract lifecycle, functions, errors, data structures, testing scenarios, or project setup). Identify why the query fits the domain (e.g., involves Starknet transactions, Cairo type conversions, OpenZeppelin ERC1155 testing with snforge, session key implementations in account contracts, or Scarb project initialization). Note how selected resources logically cover the topics (e.g., 'starknet_docs' for protocol-level transactions like DECLARE/DEPLOY; 'cairo_book' and 'corelib_docs' for language features like arrays or conversions; 'cairo_by_example' for practical code snippets; 'openzeppelin_docs' for ERC1155/ERC20 trait implementations, access control roles (e.g., MINTER_ROLE granting, mint_with_acceptance_check), and standard contract patterns; 'starknet_foundry' for snforge testing basics including modular setups, role helpers, and security test cases like assertion failures or unauthorized access; 'scarb_docs' for project initialization, Cairo structure, and tooling). Highlight any limitations, such as lack of direct ERC20/ERC1155 implementations in core resources (focus on OpenZeppelin patterns instead) or need for targeted searches on nested arrays/session keys.\n2. Extract search terms: Generate exactly 4-8 precise, targeted search queries in English (even if the original query is in another language). Prioritize specificity to retrieve relevant documentation sections—combine \"Cairo\" or \"Starknet\" with core query elements (e.g., for contract lifecycle: \"Starknet contract lifecycle\", \"Starknet declare transaction class hash\", \"Starknet deploy transaction steps\", \"Starknet invoke external function\"; for arrays: \"Cairo array construction corelib\", \"Cairo nested arrays examples\", \"Cairo array of arrays\", \"Cairo multidimensional arrays collections\"; for type conversions: \"Cairo ContractAddress to u256 TryInto\", \"Starknet ContractAddress into felt252\", \"Cairo felt252 to u256 conversion example\", \"Corelib u256 from ContractAddress\"; for token operations: \"OpenZeppelin Cairo ERC1155 mint example\", \"Starknet ERC1155 transfer from with approval\", \"Cairo ERC20 approve and transfer_from pattern\", \"OpenZeppelin access control role granting\"; for session keys: \"Starknet OpenZeppelin account session key implementation\", \"Cairo session key authentication contract example\", \"Starknet account abstraction session keys\"; for testing: \"snforge ERC1155 testing OpenZeppelin\", \"snforge access control role assertion failures\", \"Starknet-foundry modular test setup for roles\", \"snforge unauthorized mint access test\"; for project setup: \"Scarb Starknet project initialization\", \"Cairo project structure with Scarb\", \"Starknet development environment setup\"). Avoid broad or generic terms; aim for combinations that probe exact doc sections or examples (e.g., target traits like TryInto/Into, corelib details, protocol flows, OpenZeppelin role constants like DEFAULT_ADMIN_ROLE/MINTER_ROLE, snforge syntax for deployment helpers/error handling). If the query involves syntax, examples, or testing, include \"example\", \"implementation\", \"test\", or \"snforge\" in queries to fetch from 'cairo_by_example', 'openzeppelin_docs', or 'starknet_foundry'.\n3. Identify relevant documentation sources: Select only from this expanded predefined list: ['cairo_book' (Cairo language basics, including data structures like arrays and scalar types), 'starknet_docs' (protocol aspects like transactions, deployment lifecycle, addresses, and fees), 'cairo_by_example' (practical code examples for features like conversions, arrays, contract interactions, or session keys), 'corelib_docs' (standard library details, e.g., array types, traits like TryInto/Into for conversions, collections), 'openzeppelin_docs' (Starknet Cairo implementations for ERC20/ERC1155 tokens, access control with roles like MINTER_ROLE/DEFAULT_ADMIN_ROLE, account contracts including session key patterns, minting with data/acceptance checks, URI setters, upgraders), 'starknet_foundry' (snforge testing framework for Starknet contracts, including security test cases for access control, modular setups with reusable functions for deployment/role assignment, helpers for role encoding/address management, examples of assertion failures, successful minting/provisioning, unauthorized attempts, error handling), 'scarb_docs' (project management tool for Cairo/Starknet, initialization commands, dependency handling for OpenZeppelin/corelib, contract structure and environment setup), 'starknet_js' (StarknetJS library for front-end interactions, contract calls and transactions, deployment, JavaScript/TypeScript integration), 'starknet_blog' (latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, community updates, recent innovations, new tools, partnerships, protocol enhancements)]. Choose 1-4 resources that directly cover the query's topics—e.g., 'starknet_docs' and 'openzeppelin_docs' for transaction types, ERC1155 lifecycle, and role-based functions; 'corelib_docs', 'cairo_book', and 'openzeppelin_docs' for type conversions or token patterns; 'cairo_by_example' and 'starknet_foundry' for testing snippets or session key examples; 'scarb_docs' and 'cairo_book' for project setup; 'starknet_blog' for recent ecosystem updates or new features. Prioritize 'openzeppelin_docs' for standard contract traits/testing patterns, 'starknet_foundry' for snforge-specific testing (e.g., ERC1155 access control), and 'scarb_docs' for initialization. Do not include or invent any other resources (e.g., no external web guides or general libs beyond this list). If no resources fit perfectly, select the closest matches or use an empty list only for off-topic queries.\n\nGeneral strategy: Infer answers from core resources where possible (e.g., ContractAddress to u256 via felt252 wrapping and TryInto trait in corelib_docs/openzeppelin_docs; basic array support in cairo_book but probe for nesting via examples in cairo_by_example; ERC1155 minting/role assertion in openzeppelin_docs; snforge modular tests with helpers for roles in starknet_foundry; session keys via account abstraction in openzeppelin_docs/starknet_docs; Scarb init for projects in scarb_docs). For token-related or testing queries, target OpenZeppelin patterns (e.g., constructor role granting, mint_to_winner logic with assert_only_role) and snforge specifics (e.g., reusable setup for deployment, compact comments in tests). Ensure searches enable retrieving context like trait implementations (e.g., GameERC1155Impl), code snippets (e.g., role_provision granting arbitrary roles), protocol steps, or testing demos to support full query resolution, including edge cases like invalid inputs or access denials.\n\nProcess inputs in this format:\n- ### query: The main user query string (may be a complex prompt with sections like , <context>, <objective>, <requirements>, <deliverable> for tasks like generating test cases or examples).\n- ### chat_history: Optional prior conversation (e.g., \"None\" or a string of history); incorporate if it provides Cairo/Starknet context, but prioritize the current query.\n\nOutput strictly in the following structured format, with no code, no additional explanations, no deviations, and no references to this instruction. Keep the analysis to 1-2 sentences. For off-topic queries, include a brief note under ### query_analysis explaining the irrelevance (e.g., \"This query is unrelated to Cairo or Starknet topics.\").\n\n### query_analysis\n[1-2 sentences summarizing the query breakdown (including chat_history if relevant), key components, domain fit, resource selection rationale, and how search queries target specific doc sections (e.g., transaction types, array examples, conversion traits, ERC1155 testing with snforge, session key implementations, or Scarb setup). For off-topic: Brief note on irrelevance.]\n\n### search_queries\n['query1', 'query2', ..., ] # Exactly 4-8 strings for relevant; empty list [] if off-topic\n\n### resources\n['resource1', 'resource2', ...] # 1-4 from predefined list for relevant; empty list [] if off-topic", + "instructions": "You are an assistant specialized in analyzing queries related to the Cairo programming language, Starknet blockchain protocol, and associated tools including development environments, testing frameworks, and standard libraries like OpenZeppelin for Cairo. Your core task is to process a given query (and optional chat history) to determine if it pertains to Cairo/Starknet topics. Relevant topics include: contract development and lifecycle (e.g., declaration via DECLARE transaction to submit contract classes and generate class hash; deployment via DEPLOY to instantiate contracts on-chain; invocation via INVOKE to interact with external functions; account deployment via DEPLOY_ACCOUNT for account contracts); transaction types and protocol aspects; data structures (e.g., 1D arrays, spans, fixed-size arrays from corelib; Cairo supports basic arrays via corelib, but 2D or nested arrays like Array<Array<T>> are not explicitly covered in standard docs and may require searching for collections or practical examples); type conversions (e.g., ContractAddress is a felt252-based type convertible to u256 via TryInto or Into traits, as felt252 fits within u256; general scalar conversions like felt252 to u256); token operations in contracts (e.g., ERC20-like transfers, debits via transfer_from with approvals, charges potentially via minting, though implementations draw from standard patterns without external libs; for ERC1155 tokens, include minting with acceptance checks, URI setting, and role-based access like MINTER_ROLE, DEFAULT_ADMIN_ROLE using OpenZeppelin patterns); access control and roles (e.g., granting roles like MINTER_ROLE, URI_SETTER_ROLE, UPGRADER_ROLE in constructors; asserting roles in functions like mint_to_winner or role_provision); account abstraction features (e.g., session keys for authentication in smart contracts, often implemented in OpenZeppelin account contracts); testing with frameworks (e.g., snforge from Starknet-foundry for security-focused test cases, modular setups with reusable deployment and role assignment helpers, role encoding, error handling for assertion failures, unauthorized access, successful minting/role provisioning); project setup and tooling (e.g., initializing projects with Scarb for Cairo contract structure, environment setup for Starknet development); and related ecosystem elements like fees, accounts, corelib traits (e.g., integer conversions, array construction), and standard implementations (e.g., OpenZeppelin Cairo contracts for ERC20/ERC1155 traits, access control initialization and granting).\n\nStarknet contract lifecycle typically includes declaration, deployment, invocation, and account deployment, but does not involve steps like IPFS publishing. Focus on factual domain elements: Cairo language basics (syntax, data structures, traits), Starknet protocol (transactions, addresses, fees), practical examples (e.g., code snippets for session key authentication, ERC1155 minting with data spans, role provisioning), testing practices (e.g., snforge test cases for access control enforcement, edge conditions like invalid roles or amounts; use modular coding with setup functions for contract deployment, role assignment, and helpers for address management/role encoding; include demos for role assertion failures, successful operations, unauthorized attempts), project initialization (e.g., Scarb commands for new projects, dependency management for OpenZeppelin or corelib). If the query is unrelated (e.g., general OS troubleshooting like WSL networking issues, non-Cairo programming, or meta-questions like \"What can you do?\" without Cairo/Starknet context), do not generate search queries or resources—instead, output empty lists and include a brief note in the analysis section stating it's off-topic and unrelated to Cairo/Starknet.\n\nFor relevant Cairo/Starknet queries, follow this process:\n1. Analyze the query: Break it down into 1-2 sentences summarizing key components (e.g., specific concepts like transaction types, steps in contract lifecycle, functions, errors, data structures, testing scenarios, or project setup). Identify why the query fits the domain (e.g., involves Starknet transactions, Cairo type conversions, OpenZeppelin ERC1155 testing with snforge, session key implementations in account contracts, or Scarb project initialization). Note how selected resources logically cover the topics (e.g., 'starknet_docs' for protocol-level transactions like DECLARE/DEPLOY; 'cairo_book' and 'corelib_docs' for language features like arrays or conversions; 'cairo_by_example' for practical code snippets; 'openzeppelin_docs' for ERC1155/ERC20 trait implementations, access control roles (e.g., MINTER_ROLE granting, mint_with_acceptance_check), and standard contract patterns; 'starknet_foundry' for snforge testing basics including modular setups, role helpers, and security test cases like assertion failures or unauthorized access; 'scarb_docs' for project initialization, Cairo structure, and tooling). Highlight any limitations, such as lack of direct ERC20/ERC1155 implementations in core resources (focus on OpenZeppelin patterns instead) or need for targeted searches on nested arrays/session keys.\n2. Extract search terms: Generate exactly 4-8 precise, targeted search queries in English (even if the original query is in another language). Prioritize specificity to retrieve relevant documentation sections—combine \"Cairo\" or \"Starknet\" with core query elements (e.g., for contract lifecycle: \"Starknet contract lifecycle\", \"Starknet declare transaction class hash\", \"Starknet deploy transaction steps\", \"Starknet invoke external function\"; for arrays: \"Cairo array construction corelib\", \"Cairo nested arrays examples\", \"Cairo array of arrays\", \"Cairo multidimensional arrays collections\"; for type conversions: \"Cairo ContractAddress to u256 TryInto\", \"Starknet ContractAddress into felt252\", \"Cairo felt252 to u256 conversion example\", \"Corelib u256 from ContractAddress\"; for token operations: \"OpenZeppelin Cairo ERC1155 mint example\", \"Starknet ERC1155 transfer from with approval\", \"Cairo ERC20 approve and transfer_from pattern\", \"OpenZeppelin access control role granting\"; for session keys: \"Starknet OpenZeppelin account session key implementation\", \"Cairo session key authentication contract example\", \"Starknet account abstraction session keys\"; for testing: \"snforge ERC1155 testing OpenZeppelin\", \"snforge access control role assertion failures\", \"Starknet-foundry modular test setup for roles\", \"snforge unauthorized mint access test\"; for project setup: \"Scarb Starknet project initialization\", \"Cairo project structure with Scarb\", \"Starknet development environment setup\"). Avoid broad or generic terms; aim for combinations that probe exact doc sections or examples (e.g., target traits like TryInto/Into, corelib details, protocol flows, OpenZeppelin role constants like DEFAULT_ADMIN_ROLE/MINTER_ROLE, snforge syntax for deployment helpers/error handling). If the query involves syntax, examples, or testing, include \"example\", \"implementation\", \"test\", or \"snforge\" in queries to fetch from 'cairo_by_example', 'openzeppelin_docs', or 'starknet_foundry'.\n3. Identify relevant documentation sources: Select only from this expanded predefined list: ['cairo_book' (Cairo language basics, including data structures like arrays and scalar types), 'starknet_docs' (protocol aspects like transactions, deployment lifecycle, addresses, and fees), 'cairo_by_example' (practical code examples for features like conversions, arrays, contract interactions, or session keys), 'corelib_docs' (standard library details, e.g., array types, traits like TryInto/Into for conversions, collections), 'openzeppelin_docs' (Starknet Cairo implementations for ERC20/ERC1155 tokens, access control with roles like MINTER_ROLE/DEFAULT_ADMIN_ROLE, account contracts including session key patterns, minting with data/acceptance checks, URI setters, upgraders), 'starknet_foundry' (snforge testing framework for Starknet contracts, including security test cases for access control, modular setups with reusable functions for deployment/role assignment, helpers for role encoding/address management, examples of assertion failures, successful minting/provisioning, unauthorized attempts, error handling), 'scarb_docs' (project management tool for Cairo/Starknet, initialization commands, dependency handling for OpenZeppelin/corelib, contract structure and environment setup), 'starknet_js' (StarknetJS library for front-end interactions, contract calls and transactions, deployment, JavaScript/TypeScript integration), 'starknet_blog' (latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, community updates, recent innovations, new tools, partnerships, protocol enhancements), 'cairo_skills' (Cairo Ecosystem Skills. Curated, self-contained knowledge packages for specific Cairo tools and DeFi integrations. Includes: Cairo function benchmarking/profiling with cairo-profiler and pprof, Cairo coding patterns and best practices, Avnu SDK integration for Starknet DeFi (token swaps, DCA, staking, gasless transactions, paymaster), and Starknet DeFi operations (swap routing, lending, liquidity provision). Use when the query involves profiling, benchmarking, DeFi operations, DEX aggregation, or specific SDK integrations)]. Choose 1-4 resources that directly cover the query's topics—e.g., 'starknet_docs' and 'openzeppelin_docs' for transaction types, ERC1155 lifecycle, and role-based functions; 'corelib_docs', 'cairo_book', and 'openzeppelin_docs' for type conversions or token patterns; 'cairo_by_example' and 'starknet_foundry' for testing snippets or session key examples; 'scarb_docs' and 'cairo_book' for project setup; 'starknet_blog' for recent ecosystem updates or new features. Prioritize 'openzeppelin_docs' for standard contract traits/testing patterns, 'starknet_foundry' for snforge-specific testing (e.g., ERC1155 access control), and 'scarb_docs' for initialization. Do not include or invent any other resources (e.g., no external web guides or general libs beyond this list). Use 'cairo_skills' for queries involving profiling, benchmarking, DeFi operations, or Avnu/specific SDK integrations. If no resources fit perfectly, select the closest matches or use an empty list only for off-topic queries.\n\nGeneral strategy: Infer answers from core resources where possible (e.g., ContractAddress to u256 via felt252 wrapping and TryInto trait in corelib_docs/openzeppelin_docs; basic array support in cairo_book but probe for nesting via examples in cairo_by_example; ERC1155 minting/role assertion in openzeppelin_docs; snforge modular tests with helpers for roles in starknet_foundry; session keys via account abstraction in openzeppelin_docs/starknet_docs; Scarb init for projects in scarb_docs). For token-related or testing queries, target OpenZeppelin patterns (e.g., constructor role granting, mint_to_winner logic with assert_only_role) and snforge specifics (e.g., reusable setup for deployment, compact comments in tests). Ensure searches enable retrieving context like trait implementations (e.g., GameERC1155Impl), code snippets (e.g., role_provision granting arbitrary roles), protocol steps, or testing demos to support full query resolution, including edge cases like invalid inputs or access denials.\n\nProcess inputs in this format:\n- ### query: The main user query string (may be a complex prompt with sections like <title>, <context>, <objective>, <requirements>, <deliverable> for tasks like generating test cases or examples).\n- ### chat_history: Optional prior conversation (e.g., \"None\" or a string of history); incorporate if it provides Cairo/Starknet context, but prioritize the current query.\n\nOutput strictly in the following structured format, with no code, no additional explanations, no deviations, and no references to this instruction. Keep the analysis to 1-2 sentences. For off-topic queries, include a brief note under ### query_analysis explaining the irrelevance (e.g., \"This query is unrelated to Cairo or Starknet topics.\").\n\n### query_analysis\n[1-2 sentences summarizing the query breakdown (including chat_history if relevant), key components, domain fit, resource selection rationale, and how search queries target specific doc sections (e.g., transaction types, array examples, conversion traits, ERC1155 testing with snforge, session key implementations, or Scarb setup). For off-topic: Brief note on irrelevance.]\n\n### search_queries\n['query1', 'query2', ..., ] # Exactly 4-8 strings for relevant; empty list [] if off-topic\n\n### resources\n['resource1', 'resource2', ...] # 1-4 from predefined list for relevant; empty list [] if off-topic", "fields": [ { "prefix": "Chat History:", @@ -20,7 +20,7 @@ }, { "prefix": "Resources:", - "description": "List of documentation sources. If unsure what to use or if the query is not clear, use all of the available sources. Available sources: cairo_book: The Cairo Programming Language Book. Essential for core language syntax, semantics, types (felt252, structs, enums, Vec), traits, generics, control flow, memory management, writing tests, organizing a project, standard library usage, starknet interactions. Crucial for smart contract structure, storage, events, ABI, syscalls, contract deployment, interaction, L1<>L2 messaging, Starknet-specific attributes. Very important for interactions with the Starknet state and context (e.g. block, transaction) through syscalls., starknet_docs: The Starknet Documentation. For the Starknet protocol, the STWO prover, architecture, APIs, syscalls, network interaction, deployment, ecosystem tools (Starkli, indexers, StarknetJS, wallets), general Starknet knowledge. This should not be included for Coding and Programming questions, but rather, only for questions about Starknet, Proving, ZK, STWO, SHARP itself., starknet_foundry: The Starknet Foundry Documentation. For using the Foundry toolchain: `snforge` for writing, compiling, testing (unit tests, integration tests), and debugging Starknet contracts. `sncast` for deploying and interacting with contracts to Starknet., cairo_by_example: Cairo by Example Documentation. Provides practical Cairo code snippets for specific language features or common patterns. Useful for how-to syntax questions. This should not be included for Smart Contract questions, but for all other Cairo programming questions., openzeppelin_docs: OpenZeppelin Cairo Contracts Documentation. For using the OZ library: standard implementations (ERC20, ERC721), access control, security patterns, contract upgradeability. Crucial for building standard-compliant contracts., corelib_docs: Cairo Core Library Documentation. For using the Cairo core library: basic types, stdlib functions, stdlib structs, macros, and other core concepts. Essential for Cairo programming questions., scarb_docs: Scarb Documentation. For using the Scarb package manager: building, compiling, generating compilation artifacts, managing dependencies, configuration of Scarb.toml., starknet_js: StarknetJS Documentation. For using the StarknetJS library: interacting with Starknet contracts, (calls and transactions), deploying Starknet contracts, front-end APIs, javascript integration examples, guides, tutorials and general JS/TS documentation for starknet., starknet_blog: Starknet Blog Documentation. For latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, and community updates. Useful for understanding recent Starknet innovations, new tools, partnerships, and protocol enhancements." + "description": "List of documentation sources. If unsure what to use or if the query is not clear, use all of the available sources. Available sources: cairo_book: The Cairo Programming Language Book. Essential for core language syntax, semantics, types (felt252, structs, enums, Vec), traits, generics, control flow, memory management, writing tests, organizing a project, standard library usage, starknet interactions. Crucial for smart contract structure, storage, events, ABI, syscalls, contract deployment, interaction, L1<>L2 messaging, Starknet-specific attributes. Very important for interactions with the Starknet state and context (e.g. block, transaction) through syscalls., starknet_docs: The Starknet Documentation. For the Starknet protocol, the STWO prover, architecture, APIs, syscalls, network interaction, deployment, ecosystem tools (Starkli, indexers, StarknetJS, wallets), general Starknet knowledge. This should not be included for Coding and Programming questions, but rather, only for questions about Starknet, Proving, ZK, STWO, SHARP itself., starknet_foundry: The Starknet Foundry Documentation. For using the Foundry toolchain: `snforge` for writing, compiling, testing (unit tests, integration tests), and debugging Starknet contracts. `sncast` for deploying and interacting with contracts to Starknet., cairo_by_example: Cairo by Example Documentation. Provides practical Cairo code snippets for specific language features or common patterns. Useful for how-to syntax questions. This should not be included for Smart Contract questions, but for all other Cairo programming questions., openzeppelin_docs: OpenZeppelin Cairo Contracts Documentation. For using the OZ library: standard implementations (ERC20, ERC721), access control, security patterns, contract upgradeability. Crucial for building standard-compliant contracts., corelib_docs: Cairo Core Library Documentation. For using the Cairo core library: basic types, stdlib functions, stdlib structs, macros, and other core concepts. Essential for Cairo programming questions., scarb_docs: Scarb Documentation. For using the Scarb package manager: building, compiling, generating compilation artifacts, managing dependencies, configuration of Scarb.toml., starknet_js: StarknetJS Documentation. For using the StarknetJS library: interacting with Starknet contracts, (calls and transactions), deploying Starknet contracts, front-end APIs, javascript integration examples, guides, tutorials and general JS/TS documentation for starknet., starknet_blog: Starknet Blog Documentation. For latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, and community updates. Useful for understanding recent Starknet innovations, new tools, partnerships, and protocol enhancements., cairo_skills: Cairo Ecosystem Skills. Curated, self-contained knowledge packages for specific Cairo tools and DeFi integrations. Includes: Cairo function benchmarking/profiling with cairo-profiler and pprof, Cairo coding patterns and best practices, Avnu SDK integration for Starknet DeFi (token swaps, DCA, staking, gasless transactions, paymaster), and Starknet DeFi operations (swap routing, lending, liquidity provision). Use when the query involves profiling, benchmarking, DeFi operations, DEX aggregation, or specific SDK integrations." } ] }, diff --git a/python/optimizers/results/optimized_retrieval_program.json b/python/optimizers/results/optimized_retrieval_program.json index 27cfe932..0416ab29 100644 --- a/python/optimizers/results/optimized_retrieval_program.json +++ b/python/optimizers/results/optimized_retrieval_program.json @@ -3,7 +3,7 @@ "train": [], "demos": [], "signature": { - "instructions": "You are an assistant specialized in analyzing queries related to the Cairo programming language, Starknet blockchain protocol, and associated tools including development environments, testing frameworks, and standard libraries like OpenZeppelin for Cairo. Your core task is to process a given query (and optional chat history) to determine if it pertains to Cairo/Starknet topics. Relevant topics include: contract development and lifecycle (e.g., declaration via DECLARE transaction to submit contract classes and generate class hash; deployment via DEPLOY to instantiate contracts on-chain; invocation via INVOKE to interact with external functions; account deployment via DEPLOY_ACCOUNT for account contracts); transaction types and protocol aspects; data structures (e.g., 1D arrays, spans, fixed-size arrays from corelib; Cairo supports basic arrays via corelib, but 2D or nested arrays like Array<Array<T>> are not explicitly covered in standard docs and may require searching for collections or practical examples); type conversions (e.g., ContractAddress is a felt252-based type convertible to u256 via TryInto or Into traits, as felt252 fits within u256; general scalar conversions like felt252 to u256); token operations in contracts (e.g., ERC20-like transfers, debits via transfer_from with approvals, charges potentially via minting, though implementations draw from standard patterns without external libs; for ERC1155 tokens, include minting with acceptance checks, URI setting, and role-based access like MINTER_ROLE, DEFAULT_ADMIN_ROLE using OpenZeppelin patterns); access control and roles (e.g., granting roles like MINTER_ROLE, URI_SETTER_ROLE, UPGRADER_ROLE in constructors; asserting roles in functions like mint_to_winner or role_provision); account abstraction features (e.g., session keys for authentication in smart contracts, often implemented in OpenZeppelin account contracts); testing with frameworks (e.g., snforge from Starknet-foundry for security-focused test cases, modular setups with reusable deployment and role assignment helpers, role encoding, error handling for assertion failures, unauthorized access, successful minting/role provisioning); project setup and tooling (e.g., initializing projects with Scarb for Cairo contract structure, environment setup for Starknet development); and related ecosystem elements like fees, accounts, corelib traits (e.g., integer conversions, array construction), and standard implementations (e.g., OpenZeppelin Cairo contracts for ERC20/ERC1155 traits, access control initialization and granting).\n\nStarknet contract lifecycle typically includes declaration, deployment, invocation, and account deployment, but does not involve steps like IPFS publishing. Focus on factual domain elements: Cairo language basics (syntax, data structures, traits), Starknet protocol (transactions, addresses, fees), practical examples (e.g., code snippets for session key authentication, ERC1155 minting with data spans, role provisioning), testing practices (e.g., snforge test cases for access control enforcement, edge conditions like invalid roles or amounts; use modular coding with setup functions for contract deployment, role assignment, and helpers for address management/role encoding; include demos for role assertion failures, successful operations, unauthorized attempts), project initialization (e.g., Scarb commands for new projects, dependency management for OpenZeppelin or corelib). If the query is unrelated (e.g., general OS troubleshooting like WSL networking issues, non-Cairo programming, or meta-questions like \"What can you do?\" without Cairo/Starknet context), do not generate search queries or resources—instead, output empty lists and include a brief note in the analysis section stating it's off-topic and unrelated to Cairo/Starknet.\n\nFor relevant Cairo/Starknet queries, follow this process:\n1. Analyze the query: Break it down into 1-2 sentences summarizing key components (e.g., specific concepts like transaction types, steps in contract lifecycle, functions, errors, data structures, testing scenarios, or project setup). Identify why the query fits the domain (e.g., involves Starknet transactions, Cairo type conversions, OpenZeppelin ERC1155 testing with snforge, session key implementations in account contracts, or Scarb project initialization). Note how selected resources logically cover the topics (e.g., 'starknet_docs' for protocol-level transactions like DECLARE/DEPLOY; 'cairo_book' and 'corelib_docs' for language features like arrays or conversions; 'cairo_by_example' for practical code snippets; 'openzeppelin_docs' for ERC1155/ERC20 trait implementations, access control roles (e.g., MINTER_ROLE granting, mint_with_acceptance_check), and standard contract patterns; 'starknet_foundry' for snforge testing basics including modular setups, role helpers, and security test cases like assertion failures or unauthorized access; 'scarb_docs' for project initialization, Cairo structure, and tooling). Highlight any limitations, such as lack of direct ERC20/ERC1155 implementations in core resources (focus on OpenZeppelin patterns instead) or need for targeted searches on nested arrays/session keys.\n2. Extract search terms: Generate exactly 4-8 precise, targeted search queries in English (even if the original query is in another language). Prioritize specificity to retrieve relevant documentation sections—combine \"Cairo\" or \"Starknet\" with core query elements (e.g., for contract lifecycle: \"Starknet contract lifecycle\", \"Starknet declare transaction class hash\", \"Starknet deploy transaction steps\", \"Starknet invoke external function\"; for arrays: \"Cairo array construction corelib\", \"Cairo nested arrays examples\", \"Cairo array of arrays\", \"Cairo multidimensional arrays collections\"; for type conversions: \"Cairo ContractAddress to u256 TryInto\", \"Starknet ContractAddress into felt252\", \"Cairo felt252 to u256 conversion example\", \"Corelib u256 from ContractAddress\"; for token operations: \"OpenZeppelin Cairo ERC1155 mint example\", \"Starknet ERC1155 transfer from with approval\", \"Cairo ERC20 approve and transfer_from pattern\", \"OpenZeppelin access control role granting\"; for session keys: \"Starknet OpenZeppelin account session key implementation\", \"Cairo session key authentication contract example\", \"Starknet account abstraction session keys\"; for testing: \"snforge ERC1155 testing OpenZeppelin\", \"snforge access control role assertion failures\", \"Starknet-foundry modular test setup for roles\", \"snforge unauthorized mint access test\"; for project setup: \"Scarb Starknet project initialization\", \"Cairo project structure with Scarb\", \"Starknet development environment setup\"). Avoid broad or generic terms; aim for combinations that probe exact doc sections or examples (e.g., target traits like TryInto/Into, corelib details, protocol flows, OpenZeppelin role constants like DEFAULT_ADMIN_ROLE/MINTER_ROLE, snforge syntax for deployment helpers/error handling). If the query involves syntax, examples, or testing, include \"example\", \"implementation\", \"test\", or \"snforge\" in queries to fetch from 'cairo_by_example', 'openzeppelin_docs', or 'starknet_foundry'.\n3. Identify relevant documentation sources: Select only from this expanded predefined list: ['cairo_book' (Cairo language basics, including data structures like arrays and scalar types), 'starknet_docs' (protocol aspects like transactions, deployment lifecycle, addresses, and fees), 'cairo_by_example' (practical code examples for features like conversions, arrays, contract interactions, or session keys), 'corelib_docs' (standard library details, e.g., array types, traits like TryInto/Into for conversions, collections), 'openzeppelin_docs' (Starknet Cairo implementations for ERC20/ERC1155 tokens, access control with roles like MINTER_ROLE/DEFAULT_ADMIN_ROLE, account contracts including session key patterns, minting with data/acceptance checks, URI setters, upgraders), 'starknet_foundry' (snforge testing framework for Starknet contracts, including security test cases for access control, modular setups with reusable functions for deployment/role assignment, helpers for role encoding/address management, examples of assertion failures, successful minting/provisioning, unauthorized attempts, error handling), 'scarb_docs' (project management tool for Cairo/Starknet, initialization commands, dependency handling for OpenZeppelin/corelib, contract structure and environment setup), 'starknet_js' (StarknetJS library for front-end interactions, contract calls and transactions, deployment, JavaScript/TypeScript integration), 'starknet_blog' (latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, community updates, recent innovations, new tools, partnerships, protocol enhancements), 'dojo_docs' (Dojo framework for building onchain games and autonomous worlds on Starknet, including ECS patterns, world contracts, models, systems, events, Torii indexing with GraphQL API and real-time state synchronization, SDKs for TypeScript/JavaScript, Rust, C, Unity, Godot, Telegram bots, Unreal Engine, Sozo CLI tool, Katana devnet, game development patterns)]. Choose 1-4 resources that directly cover the query's topics—e.g., 'starknet_docs' and 'openzeppelin_docs' for transaction types, ERC1155 lifecycle, and role-based functions; 'corelib_docs', 'cairo_book', and 'openzeppelin_docs' for type conversions or token patterns; 'cairo_by_example' and 'starknet_foundry' for testing snippets or session key examples; 'scarb_docs' and 'cairo_book' for project setup; 'starknet_blog' for recent ecosystem updates or new features, starknet apps and protocols; 'dojo_docs' for onchain game development, ECS architecture, Torii indexing, or game-specific SDKs and tooling. Prioritize 'openzeppelin_docs' for standard contract traits/testing patterns, 'starknet_foundry' for snforge-specific testing (e.g., ERC1155 access control), 'scarb_docs' for initialization, and 'dojo_docs' for game development frameworks. Do not include or invent any other resources (e.g., no external web guides or general libs beyond this list). If no resources fit perfectly, select the closest matches or use an empty list only for off-topic queries.\n\nGeneral strategy: Infer answers from core resources where possible (e.g., ContractAddress to u256 via felt252 wrapping and TryInto trait in corelib_docs/openzeppelin_docs; basic array support in cairo_book but probe for nesting via examples in cairo_by_example; ERC1155 minting/role assertion in openzeppelin_docs; snforge modular tests with helpers for roles in starknet_foundry; session keys via account abstraction in openzeppelin_docs/starknet_docs; Scarb init for projects in scarb_docs). For token-related or testing queries, target OpenZeppelin patterns (e.g., constructor role granting, mint_to_winner logic with assert_only_role) and snforge specifics (e.g., reusable setup for deployment, compact comments in tests). Ensure searches enable retrieving context like trait implementations (e.g., GameERC1155Impl), code snippets (e.g., role_provision granting arbitrary roles), protocol steps, or testing demos to support full query resolution, including edge cases like invalid inputs or access denials.\n\nProcess inputs in this format:\n- ### query: The main user query string (may be a complex prompt with sections like <title>, <context>, <objective>, <requirements>, <deliverable> for tasks like generating test cases or examples).\n- ### chat_history: Optional prior conversation (e.g., \"None\" or a string of history); incorporate if it provides Cairo/Starknet context, but prioritize the current query.\n\nOutput strictly in the following structured format, with no code, no additional explanations, no deviations, and no references to this instruction. Keep the analysis to 1-2 sentences. For off-topic queries, include a brief note under ### query_analysis explaining the irrelevance (e.g., \"This query is unrelated to Cairo or Starknet topics.\").\n\n### query_analysis\n[1-2 sentences summarizing the query breakdown (including chat_history if relevant), key components, domain fit, resource selection rationale, and how search queries target specific doc sections (e.g., transaction types, array examples, conversion traits, ERC1155 testing with snforge, session key implementations, or Scarb setup). For off-topic: Brief note on irrelevance.]\n\n### search_queries\n['query1', 'query2', ..., ] # Exactly 4-8 strings for relevant; empty list [] if off-topic\n\n### resources\n['resource1', 'resource2', ...] # 1-4 from predefined list for relevant; empty list [] if off-topic", + "instructions": "You are an assistant specialized in analyzing queries related to the Cairo programming language, Starknet blockchain protocol, and associated tools including development environments, testing frameworks, and standard libraries like OpenZeppelin for Cairo. Your core task is to process a given query (and optional chat history) to determine if it pertains to Cairo/Starknet topics. Relevant topics include: contract development and lifecycle (e.g., declaration via DECLARE transaction to submit contract classes and generate class hash; deployment via DEPLOY to instantiate contracts on-chain; invocation via INVOKE to interact with external functions; account deployment via DEPLOY_ACCOUNT for account contracts); transaction types and protocol aspects; data structures (e.g., 1D arrays, spans, fixed-size arrays from corelib; Cairo supports basic arrays via corelib, but 2D or nested arrays like Array<Array<T>> are not explicitly covered in standard docs and may require searching for collections or practical examples); type conversions (e.g., ContractAddress is a felt252-based type convertible to u256 via TryInto or Into traits, as felt252 fits within u256; general scalar conversions like felt252 to u256); token operations in contracts (e.g., ERC20-like transfers, debits via transfer_from with approvals, charges potentially via minting, though implementations draw from standard patterns without external libs; for ERC1155 tokens, include minting with acceptance checks, URI setting, and role-based access like MINTER_ROLE, DEFAULT_ADMIN_ROLE using OpenZeppelin patterns); access control and roles (e.g., granting roles like MINTER_ROLE, URI_SETTER_ROLE, UPGRADER_ROLE in constructors; asserting roles in functions like mint_to_winner or role_provision); account abstraction features (e.g., session keys for authentication in smart contracts, often implemented in OpenZeppelin account contracts); testing with frameworks (e.g., snforge from Starknet-foundry for security-focused test cases, modular setups with reusable deployment and role assignment helpers, role encoding, error handling for assertion failures, unauthorized access, successful minting/role provisioning); project setup and tooling (e.g., initializing projects with Scarb for Cairo contract structure, environment setup for Starknet development); and related ecosystem elements like fees, accounts, corelib traits (e.g., integer conversions, array construction), and standard implementations (e.g., OpenZeppelin Cairo contracts for ERC20/ERC1155 traits, access control initialization and granting).\n\nStarknet contract lifecycle typically includes declaration, deployment, invocation, and account deployment, but does not involve steps like IPFS publishing. Focus on factual domain elements: Cairo language basics (syntax, data structures, traits), Starknet protocol (transactions, addresses, fees), practical examples (e.g., code snippets for session key authentication, ERC1155 minting with data spans, role provisioning), testing practices (e.g., snforge test cases for access control enforcement, edge conditions like invalid roles or amounts; use modular coding with setup functions for contract deployment, role assignment, and helpers for address management/role encoding; include demos for role assertion failures, successful operations, unauthorized attempts), project initialization (e.g., Scarb commands for new projects, dependency management for OpenZeppelin or corelib). If the query is unrelated (e.g., general OS troubleshooting like WSL networking issues, non-Cairo programming, or meta-questions like \"What can you do?\" without Cairo/Starknet context), do not generate search queries or resources—instead, output empty lists and include a brief note in the analysis section stating it's off-topic and unrelated to Cairo/Starknet.\n\nFor relevant Cairo/Starknet queries, follow this process:\n1. Analyze the query: Break it down into 1-2 sentences summarizing key components (e.g., specific concepts like transaction types, steps in contract lifecycle, functions, errors, data structures, testing scenarios, or project setup). Identify why the query fits the domain (e.g., involves Starknet transactions, Cairo type conversions, OpenZeppelin ERC1155 testing with snforge, session key implementations in account contracts, or Scarb project initialization). Note how selected resources logically cover the topics (e.g., 'starknet_docs' for protocol-level transactions like DECLARE/DEPLOY; 'cairo_book' and 'corelib_docs' for language features like arrays or conversions; 'cairo_by_example' for practical code snippets; 'openzeppelin_docs' for ERC1155/ERC20 trait implementations, access control roles (e.g., MINTER_ROLE granting, mint_with_acceptance_check), and standard contract patterns; 'starknet_foundry' for snforge testing basics including modular setups, role helpers, and security test cases like assertion failures or unauthorized access; 'scarb_docs' for project initialization, Cairo structure, and tooling). Highlight any limitations, such as lack of direct ERC20/ERC1155 implementations in core resources (focus on OpenZeppelin patterns instead) or need for targeted searches on nested arrays/session keys.\n2. Extract search terms: Generate exactly 4-8 precise, targeted search queries in English (even if the original query is in another language). Prioritize specificity to retrieve relevant documentation sections—combine \"Cairo\" or \"Starknet\" with core query elements (e.g., for contract lifecycle: \"Starknet contract lifecycle\", \"Starknet declare transaction class hash\", \"Starknet deploy transaction steps\", \"Starknet invoke external function\"; for arrays: \"Cairo array construction corelib\", \"Cairo nested arrays examples\", \"Cairo array of arrays\", \"Cairo multidimensional arrays collections\"; for type conversions: \"Cairo ContractAddress to u256 TryInto\", \"Starknet ContractAddress into felt252\", \"Cairo felt252 to u256 conversion example\", \"Corelib u256 from ContractAddress\"; for token operations: \"OpenZeppelin Cairo ERC1155 mint example\", \"Starknet ERC1155 transfer from with approval\", \"Cairo ERC20 approve and transfer_from pattern\", \"OpenZeppelin access control role granting\"; for session keys: \"Starknet OpenZeppelin account session key implementation\", \"Cairo session key authentication contract example\", \"Starknet account abstraction session keys\"; for testing: \"snforge ERC1155 testing OpenZeppelin\", \"snforge access control role assertion failures\", \"Starknet-foundry modular test setup for roles\", \"snforge unauthorized mint access test\"; for project setup: \"Scarb Starknet project initialization\", \"Cairo project structure with Scarb\", \"Starknet development environment setup\"). Avoid broad or generic terms; aim for combinations that probe exact doc sections or examples (e.g., target traits like TryInto/Into, corelib details, protocol flows, OpenZeppelin role constants like DEFAULT_ADMIN_ROLE/MINTER_ROLE, snforge syntax for deployment helpers/error handling). If the query involves syntax, examples, or testing, include \"example\", \"implementation\", \"test\", or \"snforge\" in queries to fetch from 'cairo_by_example', 'openzeppelin_docs', or 'starknet_foundry'.\n3. Identify relevant documentation sources: Select only from this expanded predefined list: ['cairo_book' (Cairo language basics, including data structures like arrays and scalar types), 'starknet_docs' (protocol aspects like transactions, deployment lifecycle, addresses, and fees), 'cairo_by_example' (practical code examples for features like conversions, arrays, contract interactions, or session keys), 'corelib_docs' (standard library details, e.g., array types, traits like TryInto/Into for conversions, collections), 'openzeppelin_docs' (Starknet Cairo implementations for ERC20/ERC1155 tokens, access control with roles like MINTER_ROLE/DEFAULT_ADMIN_ROLE, account contracts including session key patterns, minting with data/acceptance checks, URI setters, upgraders), 'starknet_foundry' (snforge testing framework for Starknet contracts, including security test cases for access control, modular setups with reusable functions for deployment/role assignment, helpers for role encoding/address management, examples of assertion failures, successful minting/provisioning, unauthorized attempts, error handling), 'scarb_docs' (project management tool for Cairo/Starknet, initialization commands, dependency handling for OpenZeppelin/corelib, contract structure and environment setup), 'starknet_js' (StarknetJS library for front-end interactions, contract calls and transactions, deployment, JavaScript/TypeScript integration), 'starknet_blog' (latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, community updates, recent innovations, new tools, partnerships, protocol enhancements), 'dojo_docs' (Dojo framework for building onchain games and autonomous worlds on Starknet, including ECS patterns, world contracts, models, systems, events, Torii indexing with GraphQL API and real-time state synchronization, SDKs for TypeScript/JavaScript, Rust, C, Unity, Godot, Telegram bots, Unreal Engine, Sozo CLI tool, Katana devnet, game development patterns), 'cairo_skills' (Cairo Ecosystem Skills. Curated, self-contained knowledge packages for specific Cairo tools and DeFi integrations. Includes: Cairo function benchmarking/profiling with cairo-profiler and pprof, Cairo coding patterns and best practices, Avnu SDK integration for Starknet DeFi (token swaps, DCA, staking, gasless transactions, paymaster), and Starknet DeFi operations (swap routing, lending, liquidity provision). Use when the query involves profiling, benchmarking, DeFi operations, DEX aggregation, or specific SDK integrations)]. Choose 1-4 resources that directly cover the query's topics—e.g., 'starknet_docs' and 'openzeppelin_docs' for transaction types, ERC1155 lifecycle, and role-based functions; 'corelib_docs', 'cairo_book', and 'openzeppelin_docs' for type conversions or token patterns; 'cairo_by_example' and 'starknet_foundry' for testing snippets or session key examples; 'scarb_docs' and 'cairo_book' for project setup; 'starknet_blog' for recent ecosystem updates or new features, starknet apps and protocols; 'dojo_docs' for onchain game development, ECS architecture, Torii indexing, or game-specific SDKs and tooling. Prioritize 'openzeppelin_docs' for standard contract traits/testing patterns, 'starknet_foundry' for snforge-specific testing (e.g., ERC1155 access control), 'scarb_docs' for initialization, and 'dojo_docs' for game development frameworks, 'cairo_skills' for profiling, benchmarking, DeFi operations, or Avnu/specific SDK integrations. Do not include or invent any other resources (e.g., no external web guides or general libs beyond this list). If no resources fit perfectly, select the closest matches or use an empty list only for off-topic queries.\n\nGeneral strategy: Infer answers from core resources where possible (e.g., ContractAddress to u256 via felt252 wrapping and TryInto trait in corelib_docs/openzeppelin_docs; basic array support in cairo_book but probe for nesting via examples in cairo_by_example; ERC1155 minting/role assertion in openzeppelin_docs; snforge modular tests with helpers for roles in starknet_foundry; session keys via account abstraction in openzeppelin_docs/starknet_docs; Scarb init for projects in scarb_docs). For token-related or testing queries, target OpenZeppelin patterns (e.g., constructor role granting, mint_to_winner logic with assert_only_role) and snforge specifics (e.g., reusable setup for deployment, compact comments in tests). Ensure searches enable retrieving context like trait implementations (e.g., GameERC1155Impl), code snippets (e.g., role_provision granting arbitrary roles), protocol steps, or testing demos to support full query resolution, including edge cases like invalid inputs or access denials.\n\nProcess inputs in this format:\n- ### query: The main user query string (may be a complex prompt with sections like <title>, <context>, <objective>, <requirements>, <deliverable> for tasks like generating test cases or examples).\n- ### chat_history: Optional prior conversation (e.g., \"None\" or a string of history); incorporate if it provides Cairo/Starknet context, but prioritize the current query.\n\nOutput strictly in the following structured format, with no code, no additional explanations, no deviations, and no references to this instruction. Keep the analysis to 1-2 sentences. For off-topic queries, include a brief note under ### query_analysis explaining the irrelevance (e.g., \"This query is unrelated to Cairo or Starknet topics.\").\n\n### query_analysis\n[1-2 sentences summarizing the query breakdown (including chat_history if relevant), key components, domain fit, resource selection rationale, and how search queries target specific doc sections (e.g., transaction types, array examples, conversion traits, ERC1155 testing with snforge, session key implementations, or Scarb setup). For off-topic: Brief note on irrelevance.]\n\n### search_queries\n['query1', 'query2', ..., ] # Exactly 4-8 strings for relevant; empty list [] if off-topic\n\n### resources\n['resource1', 'resource2', ...] # 1-4 from predefined list for relevant; empty list [] if off-topic", "fields": [ { "prefix": "Chat History:", @@ -19,7 +19,7 @@ }, { "prefix": "Resources:", - "description": "List of documentation sources. If unsure what to use or if the query is not clear, use all of the available sources. Available sources: cairo_book: The Cairo Programming Language Book. Essential for core language syntax, semantics, types (felt252, structs, enums, Vec), traits, generics, control flow, memory management, writing tests, organizing a project, standard library usage, starknet interactions. Crucial for smart contract structure, storage, events, ABI, syscalls, contract deployment, interaction, L1<>L2 messaging, Starknet-specific attributes. Very important for interactions with the Starknet state and context (e.g. block, transaction) through syscalls., starknet_docs: The Starknet Documentation. For the Starknet protocol, the STWO prover, architecture, APIs, syscalls, network interaction, deployment, ecosystem tools (Starkli, indexers, StarknetJS, wallets), general Starknet knowledge. This should not be included for Coding and Programming questions, but rather, only for questions about Starknet, Proving, ZK, STWO, SHARP itself., starknet_foundry: The Starknet Foundry Documentation. For using the Foundry toolchain: `snforge` for writing, compiling, testing (unit tests, integration tests), and debugging Starknet contracts. `sncast` for deploying and interacting with contracts to Starknet., cairo_by_example: Cairo by Example Documentation. Provides practical Cairo code snippets for specific language features or common patterns. Useful for how-to syntax questions. This should not be included for Smart Contract questions, but for all other Cairo programming questions., openzeppelin_docs: OpenZeppelin Cairo Contracts Documentation. For using the OZ library: standard implementations (ERC20, ERC721), access control, security patterns, contract upgradeability. Crucial for building standard-compliant contracts., corelib_docs: Cairo Core Library Documentation. For using the Cairo core library: basic types, stdlib functions, stdlib structs, macros, and other core concepts. Essential for Cairo programming questions., scarb_docs: Scarb Documentation. For using the Scarb package manager: building, compiling, generating compilation artifacts, managing dependencies, configuration of Scarb.toml., starknet_js: StarknetJS Documentation. For using the StarknetJS library: interacting with Starknet contracts, (calls and transactions), deploying Starknet contracts, front-end APIs, javascript integration examples, guides, tutorials and general JS/TS documentation for starknet., starknet_blog: Starknet Blog Documentation. For latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, and community updates. Useful for understanding recent Starknet innovations, new tools and applications on starknet, partnerships, and protocol enhancements., dojo_docs: Dojo Documentation. For building onchain games and autonomous worlds using the Dojo framework: entity component system (ECS) patterns, world contracts, models, systems, events, indexing with Torii (GraphQL API, entity subscriptions, real-time state synchronization), SDKs and client libraries including dojo.js (TypeScript/JavaScript integration, entity queries, world interactions), Rust SDK (torii-client, world state queries, account management, transaction execution, real-time subscriptions), dojo.c (C bindings for native integrations, WASM32 support), dojo.unity (Unity C# SDK with codegen plugin, World Manager), dojo.godot (Godot integration with live testnet demos), Telegram bot SDK (@dojoengine packages), support for Unreal Engine modules, Sozo CLI tool (build, migration, deployment), Katana devnet, game development patterns on Starknet." + "description": "List of documentation sources. If unsure what to use or if the query is not clear, use all of the available sources. Available sources: cairo_book: The Cairo Programming Language Book. Essential for core language syntax, semantics, types (felt252, structs, enums, Vec), traits, generics, control flow, memory management, writing tests, organizing a project, standard library usage, starknet interactions. Crucial for smart contract structure, storage, events, ABI, syscalls, contract deployment, interaction, L1<>L2 messaging, Starknet-specific attributes. Very important for interactions with the Starknet state and context (e.g. block, transaction) through syscalls., starknet_docs: The Starknet Documentation. For the Starknet protocol, the STWO prover, architecture, APIs, syscalls, network interaction, deployment, ecosystem tools (Starkli, indexers, StarknetJS, wallets), general Starknet knowledge. This should not be included for Coding and Programming questions, but rather, only for questions about Starknet, Proving, ZK, STWO, SHARP itself., starknet_foundry: The Starknet Foundry Documentation. For using the Foundry toolchain: `snforge` for writing, compiling, testing (unit tests, integration tests), and debugging Starknet contracts. `sncast` for deploying and interacting with contracts to Starknet., cairo_by_example: Cairo by Example Documentation. Provides practical Cairo code snippets for specific language features or common patterns. Useful for how-to syntax questions. This should not be included for Smart Contract questions, but for all other Cairo programming questions., openzeppelin_docs: OpenZeppelin Cairo Contracts Documentation. For using the OZ library: standard implementations (ERC20, ERC721), access control, security patterns, contract upgradeability. Crucial for building standard-compliant contracts., corelib_docs: Cairo Core Library Documentation. For using the Cairo core library: basic types, stdlib functions, stdlib structs, macros, and other core concepts. Essential for Cairo programming questions., scarb_docs: Scarb Documentation. For using the Scarb package manager: building, compiling, generating compilation artifacts, managing dependencies, configuration of Scarb.toml., starknet_js: StarknetJS Documentation. For using the StarknetJS library: interacting with Starknet contracts, (calls and transactions), deploying Starknet contracts, front-end APIs, javascript integration examples, guides, tutorials and general JS/TS documentation for starknet., starknet_blog: Starknet Blog Documentation. For latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, and community updates. Useful for understanding recent Starknet innovations, new tools and applications on starknet, partnerships, and protocol enhancements., dojo_docs: Dojo Documentation. For building onchain games and autonomous worlds using the Dojo framework: entity component system (ECS) patterns, world contracts, models, systems, events, indexing with Torii (GraphQL API, entity subscriptions, real-time state synchronization), SDKs and client libraries including dojo.js (TypeScript/JavaScript integration, entity queries, world interactions), Rust SDK (torii-client, world state queries, account management, transaction execution, real-time subscriptions), dojo.c (C bindings for native integrations, WASM32 support), dojo.unity (Unity C# SDK with codegen plugin, World Manager), dojo.godot (Godot integration with live testnet demos), Telegram bot SDK (@dojoengine packages), support for Unreal Engine modules, Sozo CLI tool (build, migration, deployment), Katana devnet, game development patterns on Starknet., cairo_skills: Cairo Ecosystem Skills. Curated, self-contained knowledge packages for specific Cairo tools and DeFi integrations. Includes: Cairo function benchmarking/profiling with cairo-profiler and pprof, Cairo coding patterns and best practices, Avnu SDK integration for Starknet DeFi (token swaps, DCA, staking, gasless transactions, paymaster), and Starknet DeFi operations (swap routing, lending, liquidity provision). Use when the query involves profiling, benchmarking, DeFi operations, DEX aggregation, or specific SDK integrations." } ] }, diff --git a/python/src/cairo_coder/core/rag_pipeline.py b/python/src/cairo_coder/core/rag_pipeline.py index fc04ffda..4e5cdc65 100644 --- a/python/src/cairo_coder/core/rag_pipeline.py +++ b/python/src/cairo_coder/core/rag_pipeline.py @@ -7,6 +7,7 @@ import asyncio import contextlib +import json import os from collections.abc import AsyncGenerator from dataclasses import dataclass @@ -139,6 +140,8 @@ async def _aprocess_query_and_retrieve_docs( ) # documents already contains all retrieved docs, no action needed + documents = await self._expand_skill_documents(documents) + # Ensure Grok summary is present and first in order (for generation context) if grok_summary_doc is not None: if grok_summary_doc in documents: @@ -150,6 +153,84 @@ async def _aprocess_query_and_retrieve_docs( return processed_query, documents, grok_citations + async def _expand_skill_documents(self, documents: list[Document]) -> list[Document]: + """ + Replace skill chunks with full skill documents when available. + + If a full document row cannot be fetched for a skill, keep that skill's + original chunks to degrade gracefully. + """ + skill_chunks = [ + document + for document in documents + if document.metadata.get("source") == DocumentSource.CAIRO_SKILLS + and document.metadata.get("skillId") + ] + if not skill_chunks: + return documents + + skill_ids = list(dict.fromkeys(doc.metadata["skillId"] for doc in skill_chunks)) + unique_ids = [f"skill-{skill_id}-full" for skill_id in skill_ids] + + try: + rows = await self.document_retriever.vector_db.afetch_by_unique_ids(unique_ids) + except Exception as e: + logger.warning( + "_expand_skill_documents: failed to fetch full rows, keeping original chunks", + error=str(e), + exc_info=True, + ) + return documents + + full_documents_by_skill_id: dict[str, Document] = {} + for row in rows: + metadata: Any = row.get("metadata", {}) + if isinstance(metadata, str): + try: + metadata = json.loads(metadata) + except Exception: + logger.warning( + "_expand_skill_documents: unable to decode metadata json, skipping row" + ) + continue + + if not isinstance(metadata, dict): + continue + + skill_id = metadata.get("skillId") + full_content = metadata.get("fullContent") + if skill_id and full_content: + full_documents_by_skill_id[skill_id] = Document( + page_content=full_content, + metadata=metadata, + ) + + result_documents = [ + document + for document in documents + if document.metadata.get("source") != DocumentSource.CAIRO_SKILLS + ] + + found_skill_ids = set(full_documents_by_skill_id) + for skill_id in skill_ids: + if skill_id not in found_skill_ids: + original_chunks = [ + document + for document in skill_chunks + if document.metadata.get("skillId") == skill_id + ] + result_documents.extend(original_chunks) + logger.warning( + "_expand_skill_documents: no full document found, keeping chunks", + skill_id=skill_id, + ) + + for skill_id in skill_ids: + if skill_id in full_documents_by_skill_id: + result_documents.append(full_documents_by_skill_id[skill_id]) + + return result_documents + @traceable(name="RagPipeline", run_type="chain") async def aforward( self, diff --git a/python/src/cairo_coder/core/types.py b/python/src/cairo_coder/core/types.py index 5c1af860..2550488c 100644 --- a/python/src/cairo_coder/core/types.py +++ b/python/src/cairo_coder/core/types.py @@ -31,6 +31,7 @@ class DocumentSource(str, Enum): """Available documentation sources.""" CAIRO_BOOK = "cairo_book" + CAIRO_SKILLS = "cairo_skills" STARKNET_DOCS = "starknet_docs" STARKNET_FOUNDRY = "starknet_foundry" CAIRO_BY_EXAMPLE = "cairo_by_example" @@ -60,8 +61,10 @@ class DocumentMetadata(TypedDict, total=False): # Source fields source: DocumentSource # DocumentSource value (e.g., "cairo_book") sourceLink: str # Full URL to the source documentation + skillId: str # Skill identifier for all-or-nothing retrieval # Additional metadata fields that may be present + fullContent: str # Full skill document content (only on full-doc rows) similarity: Optional[float] # Similarity score from retrieval (if include_similarity=True) diff --git a/python/src/cairo_coder/dspy/document_retriever.py b/python/src/cairo_coder/dspy/document_retriever.py index 26b021cb..3ec2c16a 100644 --- a/python/src/cairo_coder/dspy/document_retriever.py +++ b/python/src/cairo_coder/dspy/document_retriever.py @@ -60,6 +60,28 @@ async def _ensure_pool(self): timeout=30, ) + async def afetch_by_unique_ids(self, unique_ids: list[str]) -> list[dict]: + """ + Fetch rows by metadata.uniqueId values. + + Args: + unique_ids: List of uniqueId values to fetch. + Returns: + List of dicts containing `content` and `metadata`. + """ + if not unique_ids: + return [] + + await self._ensure_pool() + async with self.pool.acquire() as conn: + rows = await conn.fetch( + f"SELECT content, metadata FROM {self.pg_table_name} " + "WHERE metadata->>'uniqueId' = ANY($1::text[])", + unique_ids, + ) + + return [{"content": row["content"], "metadata": row["metadata"]} for row in rows] + @traceable(name="AsyncDocumentRetriever", run_type="retriever") async def aforward(self, query: str, k: int | None = None, sources: list[DocumentSource] | None = None) -> list[dspy.Example]: """Async search with PgVector for k top passages using cosine similarity with source filtering. diff --git a/python/src/cairo_coder/dspy/query_processor.py b/python/src/cairo_coder/dspy/query_processor.py index c1a3cbfd..e80ac24c 100644 --- a/python/src/cairo_coder/dspy/query_processor.py +++ b/python/src/cairo_coder/dspy/query_processor.py @@ -28,6 +28,7 @@ DocumentSource.STARKNET_JS: "StarknetJS Documentation. For using the StarknetJS library: interacting with Starknet contracts, (calls and transactions), deploying Starknet contracts, front-end APIs, javascript integration examples, guides, tutorials and general JS/TS documentation for starknet.", DocumentSource.STARKNET_BLOG: "Starknet Blog Documentation. For latest Starknet updates, announcements, feature releases, ecosystem developments, integration guides, and community updates. Useful for understanding recent Starknet innovations, new tools, partnerships, and protocol enhancements.", DocumentSource.DOJO_DOCS: "Dojo Documentation. For building onchain games and autonomous worlds using the Dojo framework: entity component system (ECS) patterns, world contracts, models, systems, events, indexing with Torii (GraphQL API, entity subscriptions, real-time state synchronization), SDKs and client libraries including dojo.js (TypeScript/JavaScript integration, entity queries, world interactions), Rust SDK (torii-client, world state queries, account management, transaction execution, real-time subscriptions), dojo.c (C bindings for native integrations, WASM32 support), dojo.unity (Unity C# SDK with codegen plugin, World Manager), dojo.godot (Godot integration with live testnet demos), Telegram bot SDK (@dojoengine packages), support for Unreal Engine modules, Sozo CLI tool (build, migration, deployment), Katana devnet, game development patterns on Starknet.", + DocumentSource.CAIRO_SKILLS: "Cairo Ecosystem Skills. Curated, self-contained knowledge packages for specific Cairo tools and DeFi integrations. Includes: Cairo function benchmarking/profiling with cairo-profiler and pprof, Cairo coding patterns and best practices, Avnu SDK integration for Starknet DeFi (token swaps, DCA, staking, gasless transactions, paymaster), and Starknet DeFi operations (swap routing, lending, liquidity provision). Use when the query involves profiling, benchmarking, DeFi operations, DEX aggregation, or specific SDK integrations.", } # Ensure all DocumentSource variants are covered diff --git a/python/tests/integration/test_skills_integration.py b/python/tests/integration/test_skills_integration.py new file mode 100644 index 00000000..4fab372a --- /dev/null +++ b/python/tests/integration/test_skills_integration.py @@ -0,0 +1,150 @@ +"""Integration tests for cairo_skills full-document expansion in the RAG pipeline.""" + +import json +from unittest.mock import AsyncMock + +import dspy +import pytest + +SKILL_ID = "benchmarking-cairo" +SKILL_FULL_CONTENT = ( + "---\nname: Cairo Benchmarking\n" + "description: Profile and benchmark Cairo functions using cairo-profiler.\n" + "---\n\n" + "# Cairo Benchmarking\n\nUse cairo-profiler to profile your Cairo functions.\n" +) +SKILL_SOURCE_LINK = ( + "https://github.com/feltroidprime/cairo-skills/tree/main/skills/benchmarking-cairo" +) + + +def _docker_available() -> bool: + """Return True when Docker daemon is reachable for TestClient fixture setup.""" + try: + import docker + + docker.from_env().ping() + return True + except Exception: + return False + + +HAS_DOCKER = _docker_available() + + +def _make_skill_example() -> dspy.Example: + """Return a cairo_skills chunk as retrieved by vector DB search.""" + return dspy.Example( + content="Benchmark Cairo with cairo-profiler", + metadata={ + "source": "cairo_skills", + "skillId": SKILL_ID, + "title": "Cairo Benchmarking", + "uniqueId": f"skill-{SKILL_ID}-0", + "sourceLink": SKILL_SOURCE_LINK, + }, + ) + + +def _make_full_doc_row() -> dict: + """Return a full skill row as fetched by unique id.""" + return { + "content": "Cairo Benchmarking: Profile and benchmark Cairo functions.", + "metadata": { + "source": "cairo_skills", + "skillId": SKILL_ID, + "title": "Cairo Benchmarking", + "uniqueId": f"skill-{SKILL_ID}-full", + "fullContent": SKILL_FULL_CONTENT, + "sourceLink": SKILL_SOURCE_LINK, + }, + } + + +def _configure_mock_vector_db(mock_vector_db) -> None: + """Configure vector DB mocks for skill chunk retrieval + full-doc expansion.""" + mock_vector_db.aforward = AsyncMock(return_value=[_make_skill_example()]) + mock_vector_db.afetch_by_unique_ids = AsyncMock(return_value=[_make_full_doc_row()]) + + +def _extract_sources_event(stream_text: str) -> dict | None: + """Extract the first SSE sources event payload from streamed response text.""" + for line in stream_text.split("\n"): + if not line.startswith("data: "): + continue + payload = line[6:] + if payload == "[DONE]": + continue + parsed = json.loads(payload) + if parsed.get("type") == "sources": + return parsed + return None + + +@pytest.mark.asyncio +async def test_full_pipeline_skill_expansion(real_pipeline, mock_vector_db): + """Full pipeline should replace skill chunks with the full skill markdown.""" + _configure_mock_vector_db(mock_vector_db) + + result = await real_pipeline.acall("How do I benchmark Cairo functions?") + skill_documents = [ + doc for doc in result.documents if doc.metadata.get("source") == "cairo_skills" + ] + + assert len(skill_documents) == 1 + assert skill_documents[0].page_content == SKILL_FULL_CONTENT + assert skill_documents[0].metadata["skillId"] == SKILL_ID + mock_vector_db.afetch_by_unique_ids.assert_awaited_once_with([f"skill-{SKILL_ID}-full"]) + + +@pytest.mark.skipif(not HAS_DOCKER, reason="Docker daemon unavailable for client fixture") +def test_chat_completions_includes_cairo_skills_in_sources(client, mock_vector_db): + """Streaming /chat/completions should emit skill source in formatted sources.""" + _configure_mock_vector_db(mock_vector_db) + + response = client.post( + "/v1/chat/completions", + json={ + "messages": [{"role": "user", "content": "How do I benchmark Cairo functions?"}], + "stream": True, + }, + ) + + assert response.status_code == 200 + sources_event = _extract_sources_event(response.text) + assert sources_event is not None + assert isinstance(sources_event.get("data"), list) + + sources = sources_event["data"] + assert any( + source.get("metadata", {}).get("title") == "Cairo Benchmarking" for source in sources + ) + assert any( + source.get("metadata", {}).get("url") == SKILL_SOURCE_LINK for source in sources + ) + assert any( + "cairo-skills" in source.get("metadata", {}).get("url", "") for source in sources + ) + + +@pytest.mark.asyncio +async def test_mcp_mode_includes_full_skill_content_in_context(real_pipeline, mock_vector_db): + """MCP mode should include expanded full skill markdown in generation context.""" + _configure_mock_vector_db(mock_vector_db) + captured_context: list[str] = [] + + async def _capture_context(query: str, context: str): + captured_context.append(context) + prediction = dspy.Prediction(skill="# Cairo Benchmarking Skill\nContent") + prediction.set_lm_usage({}) + return prediction + + real_pipeline.mcp_generation_program.acall = AsyncMock(side_effect=_capture_context) + + result = await real_pipeline.acall( + "How do I benchmark Cairo functions?", mcp_mode=True + ) + + assert captured_context + assert SKILL_FULL_CONTENT in captured_context[0] + assert "Cairo Benchmarking Skill" in result.answer diff --git a/python/tests/unit/test_core_types.py b/python/tests/unit/test_core_types.py new file mode 100644 index 00000000..e1356af2 --- /dev/null +++ b/python/tests/unit/test_core_types.py @@ -0,0 +1,16 @@ +"""Unit tests for core type contracts.""" + +from cairo_coder.core.types import DocumentMetadata, DocumentSource + + +def test_document_source_includes_cairo_skills() -> None: + """DocumentSource should expose the cairo_skills source.""" + assert DocumentSource.CAIRO_SKILLS.value == "cairo_skills" + + +def test_document_metadata_includes_skill_fields() -> None: + """DocumentMetadata should include skill metadata annotations.""" + annotations = DocumentMetadata.__annotations__ + + assert annotations["skillId"] is str + assert annotations["fullContent"] is str diff --git a/python/tests/unit/test_document_retriever.py b/python/tests/unit/test_document_retriever.py index b5e5d834..3d18ff90 100644 --- a/python/tests/unit/test_document_retriever.py +++ b/python/tests/unit/test_document_retriever.py @@ -11,7 +11,10 @@ from cairo_coder.core.config import VectorStoreConfig from cairo_coder.core.types import Document, DocumentSource, ProcessedQuery -from cairo_coder.dspy.document_retriever import DocumentRetrieverProgram +from cairo_coder.dspy.document_retriever import ( + DocumentRetrieverProgram, + SourceFilteredPgVectorRM, +) class TestDocumentRetrieverProgram: @@ -287,3 +290,80 @@ def test_create_document_retriever_defaults(self): assert isinstance(retriever, DocumentRetrieverProgram) assert retriever.max_source_count == 5 assert retriever.similarity_threshold == 0.4 + + +class TestSourceFilteredPgVectorRM: + """Unit tests for SourceFilteredPgVectorRM helper methods.""" + + @pytest.mark.asyncio + async def test_afetch_by_unique_ids_returns_empty_for_empty_input(self): + """It should return early with no DB calls when given empty input.""" + retriever = SourceFilteredPgVectorRM.__new__(SourceFilteredPgVectorRM) + retriever.pool = Mock() + retriever._ensure_pool = AsyncMock() + + result = await retriever.afetch_by_unique_ids([]) + + assert result == [] + retriever._ensure_pool.assert_not_awaited() + retriever.pool.acquire.assert_not_called() + + @pytest.mark.asyncio + async def test_afetch_by_unique_ids_uses_parameterized_query_and_returns_rows(self): + """It should fetch by unique IDs and map rows to content+metadata dicts.""" + retriever = SourceFilteredPgVectorRM.__new__(SourceFilteredPgVectorRM) + retriever.pg_table_name = "documents" + retriever._ensure_pool = AsyncMock() + + conn = AsyncMock() + conn.fetch = AsyncMock( + return_value=[ + {"content": "Full skill 1", "metadata": {"uniqueId": "skill-1-full"}}, + {"content": "Full skill 2", "metadata": {"uniqueId": "skill-2-full"}}, + ] + ) + + acquire_ctx = AsyncMock() + acquire_ctx.__aenter__.return_value = conn + acquire_ctx.__aexit__.return_value = False + + retriever.pool = Mock() + retriever.pool.acquire.return_value = acquire_ctx + + unique_ids = ["skill-1-full", "skill-2-full"] + result = await retriever.afetch_by_unique_ids(unique_ids) + + retriever._ensure_pool.assert_awaited_once() + conn.fetch.assert_awaited_once() + + query, query_unique_ids = conn.fetch.await_args.args + assert "SELECT content, metadata FROM documents" in query + assert "metadata->>'uniqueId' = ANY($1::text[])" in query + assert query_unique_ids == unique_ids + assert result == [ + {"content": "Full skill 1", "metadata": {"uniqueId": "skill-1-full"}}, + {"content": "Full skill 2", "metadata": {"uniqueId": "skill-2-full"}}, + ] + + @pytest.mark.asyncio + async def test_afetch_by_unique_ids_returns_empty_when_no_rows_match(self): + """It should return an empty list when the DB query returns no rows.""" + retriever = SourceFilteredPgVectorRM.__new__(SourceFilteredPgVectorRM) + retriever.pg_table_name = "documents" + retriever._ensure_pool = AsyncMock() + + conn = AsyncMock() + conn.fetch = AsyncMock(return_value=[]) + + acquire_ctx = AsyncMock() + acquire_ctx.__aenter__.return_value = conn + acquire_ctx.__aexit__.return_value = False + + retriever.pool = Mock() + retriever.pool.acquire.return_value = acquire_ctx + + result = await retriever.afetch_by_unique_ids(["non-existent-id"]) + + retriever._ensure_pool.assert_awaited_once() + conn.fetch.assert_awaited_once() + assert result == [] diff --git a/python/tests/unit/test_expand_skill_documents.py b/python/tests/unit/test_expand_skill_documents.py new file mode 100644 index 00000000..eadca1de --- /dev/null +++ b/python/tests/unit/test_expand_skill_documents.py @@ -0,0 +1,203 @@ +"""Unit tests for RagPipeline skill document expansion.""" + +import json +from unittest.mock import AsyncMock, Mock + +import pytest + +from cairo_coder.core.types import Document, DocumentSource + + +def make_skill_chunk(skill_id: str, chunk_number: int = 0) -> Document: + """Create a cairo_skills chunk document.""" + return Document( + page_content=f"Chunk {chunk_number} for skill {skill_id}", + metadata={ + "source": DocumentSource.CAIRO_SKILLS, + "skillId": skill_id, + "title": f"Skill {skill_id} chunk {chunk_number}", + "uniqueId": f"skill-{skill_id}-{chunk_number}", + }, + ) + + +def make_non_skill_document(title: str) -> Document: + """Create a non-skill document.""" + return Document( + page_content=f"Non-skill content: {title}", + metadata={ + "source": DocumentSource.CAIRO_BOOK, + "title": title, + "sourceLink": "https://book.cairo-lang.org/", + }, + ) + + +def make_full_document_row(skill_id: str, full_content: str) -> dict: + """Create a DB row for a full skill document.""" + return { + "content": f"Summary for {skill_id}", + "metadata": { + "source": DocumentSource.CAIRO_SKILLS, + "skillId": skill_id, + "title": f"Skill {skill_id}", + "uniqueId": f"skill-{skill_id}-full", + "fullContent": full_content, + }, + } + + +@pytest.mark.asyncio +async def test_no_skill_chunks_passthrough(pipeline): + """Documents without cairo_skills source are returned unchanged.""" + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock(return_value=[]) + pipeline.document_retriever.vector_db = vector_db + + original_docs = [ + make_non_skill_document("Doc A"), + make_non_skill_document("Doc B"), + ] + expanded_docs = await pipeline._expand_skill_documents(original_docs) + + assert expanded_docs is original_docs + vector_db.afetch_by_unique_ids.assert_not_called() + + +@pytest.mark.asyncio +async def test_single_skill_chunk_expanded(pipeline): + """A single skill chunk is replaced by a full skill document.""" + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock( + return_value=[make_full_document_row("loops", "FULL skill loops content")] + ) + pipeline.document_retriever.vector_db = vector_db + + skill_chunk = make_skill_chunk("loops", 1) + expanded_docs = await pipeline._expand_skill_documents([skill_chunk]) + + vector_db.afetch_by_unique_ids.assert_awaited_once_with(["skill-loops-full"]) + assert len(expanded_docs) == 1 + assert expanded_docs[0].page_content == "FULL skill loops content" + assert expanded_docs[0].metadata["skillId"] == "loops" + assert expanded_docs[0].metadata["uniqueId"] == "skill-loops-full" + + +@pytest.mark.asyncio +async def test_multiple_chunks_same_skill_dedup(pipeline): + """Multiple chunks for one skill become one full document.""" + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock( + return_value=[make_full_document_row("arrays", "FULL arrays content")] + ) + pipeline.document_retriever.vector_db = vector_db + + docs = [ + make_skill_chunk("arrays", 0), + make_skill_chunk("arrays", 1), + make_skill_chunk("arrays", 2), + ] + expanded_docs = await pipeline._expand_skill_documents(docs) + + assert len(expanded_docs) == 1 + assert expanded_docs[0].page_content == "FULL arrays content" + assert expanded_docs[0].metadata["skillId"] == "arrays" + + +@pytest.mark.asyncio +async def test_non_skill_docs_preserved(pipeline): + """Non-skill documents are preserved in count and order.""" + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock( + return_value=[make_full_document_row("traits", "FULL traits content")] + ) + pipeline.document_retriever.vector_db = vector_db + + non_skill_1 = make_non_skill_document("First non-skill") + non_skill_2 = make_non_skill_document("Second non-skill") + docs = [non_skill_1, make_skill_chunk("traits", 0), non_skill_2] + + expanded_docs = await pipeline._expand_skill_documents(docs) + + non_skill_docs = [ + document + for document in expanded_docs + if document.metadata.get("source") != DocumentSource.CAIRO_SKILLS + ] + assert non_skill_docs == [non_skill_1, non_skill_2] + assert len(non_skill_docs) == 2 + + +@pytest.mark.asyncio +async def test_graceful_degradation_no_full_doc(pipeline): + """If no full row is found, original skill chunks are kept.""" + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock(return_value=[]) + pipeline.document_retriever.vector_db = vector_db + + docs = [make_skill_chunk("storage", 0), make_skill_chunk("storage", 1)] + expanded_docs = await pipeline._expand_skill_documents(docs) + + assert expanded_docs == docs + + +@pytest.mark.asyncio +async def test_mixed_found_and_not_found(pipeline): + """Found skills are expanded; missing skills keep original chunks.""" + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock( + return_value=[make_full_document_row("macros", "FULL macros content")] + ) + pipeline.document_retriever.vector_db = vector_db + + macros_chunk = make_skill_chunk("macros", 0) + memory_chunk_1 = make_skill_chunk("memory", 0) + memory_chunk_2 = make_skill_chunk("memory", 1) + docs = [macros_chunk, memory_chunk_1, memory_chunk_2] + + expanded_docs = await pipeline._expand_skill_documents(docs) + + assert any( + document.metadata.get("uniqueId") == "skill-macros-full" + and document.page_content == "FULL macros content" + for document in expanded_docs + ) + assert memory_chunk_1 in expanded_docs + assert memory_chunk_2 in expanded_docs + assert macros_chunk not in expanded_docs + + +@pytest.mark.asyncio +async def test_fetch_exception_returns_original_documents(pipeline): + """If fetching full docs fails, the original list is returned unchanged.""" + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock(side_effect=RuntimeError("db unavailable")) + pipeline.document_retriever.vector_db = vector_db + + original_docs = [make_skill_chunk("iterators", 0), make_non_skill_document("Reference")] + expanded_docs = await pipeline._expand_skill_documents(original_docs) + + assert expanded_docs is original_docs + + +@pytest.mark.asyncio +async def test_metadata_string_deserialization(pipeline): + """String metadata rows are deserialized before creating full docs.""" + metadata = { + "source": DocumentSource.CAIRO_SKILLS, + "skillId": "enums", + "title": "Enums", + "uniqueId": "skill-enums-full", + "fullContent": "FULL enums content", + } + vector_db = Mock() + vector_db.afetch_by_unique_ids = AsyncMock( + return_value=[{"content": "summary", "metadata": json.dumps(metadata)}] + ) + pipeline.document_retriever.vector_db = vector_db + + expanded_docs = await pipeline._expand_skill_documents([make_skill_chunk("enums", 0)]) + + assert len(expanded_docs) == 1 + assert expanded_docs[0].page_content == "FULL enums content" + assert expanded_docs[0].metadata["skillId"] == "enums" diff --git a/python/tests/unit/test_query_processor.py b/python/tests/unit/test_query_processor.py index 5f9ebd9d..d0f7a9c7 100644 --- a/python/tests/unit/test_query_processor.py +++ b/python/tests/unit/test_query_processor.py @@ -11,7 +11,10 @@ import pytest from cairo_coder.core.types import DocumentSource, ProcessedQuery -from cairo_coder.dspy.query_processor import CairoQueryAnalysis, QueryProcessorProgram +from cairo_coder.dspy.query_processor import ( + CairoQueryAnalysis, + QueryProcessorProgram, +) class TestQueryProcessorProgram: