diff --git a/README.md b/README.md index 545a321..469e04a 100644 --- a/README.md +++ b/README.md @@ -18,38 +18,124 @@ npm install open-deep-research ## 📝 Usage ```typescript -import { createDeepResearch } from 'open-deep-research'; - -// Simplest usage with a single API key -const research = await createDeepResearch({ - prompt: ['What are the latest advancements in quantum computing?'], - apiKey: 'your-deepseek-api-key', // Single API key for default model - model: 'deepseek-r1', // Specify single model directly - depth: { level: 3 }, - breadth: { maxParallelTopics: 4 }, +import createDeepResearch from 'open-deep-research'; + +// Create instance using the factory function +const deepResearch = await createDeepResearch({ + depth: { + level: 3, // Detailed analysis + includeReferences: true, + }, + breadth: { + level: 2, // Main topic + direct relationships + maxParallelTopics: 3, + }, + synthesis: { + maxOutputTokens: 8000, // Hard upper limit of tokens + targetOutputLength: 5000, + formatAsMarkdown: true, + }, + models: { + default: 'gpt-4o', // Default model + reasoning: 'gemini-1.5-pro', // Reasoning model + }, + jigsawApiKey: 'your-jigsaw-api-key', }); -// OR advanced usage with multiple models -const research = await createDeepResearch({ - prompt: ['What are the latest advancements in quantum computing?'], - apiKeys: { - deepseek: 'your-deepseek-api-key', - openai: 'your-openai-api-key', - gemini: 'your-gemini-api-key', +// Need to provide prompts array as required by generate method +const prompts = [ + 'What are the recent developments in quantum computing?', + // Add more related prompts if needed +]; + +const result = await deepResearch.generate(prompts, 'markdown'); +``` + +### Using String Model Names + +```typescript +import createDeepResearch from 'open-deep-research'; + +// Create instance using the factory function with string model names +const deepResearch = await createDeepResearch({ + depth: { + level: 3, // Detailed analysis + includeReferences: true, + }, + breadth: { + level: 2, // Main topic + direct relationships + maxParallelTopics: 3, + }, + synthesis: { + maxOutputTokens: 8000, // Hard upper limit of tokens + targetOutputLength: 5000, + formatAsMarkdown: true, }, models: { - subQuestionGeneration: 'gemini-1.5-flash', - webSearchAnalysis: 'deepseek-r1', - finalSynthesis: 'gpt-4o', + default: 'gpt-4o', // Default model + reasoning: 'gemini-1.5-pro', // Reasoning model }, - depth: { level: 3 }, - breadth: { maxParallelTopics: 4 }, + jigsawApiKey: 'your-jigsaw-api-key', }); -// Get the research results -const results = await research.execute(); -console.log(results.answer); +// Need to provide prompts array as required by generate method +const prompts = [ + 'What are the recent developments in quantum computing?', + // Add more related prompts if needed +]; +const result = await deepResearch.generate(prompts, 'markdown'); +``` + +### Using Direct Model Instances + +You can also pass model instances directly, which gives you more control over model configuration: + +```typescript +import createDeepResearch from 'open-deep-research'; +import { createOpenAI } from '@ai-sdk/openai'; +import { createGoogleGenerativeAI } from '@ai-sdk/google'; + +// Create model instances directly +const openai = createOpenAI({ + apiKey: process.env.OPENAI_API_KEY, + // Add custom settings as needed + compatibility: 'strict', +}); + +const gemini = createGoogleGenerativeAI({ + apiKey: process.env.GEMINI_API_KEY, +}); + +// Get model instances +const openaiModel = openai.languageModel('gpt-4o'); +const geminiModel = gemini.languageModel('gemini-1.5-pro'); + +// Create instance using the factory function with direct model instances +const deepResearch = await createDeepResearch({ + depth: { + level: 3, + includeReferences: true, + }, + breadth: { + level: 2, + maxParallelTopics: 3, + }, + synthesis: { + maxOutputTokens: 8000, + targetOutputLength: 5000, + formatAsMarkdown: true, + }, + models: { + default: openaiModel, // Pass the model instance directly + reasoning: geminiModel, // Pass the model instance directly + }, + jigsawApiKey: 'your-jigsaw-api-key', +}); + +const prompts = ['What are the recent developments in quantum computing?']; + +const result = await deepResearch.generate(prompts, 'markdown'); ``` ## 🧩 How It Works @@ -172,13 +258,14 @@ To implement the deep research functionality: - Link assertions in the final answer to specific sources 5. **Implement memory management**: + - Track token usage throughout the research process - Apply pruning strategies to manage context size - Implement hierarchical storage for context data - - 1. Keep context in memory during a single run (tree structure) - - 2. Persist to disk at regular intervals or per depth (for crash recovery + debugging) - - 3. Pass context around functions to keep it functional and testable + - 1. Keep context in memory during a single run (tree structure) + - 2. Persist to disk at regular intervals or per depth (for crash recovery + debugging) + - 3. Pass context around functions to keep it functional and testable ## 📄 License diff --git a/bun.lock b/bun.lock deleted file mode 100644 index db772aa..0000000 --- a/bun.lock +++ /dev/null @@ -1,342 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "open-deep-research", - "dependencies": { - "@ai-sdk/anthropic": "^1.1.2", - "@ai-sdk/deepinfra": "^0.1.5", - "@ai-sdk/google": "^1.1.2", - "@ai-sdk/groq": "^1.1.2", - "@ai-sdk/openai": "^1.1.9", - "@types/uuid": "^10.0.0", - "ai": "^4.1.5", - "jigsawstack": "^0.2.2", - "uuid": "^11.1.0", - "zod": "^3.24.1", - }, - "devDependencies": { - "@types/node": "^22.10.10", - "dotenv": "^16.5.0", - "pkgroll": "^2.6.1", - "ts-node": "^10.9.2", - "tsx": "^4.19.2", - }, - }, - }, - "packages": { - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.7" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-PyE7EC2fPjs9DnzRAHDrPQmcnI2m2Eojr8pfhckOejOlDEh2w7NnSJr1W3qe5hUWzKr+6d7NG1ZKR9fhmpDdEQ=="], - - "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@0.1.18", "", { "dependencies": { "@ai-sdk/openai-compatible": "0.1.17", "@ai-sdk/provider": "1.0.12", "@ai-sdk/provider-utils": "2.1.15" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-x9dnyhJGcxGom09UTKc8GazwPQpM2IvJb5CRMp7xMp91I+bUS3ItHxw8NyvlVm1kLATF2H2FzKmhdTmk+Fr1dA=="], - - "@ai-sdk/google": ["@ai-sdk/google@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.7" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-A8AYqCmBs9SJFiAOP6AX0YEDHWTDrCaUDiRY2cdMSKjJiEknvwnPrAAKf3idgVqYaM2kS0qWz5v9v4pBzXDx+w=="], - - "@ai-sdk/groq": ["@ai-sdk/groq@1.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.7" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-DRq0b4twVUh52DFnIhVC4F14Po8w+76sCdigMRRIcAiSmGRr9I3Vyot36tun1q4tBZMYSvQUss60W3eiaoa6mg=="], - - "@ai-sdk/openai": ["@ai-sdk/openai@1.3.16", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.7" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-pjtiBKt1GgaSKZryTbM3tqgoegJwgAUlp1+X5uN6T+VPnI4FLSymV65tyloWzDlyqZmi9HXnnSRPu76VoL5D5g=="], - - "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@0.1.17", "", { "dependencies": { "@ai-sdk/provider": "1.0.12", "@ai-sdk/provider-utils": "2.1.15" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-e60+yxQ29e8RlsTWBW4kLuQJMpVJzH5+cpOeUXLXU6M9wc8BOQCyYg4jYh2ldnfvYCKXYxb2kYeLW7L9fqhhMw=="], - - "@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], - - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.7", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-kM0xS3GWg3aMChh9zfeM+80vEZfXzR3JEUBdycZLtbRZ2TRT8xOj3WodGHPb06sUK5yD7pAXC/P7ctsi2fvUGQ=="], - - "@ai-sdk/react": ["@ai-sdk/react@1.2.9", "", { "dependencies": { "@ai-sdk/provider-utils": "2.2.7", "@ai-sdk/ui-utils": "1.2.8", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["zod"] }, "sha512-/VYm8xifyngaqFDLXACk/1czDRCefNCdALUyp+kIX6DUIYUWTM93ISoZ+qJ8+3E+FiJAKBQz61o8lIIl+vYtzg=="], - - "@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.7", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-nls/IJCY+ks3Uj6G/agNhXqQeLVqhNfoJbuNgCny+nX2veY5ADB91EcZUqVeQ/ionul2SeUswPY6Q/DxteY29Q=="], - - "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.2", "", { "os": "android", "cpu": "arm" }, "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.2", "", { "os": "android", "cpu": "x64" }, "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.2", "", { "os": "linux", "cpu": "arm" }, "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.2", "", { "os": "linux", "cpu": "x64" }, "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.2", "", { "os": "none", "cpu": "arm64" }, "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.2", "", { "os": "none", "cpu": "x64" }, "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.2", "", { "os": "win32", "cpu": "x64" }, "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - - "@rollup/plugin-alias": ["@rollup/plugin-alias@5.1.1", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ=="], - - "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ=="], - - "@rollup/plugin-dynamic-import-vars": ["@rollup/plugin-dynamic-import-vars@2.1.5", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "astring": "^1.8.5", "estree-walker": "^2.0.2", "fast-glob": "^3.2.12", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-Mymi24fd9hlRifdZV/jYIFj1dn99F34imiYu3KzlAcgBcRi3i9SucgW/VRo5SQ9K4NuQ7dCep6pFWgNyhRdFHQ=="], - - "@rollup/plugin-inject": ["@rollup/plugin-inject@5.0.5", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "estree-walker": "^2.0.2", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg=="], - - "@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="], - - "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA=="], - - "@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.0", "", { "os": "android", "cpu": "arm" }, "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.0", "", { "os": "android", "cpu": "arm64" }, "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ=="], - - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg=="], - - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ=="], - - "@tsconfig/node10": ["@tsconfig/node10@1.0.11", "", {}, "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="], - - "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="], - - "@tsconfig/node14": ["@tsconfig/node14@1.0.3", "", {}, "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="], - - "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="], - - "@types/diff-match-patch": ["@types/diff-match-patch@1.0.36", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="], - - "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], - - "@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], - - "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], - - "@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="], - - "acorn": ["acorn@8.14.1", "", { "bin": "bin/acorn" }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], - - "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], - - "ai": ["ai@4.3.9", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.7", "@ai-sdk/react": "1.2.9", "@ai-sdk/ui-utils": "1.2.8", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-P2RpV65sWIPdUlA4f1pcJ11pB0N1YmqPVLEmC4j8WuBwKY0L3q9vGhYPh0Iv+spKHKyn0wUbMfas+7Z6nTfS0g=="], - - "arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="], - - "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], - - "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], - - "create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="], - - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - - "diff": ["diff@4.0.2", "", {}, "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="], - - "diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="], - - "dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="], - - "esbuild": ["esbuild@0.25.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.2", "@esbuild/android-arm": "0.25.2", "@esbuild/android-arm64": "0.25.2", "@esbuild/android-x64": "0.25.2", "@esbuild/darwin-arm64": "0.25.2", "@esbuild/darwin-x64": "0.25.2", "@esbuild/freebsd-arm64": "0.25.2", "@esbuild/freebsd-x64": "0.25.2", "@esbuild/linux-arm": "0.25.2", "@esbuild/linux-arm64": "0.25.2", "@esbuild/linux-ia32": "0.25.2", "@esbuild/linux-loong64": "0.25.2", "@esbuild/linux-mips64el": "0.25.2", "@esbuild/linux-ppc64": "0.25.2", "@esbuild/linux-riscv64": "0.25.2", "@esbuild/linux-s390x": "0.25.2", "@esbuild/linux-x64": "0.25.2", "@esbuild/netbsd-arm64": "0.25.2", "@esbuild/netbsd-x64": "0.25.2", "@esbuild/openbsd-arm64": "0.25.2", "@esbuild/openbsd-x64": "0.25.2", "@esbuild/sunos-x64": "0.25.2", "@esbuild/win32-arm64": "0.25.2", "@esbuild/win32-ia32": "0.25.2", "@esbuild/win32-x64": "0.25.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ=="], - - "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - - "eventsource-parser": ["eventsource-parser@3.0.1", "", {}, "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - - "fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], - - "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="], - - "isomorphic-fetch": ["isomorphic-fetch@3.0.0", "", { "dependencies": { "node-fetch": "^2.6.1", "whatwg-fetch": "^3.4.1" } }, "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA=="], - - "jigsawstack": ["jigsawstack@0.2.2", "", { "dependencies": { "isomorphic-fetch": "^3.0.0" } }, "sha512-xLImEGwajRI+75jl1er8tzlR7xynQLSym8srLQEfpWYZLxq+KnTx9AtYLeY5k3qX5sq2uoSbV7BPJdyslUs3DQ=="], - - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], - - "jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="], - - "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], - - "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], - - "pkgroll": ["pkgroll@2.12.1", "", { "dependencies": { "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-dynamic-import-vars": "^2.1.5", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/pluginutils": "^5.1.4", "esbuild": "^0.25.1", "magic-string": "^0.30.17", "rollup": "^4.34.6", "rollup-pluginutils": "^2.8.2" }, "peerDependencies": { "typescript": "^4.1 || ^5.0" }, "optionalPeers": ["typescript"], "bin": { "pkgroll": "dist/cli.mjs" } }, "sha512-MpooedkVk28Sl1I5q8YO2QZmdlHdEtCzv1nReZdHNRhY0CzbZ14TewN47JopF+0rGCaQOERSPfcIOgPZDQXlZA=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], - - "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], - - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "rollup": ["rollup@4.40.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w=="], - - "rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - - "swr": ["swr@2.3.3", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A=="], - - "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="], - - "tsx": ["tsx@4.19.3", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], - - "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], - - "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="], - - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="], - - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - - "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="], - - "zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], - - "@ai-sdk/deepinfra/@ai-sdk/provider": ["@ai-sdk/provider@1.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-88Uu1zJIE1UUOVJWfE2ybJXgiH8JJ97QY9fbmplErEbfa/k/1kF+tWMVAAJolF2aOGmazQGyQLhv4I9CCuVACw=="], - - "@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.1.15", "", { "dependencies": { "@ai-sdk/provider": "1.0.12", "eventsource-parser": "^3.0.0", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-ndMVtDm2xS86t45CJZSfyl7UblZFewRB8gZkXQHeNi7BhjCYkhE+XQMwfDl6UOAO7kaV60IC1R4JLDWxWiiHug=="], - - "@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@1.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-88Uu1zJIE1UUOVJWfE2ybJXgiH8JJ97QY9fbmplErEbfa/k/1kF+tWMVAAJolF2aOGmazQGyQLhv4I9CCuVACw=="], - - "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.1.15", "", { "dependencies": { "@ai-sdk/provider": "1.0.12", "eventsource-parser": "^3.0.0", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-ndMVtDm2xS86t45CJZSfyl7UblZFewRB8gZkXQHeNi7BhjCYkhE+XQMwfDl6UOAO7kaV60IC1R4JLDWxWiiHug=="], - - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], - } -} diff --git a/examples/basic/simple-research.ts b/examples/basic/simple-research.ts index 5498583..d88f7dc 100644 --- a/examples/basic/simple-research.ts +++ b/examples/basic/simple-research.ts @@ -1,43 +1,91 @@ import createDeepResearch from '../..'; +import 'dotenv/config'; +import { createOpenAI } from '@ai-sdk/openai'; +import { createGoogleGenerativeAI } from '@ai-sdk/google'; +import { createDeepInfra } from '@ai-sdk/deepinfra'; // Basic usage example async function basicResearch() { - const result = await createDeepResearch({ - prompt: [ - 'Could you tell me what the best area to live in SF?', - ], - // Using mostly default settings with slight modifications + // Create model instances directly + const openai = createOpenAI({ + apiKey: process.env.OPENAI_API_KEY, + }); + + const gemini = createGoogleGenerativeAI({ + apiKey: process.env.GEMINI_API_KEY, + }); + + const deepinfra = createDeepInfra({ + apiKey: process.env.DEEPINFRA_API_KEY, + }); + + // Get model instances + const openaiModel = openai.languageModel('gpt-4o'); + const geminiModel = gemini.languageModel('gemini-2.0-flash'); + const deepseekModel = deepinfra.languageModel('deepseek-ai/DeepSeek-R1'); + + // Create instance using the factory function with direct model instances + const deepResearch = await createDeepResearch({ depth: { - level: 2, // Detailed analysis + level: 3, // Detailed analysis includeReferences: true, + confidenceThreshold: 0.7, }, breadth: { level: 2, // Main topic + direct relationships maxParallelTopics: 4, + includeRelatedTopics: true, + minRelevanceScore: 0.8, }, synthesis: { maxOutputTokens: 8000, // Hard upper limit of tokens targetOutputLength: 5000, formatAsMarkdown: true, }, - format: 'json', + models: { + default: openaiModel, // Pass the model instance directly + reasoning: deepseekModel, // Pass the model instance directly + output: geminiModel, // Pass the model instance directly + }, + jigsawApiKey: process.env.JIGSAW_API_KEY, }); - // Log research results - console.log('\n=== RESEARCH SUMMARY ==='); - console.log(`Research completed successfully: ${result.success}`); - console.log('\n=== RESEARCH ==='); - console.log(result.research); + // Need to provide prompts array as required by generate method + const prompts = [ + 'what is the meaning of life?', + // Add more related prompts if needed + ]; + + try { + console.log('Starting deep research...'); + const result = await deepResearch.generate(prompts, 'markdown'); + + // Log research results + console.log('\n=== RESEARCH SUMMARY ==='); + console.log(`Research completed successfully: ${result.success}`); + + console.log('\n=== RESEARCH ==='); + console.log(result.research); - // Log token usage - console.log('\n=== TOKEN USAGE ==='); - console.log(result._usage); + // Log token usage + console.log('\n=== TOKEN USAGE ==='); + console.log({ + inputTokens: result._usage.input_tokens, + outputTokens: result._usage.output_tokens, + inferenceTimeTokens: result._usage.inference_time_tokens, + totalTokens: result._usage.total_tokens, + }); - // Log sources - console.log('\n=== SOURCES ==='); - console.log(result.sources); + // Log sources (currently empty array, but will be populated in future) + console.log('\n=== SOURCES ==='); + console.log(result.sources); - return result; + return result; + } catch (error) { + console.error('Research failed with error:', error); + process.exit(1); + } } -basicResearch().catch(console.error); +// Run the research +basicResearch(); diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 9410474..7bcf673 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -3,11 +3,11 @@ import { ResearchBreadthConfig, ResearchDepthConfig, } from '../types'; -import { SynthesisConfig } from '../types/synthesis'; +import { ReportConfig } from '../types/synthesis'; export const DEFAULT_MODEL_CONFIG: ModelConfig = { default: 'gpt-4.1', - quick: 'gemini-2-flash', + output: 'gemini-2-flash', reasoning: 'deepseek-r1', }; @@ -25,18 +25,14 @@ export const DEFAULT_BREADTH_CONFIG: ResearchBreadthConfig = { minRelevanceScore: 0.7, }; -export const DEFAULT_SYNTHESIS_CONFIG: SynthesisConfig = { - maxOutputTokens: 4000, +export const DEFAULT_SYNTHESIS_CONFIG: ReportConfig = { targetOutputLength: 'standard', formatAsMarkdown: true, }; -export const DEFAULT_FORMAT = 'json'; - export const DEFAULT_CONFIG = { models: DEFAULT_MODEL_CONFIG, depth: DEFAULT_DEPTH_CONFIG, breadth: DEFAULT_BREADTH_CONFIG, synthesis: DEFAULT_SYNTHESIS_CONFIG, - format: DEFAULT_FORMAT, } as const; diff --git a/src/generators/followupQuestionGenerator.ts b/src/generators/followupQuestionGenerator.ts index 6b0b51b..ca3a7c3 100644 --- a/src/generators/followupQuestionGenerator.ts +++ b/src/generators/followupQuestionGenerator.ts @@ -1,111 +1,100 @@ -import GeminiProvider from '../provider/gemini'; import { WebSearchResult } from '../types'; import 'dotenv/config'; import { cleanJsonResponse } from '../utils/utils'; import { generateFollowupPrompts } from '../prompts/generators'; +import { AIProvider } from '../provider/aiProvider'; -export class FollowupQuestionGenerator { - // private openaiInstance: OpenAIProvider; - private geminiInstance: GeminiProvider; +/** + * Generate follow-up questions based on search results + */ +export async function generateFollowupQuestions( + mainPrompt: string[], + searchResult: WebSearchResult, + maxQuestions: number = 4, + provider: AIProvider, + model: string = 'gemini-2.0-flash' +): Promise { + const { systemPrompt, userPrompt } = generateFollowupPrompts({ + maxQuestions, + searchResult, + mainPrompt, + }); - constructor() { - // this.openaiInstance = OpenAIProvider.getInstance({ - // apiKey: process.env.OPENAI_API_KEY || '', - // }); + try { + const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; - this.geminiInstance = GeminiProvider.getInstance({ - apiKey: process.env.GEMINI_API_KEY || '', - }); - } - - async generateFollowupQuestions( - mainPrompt: string[], - searchResult: WebSearchResult, - maxQuestions: number = 2 - ): Promise { - const { systemPrompt, userPrompt } = generateFollowupPrompts({ - maxQuestions, - searchResult, - mainPrompt, - }); + // Try up to 3 times to get a valid response + let attempts = 0; + const maxAttempts = 3; - try { - const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; + while (attempts < maxAttempts) { + attempts++; + try { + const response = await provider.generateText(combinedPrompt, model); - // Try up to 3 times to get a valid response - let attempts = 0; - const maxAttempts = 3; - - while (attempts < maxAttempts) { - attempts++; - try { - const response = await this.geminiInstance.generateText( - combinedPrompt, - 'gemini-2.0-flash' - ); + // Clean the response to handle markdown-formatted JSON + const cleanedResponse = cleanJsonResponse(response); - // Clean the response to handle markdown-formatted JSON - const cleanedResponse = cleanJsonResponse(response); + // Try to parse the JSON + const parsedQuestions = JSON.parse(cleanedResponse); - // Try to parse the JSON - const parsedQuestions = JSON.parse(cleanedResponse); - - // Validate the response format - if (Array.isArray(parsedQuestions) && parsedQuestions.length > 0) { - // Return only the requested number of questions - return parsedQuestions.slice(0, maxQuestions); - } else { - console.warn( - `Attempt ${attempts}: Response is not a valid array or is empty` - ); - if (attempts >= maxAttempts) { - // If we've reached max attempts, return default questions - return this.generateDefaultQuestions( - searchResult.question.question, - maxQuestions - ); - } - } - } catch (parseError) { + // Validate the response format + if (Array.isArray(parsedQuestions) && parsedQuestions.length > 0) { + // Return only the requested number of questions + return parsedQuestions.slice(0, maxQuestions); + } else { console.warn( - `Attempt ${attempts}: Failed to parse response: ${parseError}` + `Attempt ${attempts}: Response is not a valid array or is empty` ); if (attempts >= maxAttempts) { // If we've reached max attempts, return default questions - return this.generateDefaultQuestions( + return generateDefaultQuestions( searchResult.question.question, maxQuestions ); } } + } catch (parseError) { + console.warn( + `Attempt ${attempts}: Failed to parse response: ${parseError}` + ); + if (attempts >= maxAttempts) { + // If we've reached max attempts, return default questions + return generateDefaultQuestions( + searchResult.question.question, + maxQuestions + ); + } } - - // Fallback if all attempts fail - return this.generateDefaultQuestions( - searchResult.question.question, - maxQuestions - ); - } catch (error) { - console.error('Error generating follow-up questions:', error); - return this.generateDefaultQuestions( - searchResult.question.question, - maxQuestions - ); } + + // Fallback if all attempts fail + return generateDefaultQuestions( + searchResult.question.question, + maxQuestions + ); + } catch (error) { + console.error('Error generating follow-up questions:', error); + return generateDefaultQuestions( + searchResult.question.question, + maxQuestions + ); } +} - // Generate default follow-up questions as a fallback - private generateDefaultQuestions( - originalQuestion: string, - count: number - ): string[] { - const defaultQuestions = [ - `What are the limitations or challenges related to ${originalQuestion}?`, - `How might future developments impact ${originalQuestion}?`, - `What are the ethical considerations surrounding ${originalQuestion}?`, - `How does ${originalQuestion} vary across different contexts or regions?`, - ]; +/** + * Generate default follow-up questions as a fallback + */ +function generateDefaultQuestions( + originalQuestion: string, + count: number +): string[] { + const defaultQuestions = [ + `What are the limitations or challenges related to ${originalQuestion}?`, + `How might future developments impact ${originalQuestion}?`, + `What are the ethical considerations surrounding ${originalQuestion}?`, + `How does ${originalQuestion} vary across different contexts or regions?`, + ]; - return defaultQuestions.slice(0, count); - } + return defaultQuestions.slice(0, count); } diff --git a/src/generators/subQuestionGenerator.ts b/src/generators/subQuestionGenerator.ts index 4ce7fee..53b8421 100644 --- a/src/generators/subQuestionGenerator.ts +++ b/src/generators/subQuestionGenerator.ts @@ -1,134 +1,150 @@ import { SubQuestion } from '../types/generators'; import { ResearchBreadthConfig } from '../types'; import 'dotenv/config'; -import { GeminiProvider } from '../provider/gemini'; + import { generateSubQuestionsPrompt, checkRelevancePrompt, } from '../prompts/generators'; +import { AIProvider } from '../provider/aiProvider'; + +/** + * Checks if a question is relevant to the main research topic + */ +export async function checkRelevance( + question: string, + mainPrompt: string[], + model: string, + provider: AIProvider +): Promise { + const { systemPrompt, userPrompt } = checkRelevancePrompt({ + question, + mainPrompt, + }); + + const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; + + const response = await provider.generateText(combinedPrompt, model); + + // Normalize the response to handle different formats + const normalizedResponse = response.trim().toLowerCase(); + + // Check if it contains "true" anywhere in the response + return normalizedResponse.includes('true'); +} -export class SubQuestionGenerator { - private geminiInstance: GeminiProvider; - constructor() { - // if(!process.env.OPENAI_API_KEY) { - // throw new Error('OPENAI_API_KEY is not set'); - // } - this.geminiInstance = GeminiProvider.getInstance({ - apiKey: process.env.GEMINI_API_KEY || '', - }); +/** + * Validates the generated questions for relevance and proper format + */ +async function validateResponse( + questions: SubQuestion[], + mainPrompt: string[], + model: string, + provider: AIProvider +): Promise { + if (!Array.isArray(questions)) { + throw new Error('Invalid response format: expected an array of questions'); } - async generateSubQuestions( - mainPrompt: string[], - breadthConfig: ResearchBreadthConfig - ): Promise { - const targetQuestionCount = breadthConfig.maxParallelTopics + 2; - - const { systemPrompt, userPrompt } = generateSubQuestionsPrompt({ - mainPrompt, - targetQuestionCount, - }); + const validatedQuestions = [...questions]; // Create a copy to avoid modifying during iteration - try { - const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; - // const response = await this.openaiInstance.generateText( - // combinedPrompt, - // 'gpt-4o' - // ); - const response = await this.geminiInstance.generateText( - combinedPrompt, - 'gemini-2.0-flash' - ); - - let parsedQuestions; - try { - parsedQuestions = JSON.parse(response); - } catch (parseError) { - throw new Error(`Failed to parse response as JSON: ${parseError}`); + // Using Promise.all for proper handling of asynchronous operations + const relevanceChecks = await Promise.all( + validatedQuestions.map(async (q) => { + if (!q.question || typeof q.relevanceScore !== 'number') { + q.relevanceScore = 0; + return false; + } + if (q.relevanceScore < 0 || q.relevanceScore > 1) { + q.relevanceScore = 0; + return false; } - // pick the questions equal to the breadthConfig.maxParallelTopics - let questions: SubQuestion[] = parsedQuestions.slice( - 0, - breadthConfig.maxParallelTopics - ); - - questions = await this.validateResponse(questions, mainPrompt); - - return { - questions, - metadata: { - totalGenerated: questions.length, - averageRelevanceScore: - questions.reduce((acc, q) => acc + q.relevanceScore, 0) / - questions.length, - generationTimestamp: new Date().toISOString(), - }, - }; - } catch (error: unknown) { - throw new Error( - `Failed to generate sub-questions: ${ - error instanceof Error ? error.message : String(error) - }` - ); - } + // Check if the question is relevant to the main research topic + return await checkRelevance(q.question, mainPrompt, model, provider); + }) + ); + + // Filter out irrelevant questions + return validatedQuestions.filter( + (_, index) => + relevanceChecks[index] && validatedQuestions[index].relevanceScore > 0 + ); +} + +/** + * Generates sub-questions for a main research topic + */ + +export interface GenerateSubQuestionsOptions { + mainPrompt: string[]; + breadthConfig: ResearchBreadthConfig; + provider: AIProvider; + generationModel?: string; + relevanceCheckModel?: string; +} + +export async function generateSubQuestions({ + mainPrompt, + breadthConfig, + provider, + generationModel = 'gemini-2.0-flash', + relevanceCheckModel = 'gpt-4o', +}: GenerateSubQuestionsOptions): Promise { + if (!mainPrompt || mainPrompt.length === 0) { + throw new Error('Prompts must be set before generating sub-questions'); } - public async checkRelevance( - question: string, - mainPrompt: string[] - ): Promise { - const { systemPrompt, userPrompt } = checkRelevancePrompt({ - question, - mainPrompt, - }); + const targetQuestionCount = breadthConfig.maxParallelTopics + 2; + + const { systemPrompt, userPrompt } = generateSubQuestionsPrompt({ + mainPrompt, + targetQuestionCount, + }); + try { const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; - const response = await this.geminiInstance.generateText( + + const response = await provider.generateText( combinedPrompt, - 'gemini-1.5-flash' + generationModel ); - // Normalize the response to handle different formats - const normalizedResponse = response.trim().toLowerCase(); - - // Check if it contains "true" anywhere in the response - return normalizedResponse.includes('true'); - } - - private async validateResponse( - questions: SubQuestion[], - mainPrompt: string[] - ): Promise { - if (!Array.isArray(questions)) { - throw new Error( - 'Invalid response format: expected an array of questions' - ); + let parsedQuestions; + try { + parsedQuestions = JSON.parse(response); + } catch (parseError) { + throw new Error(`Failed to parse response as JSON: ${parseError}`); } - const validatedQuestions = [...questions]; // Create a copy to avoid modifying during iteration - - // Using Promise.all for proper handling of asynchronous operations - const relevanceChecks = await Promise.all( - validatedQuestions.map(async (q) => { - if (!q.question || typeof q.relevanceScore !== 'number') { - q.relevanceScore = 0; - return false; - } - if (q.relevanceScore < 0 || q.relevanceScore > 1) { - q.relevanceScore = 0; - return false; - } - - // Check if the question is relevant to the main research topic - return await this.checkRelevance(q.question, mainPrompt); - }) + // pick the questions equal to the breadthConfig.maxParallelTopics + let questions: SubQuestion[] = parsedQuestions.slice( + 0, + breadthConfig.maxParallelTopics + ); + + questions = await validateResponse( + questions, + mainPrompt, + relevanceCheckModel, + provider ); - // Filter out irrelevant questions - return validatedQuestions.filter( - (_, index) => - relevanceChecks[index] && validatedQuestions[index].relevanceScore > 0 + return { + questions, + metadata: { + totalGenerated: questions.length, + averageRelevanceScore: + questions.reduce((acc, q) => acc + q.relevanceScore, 0) / + questions.length, + generationTimestamp: new Date().toISOString(), + }, + }; + } catch (error: unknown) { + throw new Error( + `Failed to generate sub-questions: ${ + error instanceof Error ? error.message : String(error) + }` ); } } diff --git a/src/index.ts b/src/index.ts index b54ff1e..f5f9f4a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,18 @@ +import AIProvider from './provider/aiProvider'; import { DeepResearchConfig, DeepResearchInstance, DeepResearchResponse, RecursiveResearchResult, + ResearchSource, } from './types'; -import { FollowupQuestionGenerator } from './generators/followupQuestionGenerator'; -import { Synthesizer } from './synthesis/synthesizer'; +import { generateFollowupQuestions } from './generators/followupQuestionGenerator'; +import { generateSubQuestions } from './generators/subQuestionGenerator'; +import { + synthesize, + generateReport, + hasSufficientInformation, +} from './synthesis/synthesizer'; import { DEFAULT_CONFIG, @@ -13,7 +20,6 @@ import { DEFAULT_BREADTH_CONFIG, DEFAULT_SYNTHESIS_CONFIG, } from './config/defaults'; -import { SubQuestionGenerator } from './generators/subQuestionGenerator'; import { SubQuestionGeneratorResult } from './types/generators'; import { WebSearchResult } from './types'; import 'dotenv/config'; @@ -22,28 +28,48 @@ import { SynthesisOutput } from './types/synthesis'; export class DeepResearch implements DeepResearchInstance { public config: DeepResearchConfig; - private questionGenerator: SubQuestionGenerator; - private followupGenerator: FollowupQuestionGenerator; - private synthesizer: Synthesizer; + public prompts?: string[]; private depthSynthesis: Map; + private aiProvider: AIProvider; constructor(config: Partial) { this.config = this.validateAndMergeConfig(config); - this.questionGenerator = new SubQuestionGenerator(); - this.followupGenerator = new FollowupQuestionGenerator(); - this.synthesizer = new Synthesizer(); + + // Initialize AIProvider + this.aiProvider = new AIProvider(); + + // Add models from config.models if available + if (config.models) { + // For each model type (default, quick, reasoning, etc.) + Object.entries(config.models).forEach(([modelType, modelValue]) => { + if (modelValue) { + if (typeof modelValue !== 'string') { + // It's a LanguageModelV1 instance, add it as a direct model + this.aiProvider.addDirectProvider(modelType, modelValue); + } + // If it's a string, it will be handled by the generateText method + } + }); + } + this.depthSynthesis = new Map(); } private validateAndMergeConfig( config: Partial ): DeepResearchConfig { - if (!config.prompt || !Array.isArray(config.prompt)) { - throw new Error('Prompt must be provided as an array'); + // Merge models carefully to handle both string and LanguageModelV1 instances + const mergedModels = { ...DEFAULT_CONFIG.models }; + + if (config.models) { + Object.entries(config.models).forEach(([key, value]) => { + if (value !== undefined) { + mergedModels[key] = value; + } + }); } return { - prompt: config.prompt, depth: { ...DEFAULT_DEPTH_CONFIG, ...config.depth, @@ -56,44 +82,193 @@ export class DeepResearch implements DeepResearchInstance { ...DEFAULT_SYNTHESIS_CONFIG, ...config.synthesis, }, - format: config.format || DEFAULT_CONFIG.format, - models: { - ...DEFAULT_CONFIG.models, - ...config.models, - }, + models: mergedModels, + jigsawApiKey: + config.jigsawApiKey || + (() => { + throw new Error('Jigsaw API key must be provided in config'); + })(), }; } - public async fireWebSearches( - subQuestions: SubQuestionGeneratorResult - ): Promise { - const jigsaw = JigsawProvider.getInstance(); - const results = await jigsaw.fireWebSearches(subQuestions); - return results; + public getSynthesis(): Map { + return this.depthSynthesis; } - public async generateSubQuestions(): Promise { - return this.questionGenerator.generateSubQuestions(this.config.prompt, { - ...DEFAULT_BREADTH_CONFIG, - ...this.config.breadth, + public async generate(prompt: string[]): Promise { + if (!prompt || !Array.isArray(prompt) || prompt.length === 0) { + throw new Error('Prompt must be provided as a non-empty array'); + } + + // Store the prompt in the class property + this.prompts = prompt; + + // Generate sub-questions directly using the imported function + const subQuestions = await generateSubQuestions({ + mainPrompt: this.prompts, + breadthConfig: { + ...DEFAULT_BREADTH_CONFIG, + ...this.config.breadth, + }, + provider: this.aiProvider, + generationModel: this.config.models?.default as string, + relevanceCheckModel: this.config.models?.reasoning as string, + }); + console.log(`Generated ${subQuestions.questions.length} sub-questions`); + + // Fire web searches directly + const jigsaw = JigsawProvider.getInstance(this.config.jigsawApiKey); + const initialSearch = await jigsaw.fireWebSearches(subQuestions); + console.log(`Received ${initialSearch.length} initial search results`); + + // Perform recursive research + const recursiveResult = await this.performRecursiveResearch(initialSearch); + console.log( + `Recursive research completed with reason: ${recursiveResult.reason}` + ); + + // Get all the syntheses + const allDepthSynthesis = this.getSynthesis(); + console.log( + `Synthesis map contains ${allDepthSynthesis.size} depth levels` + ); + + // Generate the final synthesis + const allSyntheses: SynthesisOutput[] = []; + this.depthSynthesis.forEach((syntheses) => { + allSyntheses.push(...syntheses); }); + + const finalReport = await generateReport( + { + mainPrompt: this.prompts, + allSyntheses: allSyntheses, + }, + { + maxOutputTokens: this.config.synthesis?.maxOutputTokens, + targetOutputLength: + this.config.synthesis?.targetOutputLength ?? 'standard', + formatAsMarkdown: true, + }, + this.aiProvider, + this.config.models?.reasoning as string + ); + + console.log( + `Final research report generated with ${ + finalReport.analysis ? finalReport.analysis.length : 0 + } characters` + ); + + console.log(`\n===== FINAL REPORT DEBUG =====`); + console.log(`Report object keys: ${Object.keys(finalReport).join(', ')}`); + console.log(`Report analysis exists: ${!!finalReport.analysis}`); + console.log(`Report analysis type: ${typeof finalReport.analysis}`); + console.log( + `Report analysis length: ${ + finalReport.analysis ? finalReport.analysis.length : 0 + }` + ); + console.log( + `Report analysis preview: ${ + finalReport.analysis + ? finalReport.analysis.substring(0, 200) + : 'No analysis' + }` + ); + + if (!finalReport.analysis || finalReport.analysis.length === 0) { + console.error(`WARNING: Final report analysis is empty or undefined!`); + } + + // Calculate token usage (placeholder values - implement actual counting) + const inputTokens = 256; // Estimate based on prompt length + const outputTokens = 500; // Rough estimate + const inferenceTimeTokens = 975; // Placeholder + const totalTokens = inputTokens + outputTokens + inferenceTimeTokens; + + // Ensure we have a valid research output + let research = finalReport.analysis || 'No research results available.'; + + // Collect sources from all search results + const sources: ResearchSource[] = []; + + // Extract unique sources from initial search results + initialSearch.forEach((result) => { + if (result.searchResults && result.searchResults.results) { + result.searchResults.results.forEach((source) => { + // Only add unique URLs + if (source.url && !sources.some((s) => s.url === source.url)) { + // Create a source object with only properties from the ResearchSource interface + const researchSource: ResearchSource = { + url: source.url, + content: source.content || '', + ai_overview: source.ai_overview || '', + title: source.title || 'Unknown Title', + domain: source.domain || '', + isAcademic: source.isAcademic, + }; + + // Add domain if not present but URL is valid + if (!researchSource.domain && researchSource.url) { + try { + researchSource.domain = new URL(researchSource.url).hostname; + } catch (e) { + // Invalid URL, keep domain empty + } + } + + sources.push(researchSource); + } + }); + } + }); + + console.log(`\n===== FINAL RESEARCH SUMMARY =====`); + console.log( + `Research completed with ${this.depthSynthesis.size} depth levels` + ); + console.log(`Final report length: ${research.length} characters`); + console.log(`Key themes identified: ${finalReport.keyThemes.join(', ')}`); + console.log(`Sources collected: ${sources.length}`); + + return { + success: true, + research: research, + _usage: { + input_tokens: Math.round(inputTokens), + output_tokens: Math.round(outputTokens), + inference_time_tokens: inferenceTimeTokens, + total_tokens: Math.round(totalTokens), + }, + sources: sources, + }; } - public async performRecursiveResearch( + // We still need the recursive research method since it's complex and has internal state + private async performRecursiveResearch( initialResults: WebSearchResult[], currentDepth: number = 1, parentSynthesis?: SynthesisOutput ): Promise { + if (!this.prompts || this.prompts.length === 0) { + throw new Error('Prompts must be set before performing research'); + } + // Store conditions in variables const isMaxDepthReached = currentDepth >= (this.config.depth?.level ?? DEFAULT_DEPTH_CONFIG.level); - console.log(`Performing research at depth level: ${currentDepth}`); + console.log(`\n===== DEPTH LEVEL ${currentDepth} =====`); + console.log(`Initial web search results: ${initialResults.length}`); // Check if we already have sufficient information - const hasSufficientInfo = await this.synthesizer.hasSufficientInformation( + console.log( + `Checking if we have sufficient information at depth ${currentDepth}...` + ); + const hasSufficientInfo = await hasSufficientInformation( { - mainPrompt: this.config.prompt, + mainPrompt: this.prompts, results: initialResults, currentDepth, parentSynthesis, @@ -101,10 +276,13 @@ export class DeepResearch implements DeepResearchInstance { this.config.depth?.confidenceThreshold || DEFAULT_DEPTH_CONFIG.confidenceThreshold ); + console.log(`Sufficient information check result: ${hasSufficientInfo}`); // Early return conditions - but don't generate final synthesis yet if (isMaxDepthReached) { + console.log(`\n===== DEPTH ${currentDepth} SUMMARY =====`); console.log(`Maximum depth level ${currentDepth} reached.`); + console.log(`Early termination due to max depth reached.`); return { isComplete: true, reason: 'max_depth_reached', @@ -112,7 +290,9 @@ export class DeepResearch implements DeepResearchInstance { } if (hasSufficientInfo) { + console.log(`\n===== DEPTH ${currentDepth} SUMMARY =====`); console.log(`Sufficient information found at depth ${currentDepth}.`); + console.log(`Early termination due to sufficient information.`); return { isComplete: true, reason: 'sufficient_info', @@ -120,12 +300,20 @@ export class DeepResearch implements DeepResearchInstance { } // First, synthesize the current level results - const synthesis = await this.synthesizer.synthesizeResults({ - mainPrompt: this.config.prompt, - results: initialResults, - currentDepth, - parentSynthesis, - }); + console.log( + `Starting synthesis at depth ${currentDepth} with ${initialResults.length} results...` + ); + const synthesis = await synthesize( + { + mainPrompt: this.prompts, + results: initialResults, + currentDepth, + parentSynthesis, + }, + this.aiProvider, + this.config.models?.default as string + ); + console.log(`Synthesis at depth ${currentDepth} completed`); // Store the synthesis for this depth level if (!this.depthSynthesis.has(currentDepth)) { @@ -140,14 +328,31 @@ export class DeepResearch implements DeepResearchInstance { }); // For each search result, generate follow-up questions + let totalFollowUpQuestions = 0; + let totalWebSearches = 0; + + console.log( + `\nProcessing ${initialResults.length} search results for follow-up questions at depth ${currentDepth}...` + ); + for (const result of initialResults) { - const followupQuestions = - await this.followupGenerator.generateFollowupQuestions( - this.config.prompt, - result, - this.config.breadth?.maxParallelTopics || - DEFAULT_BREADTH_CONFIG.maxParallelTopics - ); + console.log( + `Generating follow-up questions for result: "${result.question.question.substring( + 0, + 50 + )}..."` + ); + // Use the function directly + const followupQuestions = await generateFollowupQuestions( + this.prompts, + result, + this.config.breadth?.maxParallelTopics || + DEFAULT_BREADTH_CONFIG.maxParallelTopics, + this.aiProvider, + (this.config.models?.default as string) || 'gemini-2.0-flash' + ); + console.log(`Generated ${followupQuestions.length} follow-up questions`); + totalFollowUpQuestions += followupQuestions.length; if (followupQuestions.length > 0) { // Convert follow-up questions to SubQuestionGeneratorResult format @@ -166,15 +371,29 @@ export class DeepResearch implements DeepResearchInstance { }; try { - // Fire web searches for the follow-up questions - const followupResults = await this.fireWebSearches(subQuestions); + // Fire web searches directly + console.log( + `Firing web searches for ${subQuestions.questions.length} follow-up questions...` + ); + const jigsaw = JigsawProvider.getInstance(this.config.jigsawApiKey); + const followupResults = await jigsaw.fireWebSearches(subQuestions); + console.log( + `Received ${followupResults.length} web search results for follow-up questions` + ); + totalWebSearches += followupResults.length; // Recursively process deeper results with the current synthesis + console.log( + `Starting recursive research at depth ${currentDepth + 1}...` + ); const deeperResult = await this.performRecursiveResearch( followupResults, currentDepth + 1, synthesis ); + console.log( + `Returned from recursive research at depth ${currentDepth + 1}` + ); // If we got a result from deeper level (null means we should stop), return it if (deeperResult !== null) { @@ -192,194 +411,55 @@ export class DeepResearch implements DeepResearchInstance { } // If we get here, we've completed this depth but haven't triggered early termination + console.log(`\n===== DEPTH ${currentDepth} SUMMARY =====`); + console.log( + `Total follow-up questions generated: ${totalFollowUpQuestions}` + ); + console.log(`Total web searches performed: ${totalWebSearches}`); + console.log(`Research at depth ${currentDepth} completed\n`); + return { isComplete: true, reason: 'research_complete', }; // Signal that we're done with research } - - public getSynthesis(): Map { - return this.depthSynthesis; - } - - public async generateFinalSynthesis(): Promise { - // Get all the syntheses from all depth levels - const allSyntheses: SynthesisOutput[] = []; - this.depthSynthesis.forEach((syntheses) => { - allSyntheses.push(...syntheses); - }); - - // Use the synthesizer's generateFinalSynthesis method - return this.synthesizer.generateFinalSynthesis({ - mainPrompt: this.config.prompt, - allSyntheses: allSyntheses, - maxOutputTokens: this.config.synthesis?.maxOutputTokens, - targetOutputLength: - this.config.synthesis?.targetOutputLength ?? - DEFAULT_SYNTHESIS_CONFIG.targetOutputLength, - }); - } } export async function createDeepResearch( config: Partial -): Promise { - const deepResearch = new DeepResearch(config); - const subQuestions = await deepResearch.generateSubQuestions(); - - console.log(`Generated ${subQuestions.questions.length} sub-questions`); - - const initialSearch = await deepResearch.fireWebSearches(subQuestions); - - console.log(`Received ${initialSearch.length} initial search results`); - - // Perform recursive research to populate the depthSynthesis map - const recursiveResult = await deepResearch.performRecursiveResearch( - initialSearch - ); - console.log( - `Recursive research completed with reason: ${recursiveResult.reason}` - ); - - // Get all the syntheses - const allDepthSynthesis = deepResearch.getSynthesis(); - console.log(`Synthesis map contains ${allDepthSynthesis.size} depth levels`); - allDepthSynthesis.forEach((syntheses, depth) => { - console.log(`Depth ${depth}: ${syntheses.length} syntheses generated`); - }); - - // Generate the final synthesis - const finalSynthesis = await deepResearch.generateFinalSynthesis(); - console.log( - `Final synthesis generated with ${ - finalSynthesis.analysis ? finalSynthesis.analysis.length : 0 - } characters` - ); - - // Calculate token usage (placeholder values - implement actual counting) - const inputTokens = 256; // Estimate based on prompt length - const outputTokens = 500; // Rough estimate - const inferenceTimeTokens = 975; // Placeholder - const totalTokens = inputTokens + outputTokens + inferenceTimeTokens; - - // Ensure we have a valid research output - let research = 'No research results available.'; - - if (finalSynthesis) { - if (finalSynthesis.analysis) { - // If analysis field exists, use it - research = finalSynthesis.analysis; - } else if ( - config.format === 'json' && - Object.keys(finalSynthesis).length > 0 - ) { - // If we're in JSON format and have synthesis data but no analysis field, - // generate a formatted research output from the available fields - try { - const sections = []; - - // Add title based on the prompt - sections.push( - `# Research on ${config.prompt?.[0] || 'Requested Topic'}\n` - ); - - // Add key themes section - if (finalSynthesis.keyThemes && finalSynthesis.keyThemes.length > 0) { - sections.push('## Key Themes\n'); - finalSynthesis.keyThemes.forEach((theme) => { - sections.push(`- ${theme}`); - }); - sections.push('\n'); - } - - // Add insights section - if (finalSynthesis.insights && finalSynthesis.insights.length > 0) { - sections.push('## Key Insights\n'); - finalSynthesis.insights.forEach((insight) => { - sections.push(`- ${insight}`); - }); - sections.push('\n'); - } - - // Add knowledge gaps section - if ( - finalSynthesis.knowledgeGaps && - finalSynthesis.knowledgeGaps.length > 0 - ) { - sections.push('## Knowledge Gaps\n'); - finalSynthesis.knowledgeGaps.forEach((gap) => { - sections.push(`- ${gap}`); - }); - sections.push('\n'); - } - - // Add conflicting information section - if ( - finalSynthesis.conflictingInformation && - finalSynthesis.conflictingInformation.length > 0 - ) { - sections.push('## Conflicting Information\n'); - finalSynthesis.conflictingInformation.forEach((conflict) => { - sections.push(`### ${conflict.topic}\n`); - conflict.conflicts.forEach((item) => { - sections.push(`- **Claim 1**: ${item.claim1}`); - sections.push(`- **Claim 2**: ${item.claim2}`); - sections.push( - `- **Resolution**: ${ - item.resolution || 'No resolution provided' - }\n` - ); - }); - }); - sections.push('\n'); - } - - // Add related questions section - if ( - finalSynthesis.relatedQuestions && - finalSynthesis.relatedQuestions.length > 0 - ) { - sections.push('## Related Questions\n'); - finalSynthesis.relatedQuestions.forEach((question) => { - sections.push(`- ${question}`); - }); - sections.push('\n'); - } - - // Add confidence score - if (finalSynthesis.confidence !== undefined) { - sections.push( - `## Confidence Score\n${finalSynthesis.confidence * 100}%\n` - ); - } - - research = sections.join('\n'); - } catch (error) { - console.error('Error formatting research output:', error); - } - } - } - - console.log(`Research output length: ${research.length} characters`); - if (research === 'No research results available.') { - console.log('WARNING: Using default "No research results available" text'); - console.log( - 'Final synthesis data:', - JSON.stringify(finalSynthesis, null, 2) - ); - } - - return { - success: true, - research: research, - _usage: { - input_tokens: Math.round(inputTokens), - output_tokens: Math.round(outputTokens), - inference_time_tokens: inferenceTimeTokens, - total_tokens: Math.round(totalTokens), +): Promise { + // Set up default configs + const defaultConfig: DeepResearchConfig = { + depth: { + level: 3, + maxTokensPerAnalysis: 4000, + includeReferences: true, + confidenceThreshold: 0.7, + }, + breadth: { + level: 2, + maxParallelTopics: 3, + includeRelatedTopics: true, + minRelevanceScore: 0.8, + }, + synthesis: { + maxOutputTokens: 8000, + targetOutputLength: 5000, + formatAsMarkdown: true, }, - sources: [], // Now populated from search results }; + + // Merge provided config with defaults + const mergedConfig = { + ...defaultConfig, + ...config, + depth: { ...defaultConfig.depth, ...config.depth }, + breadth: { ...defaultConfig.breadth, ...config.breadth }, + synthesis: { ...defaultConfig.synthesis, ...config.synthesis }, + }; + + // Return new instance with merged config + return new DeepResearch(mergedConfig); } // Default export diff --git a/src/prompts/finalSynthesis.md b/src/prompts/finalSynthesis.md deleted file mode 100644 index 3f2a099..0000000 --- a/src/prompts/finalSynthesis.md +++ /dev/null @@ -1,35 +0,0 @@ -You are a world-class research analyst and writer. When given: -• A Main Topic (or set of topics) - • A series of intermediate syntheses or past recursive analyses - • A collection of raw search results, each with a question and an AI-generated overview - • A user-specified length goal (either a minimum token/word count, or a style hint: “concise,” “standard,” or “detailed”) - -your task is to produce a single, cohesive **deep research article** that: - -1. **Introduces** the topic—outlining scope, importance, and objectives. -2. **Synthesizes** all intermediate analyses and search results, weaving them into a structured narrative. -3. **Identifies** and groups the key themes and patterns that emerge across sources. -4. **Highlights** novel insights your analysis uncovers—points not explicitly stated in any one source. -5. **Notes** contradictions or conflicts in the literature, resolving them or clearly framing open debates. -6. **Pinpoints** remaining knowledge gaps and recommends avenues for further inquiry. -7. **Concludes** with a concise summary of findings and practical or theoretical implications. -8. **Cites** every factual claim or statistic with in-text references (e.g. “[1]”, “[2]”) and appends a numbered bibliography at the end. -9. **Meets** the user’s length requirement—if given as a number, be **at least** that many tokens/words; if “concise,” “standard,” or “detailed,” adjust your level of depth accordingly. - -**Structure your output** exactly like this: - -– Title: A descriptive, engaging headline -– Abstract: 3–5 sentences summary -– Table of Contents (with section headings) -– 1. Introduction -– 2. Background & Literature Review -– 3. Thematic Synthesis - 3.1 Theme A - 3.2 Theme B -– 4. Novel Insights -– 5. Conflicting Evidence & Resolutions -– 6. Knowledge Gaps & Future Directions -– 7. Conclusion -– References - -Be sure your final article is polished, logically coherent, and self-contained: a reader who sees only this text should come away with a full, evidence-backed understanding of the topic. diff --git a/src/prompts/intermediateSynthesis.md b/src/prompts/intermediateSynthesis.md deleted file mode 100644 index 04f01ad..0000000 --- a/src/prompts/intermediateSynthesis.md +++ /dev/null @@ -1,28 +0,0 @@ -`You are an expert research synthesizer tasked with an INTERMEDIATE synthesis only. -Your goal is to extract **all** possible information from the material provided—do **not** draw final conclusions or trim content. Instead, produce exhaustive, unfiltered lists that a later “final synthesis” step will distill. - -Specifically, you must: -1. Extract and list **every** theme or pattern you can find -2. Enumerate **all** novel insights or observations -3. Identify **all** contradictions or discrepancies between sources -4. Catalog **all** knowledge gaps or open questions -5. Collect any related or follow-up questions - -**Output** a valid JSON object exactly like this: - -{ - "analysis": "Detailed commentary on what the sources contain…", - "allThemes": ["Theme A", "Theme B", /* …every theme… */], - "allInsights": ["Insight 1", "Insight 2", /* …every insight… */], - "allConflicts": [ - { - "topic": "Topic with conflict", - "conflicts": [ - { "claim1": "Claim X", "claim2": "Claim Y" }, - /* …all conflict pairs… */ - ] - } - ], - "allKnowledgeGaps": ["Gap A", "Gap B", /* …every gap… */], - "relatedQuestions": ["Question 1", "Question 2", /* …every question… */] -}` \ No newline at end of file diff --git a/src/prompts/synthesis.ts b/src/prompts/synthesis.ts index 4babbcb..1a8f292 100644 --- a/src/prompts/synthesis.ts +++ b/src/prompts/synthesis.ts @@ -73,7 +73,7 @@ Please synthesize this information according to the instructions.`; }; }; -export const generateFinalSynthesisPrompt = ({ +export const generateReportPrompt = ({ mainPrompt, allSyntheses = [], maxOutputTokens, diff --git a/src/provider/aiProvider.ts b/src/provider/aiProvider.ts new file mode 100644 index 0000000..437578b --- /dev/null +++ b/src/provider/aiProvider.ts @@ -0,0 +1,274 @@ +import { generateText } from 'ai'; +import { GoogleGenerativeAIProvider } from '@ai-sdk/google'; +import { DeepInfraProvider } from '@ai-sdk/deepinfra'; +import { OpenAIProvider as OpenAISDKProvider } from '@ai-sdk/openai'; +import { ProviderV1, LanguageModelV1 } from '@ai-sdk/provider'; +import { createGoogleGenerativeAI } from '@ai-sdk/google'; +import { createOpenAI } from '@ai-sdk/openai'; +import { createDeepInfra } from '@ai-sdk/deepinfra'; + +/** + * AIProvider acts as an abstract factory for different AI model providers + * It unifies the interface for interacting with different provider types + */ +export class AIProvider { + private providers: Map = new Map(); + private directModels: Map = new Map(); + + // Model prefix to provider mapping + private modelToProviderMap: Record = { + // OpenAI models + gpt: 'openai', + 'text-davinci': 'openai', + 'text-curie': 'openai', + 'text-babbage': 'openai', + 'text-ada': 'openai', + davinci: 'openai', + curie: 'openai', + babbage: 'openai', + ada: 'openai', + whisper: 'openai', + 'dall-e': 'openai', + o1: 'openai', + o3: 'openai', + o4: 'openai', + + // Google/Gemini models + gemini: 'gemini', + palm: 'gemini', + 'text-bison': 'gemini', + 'chat-bison': 'gemini', + + // DeepInfra models + deepseek: 'deepinfra', + mistral: 'deepinfra', + llama: 'deepinfra', + mixtral: 'deepinfra', + qwen: 'deepinfra', + yi: 'deepinfra', + phi: 'deepinfra', + claude: 'deepinfra', + }; + + /** + * Initialize the provider with optional provider instances + */ + constructor({ + gemini, + openai, + deepseek, + ...otherProviders + }: { + gemini?: GoogleGenerativeAIProvider; + openai?: OpenAISDKProvider; + deepseek?: DeepInfraProvider; + [key: string]: any; + } = {}) { + if (gemini) this.providers.set('gemini', gemini); + if (openai) this.providers.set('openai', openai); + if (deepseek) this.providers.set('deepseek', deepseek); + + // Handle any additional providers + Object.entries(otherProviders).forEach(([key, provider]) => { + if (provider) this.providers.set(key, provider); + }); + + // Initialize providers from environment variables if they don't exist + this.initializeProvidersFromEnv(); + } + + /** + * Initialize providers from environment variables + */ + private initializeProvidersFromEnv(): void { + // Initialize OpenAI provider if not already set + if (!this.providers.has('openai') && process.env.OPENAI_API_KEY) { + try { + const openai = createOpenAI({ + apiKey: process.env.OPENAI_API_KEY, + }); + this.providers.set('openai', openai); + console.log('OpenAI provider initialized from environment variable'); + } catch (error) { + console.warn('Failed to initialize OpenAI provider:', error); + } + } + + // Initialize Gemini provider if not already set + if (!this.providers.has('gemini') && process.env.GEMINI_API_KEY) { + try { + const gemini = createGoogleGenerativeAI({ + apiKey: process.env.GEMINI_API_KEY, + }); + this.providers.set('gemini', gemini); + console.log('Gemini provider initialized from environment variable'); + } catch (error) { + console.warn('Failed to initialize Gemini provider:', error); + } + } + + // Initialize DeepInfra provider if not already set + if (!this.providers.has('deepinfra') && process.env.DEEPINFRA_API_KEY) { + try { + const deepinfra = createDeepInfra({ + apiKey: process.env.DEEPINFRA_API_KEY, + }); + this.providers.set('deepinfra', deepinfra); + console.log('DeepInfra provider initialized from environment variable'); + } catch (error) { + console.warn('Failed to initialize DeepInfra provider:', error); + } + } + } + + /** + * Add or replace a provider + */ + addProvider(name: string, provider: ProviderV1): void { + this.providers.set(name, provider); + } + + /** + * Add a direct model with a specific identifier + * This is for cases where the user provides a model instance directly + */ + addDirectProvider(id: string, model: LanguageModelV1): void { + this.directModels.set(id, model); + } + + /** + * Get a specific provider by name + */ + getProvider(name: string): ProviderV1 | undefined { + return this.providers.get(name); + } + + /** + * Map a model name to its provider + */ + private getProviderNameForModel(modelName: string): string { + // First check if the model name starts with a known provider prefix + const [prefix] = modelName.split('-'); + + // Check if we have a direct mapping for this prefix + if (this.modelToProviderMap[prefix]) { + return this.modelToProviderMap[prefix]; + } + + // Check if the model name contains a provider name + for (const [modelPrefix, providerName] of Object.entries( + this.modelToProviderMap + )) { + if (modelName.includes(modelPrefix)) { + return providerName; + } + } + + // If we couldn't determine the provider, check if we have any providers available + if (this.providers.has('openai')) { + return 'openai'; + } else if (this.providers.has('gemini')) { + return 'gemini'; + } else if (this.providers.has('deepinfra')) { + return 'deepinfra'; + } + + // Default to OpenAI if we can't determine the provider + return 'openai'; + } + + /** + * Generate text using the specified model or provider + * The model can be: + * 1. A direct LanguageModelV1 instance + * 2. A string ID for a stored direct model + * 3. A string model name (e.g., 'gpt-4o', 'gemini-1.5-pro') + */ + async generateText( + prompt: string, + modelOrProvider: string | LanguageModelV1 + ): Promise { + try { + // Case 1: Direct LanguageModelV1 instance + if (typeof modelOrProvider !== 'string') { + // If it's a direct model instance, use it directly + const result = await generateText({ + model: modelOrProvider, + prompt, + }); + return result.text; + } + + // Case 2: Check if it's a direct model ID we've stored + if (this.directModels.has(modelOrProvider)) { + const model = this.directModels.get(modelOrProvider); + if (!model) { + throw new Error(`Direct model '${modelOrProvider}' not found`); + } + const result = await generateText({ + model, // This is already a LanguageModelV1 + prompt, + }); + return result.text; + } + + // Case 3: String model name - determine the provider + const providerName = this.getProviderNameForModel(modelOrProvider); + const provider = this.providers.get(providerName); + + if (!provider) { + throw new Error( + `Provider '${providerName}' not found for model '${modelOrProvider}'. Please add it using addProvider method or set the appropriate API key in environment variables.` + ); + } + + try { + // Use the languageModel method to get the LanguageModelV1 instance + const model = provider.languageModel(modelOrProvider); + + const result = await generateText({ + model, // Now this is a LanguageModelV1 + prompt, + }); + + return result.text; + } catch (modelError) { + // If the model doesn't exist, try using a default model for the provider + console.warn( + `Model '${modelOrProvider}' not found, trying default model for ${providerName}` + ); + + let defaultModel: string; + switch (providerName) { + case 'openai': + defaultModel = 'gpt-4o'; + break; + case 'gemini': + defaultModel = 'gemini-2.0-flash'; + break; + case 'deepinfra': + defaultModel = 'deepseek-ai/DeepSeek-R1'; + break; + default: + throw modelError; + } + + const model = provider.languageModel(defaultModel); + const result = await generateText({ + model, + prompt, + }); + + return result.text; + } + } catch (error) { + throw new Error( + `Error generating text with ${String(modelOrProvider)}: ${ + error instanceof Error ? error.message : String(error) + }` + ); + } + } +} + +export default AIProvider; diff --git a/src/provider/deepseek.ts b/src/provider/deepseek.ts deleted file mode 100644 index 7ed8717..0000000 --- a/src/provider/deepseek.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { DeepInfraProvider, DeepInfraProviderSettings, createDeepInfra } from "@ai-sdk/deepinfra"; -import { generateText } from "ai"; - -export class DeepSeekProvider { - private client: DeepInfraProvider; - private static instance: DeepSeekProvider; - - private constructor(settings: DeepInfraProviderSettings) { - this.client = createDeepInfra({ - apiKey: settings.apiKey, - ...settings - }); - } - - public static getInstance(settings: DeepInfraProviderSettings): DeepSeekProvider { - if (!DeepSeekProvider.instance) { - DeepSeekProvider.instance = new DeepSeekProvider(settings); - } - return DeepSeekProvider.instance; - } - - async generateText(prompt: string, model: string): Promise { - const result = await generateText({ - model: this.client(model), - prompt: prompt, - }); - return result.text; - } -} - -export default DeepSeekProvider; diff --git a/src/provider/gemini.ts b/src/provider/gemini.ts deleted file mode 100644 index 192fbca..0000000 --- a/src/provider/gemini.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GoogleGenerativeAIProvider, GoogleGenerativeAIProviderSettings, createGoogleGenerativeAI} from "@ai-sdk/google"; -import { generateText } from "ai"; - -export class GeminiProvider { - private client: GoogleGenerativeAIProvider; - private static instance: GeminiProvider; - - private constructor(settings: GoogleGenerativeAIProviderSettings) { - this.client = createGoogleGenerativeAI({ - apiKey: settings.apiKey, - ...settings - }); - } - - public static getInstance(settings: GoogleGenerativeAIProviderSettings): GeminiProvider { - if (!GeminiProvider.instance) { - GeminiProvider.instance = new GeminiProvider(settings); - } - return GeminiProvider.instance; - } - - async generateText(prompt: string, model: string): Promise { - const result = await generateText({ - model: this.client(model), - prompt: prompt, - }); - return result.text; - } -} - -export default GeminiProvider; diff --git a/src/provider/jigsaw.ts b/src/provider/jigsaw.ts index 3022686..ecc510e 100644 --- a/src/provider/jigsaw.ts +++ b/src/provider/jigsaw.ts @@ -8,15 +8,15 @@ export class JigsawProvider { private static instance: JigsawProvider; private jigsawInstance: ReturnType; - private constructor() { + private constructor(apiKey?: string) { this.jigsawInstance = JigsawStack({ - apiKey: process.env.JIGSAW_API_KEY, + apiKey: apiKey || process.env.JIGSAW_API_KEY, }); } - public static getInstance(): JigsawProvider { + public static getInstance(apiKey?: string): JigsawProvider { if (!JigsawProvider.instance) { - JigsawProvider.instance = new JigsawProvider(); + JigsawProvider.instance = new JigsawProvider(apiKey); } return JigsawProvider.instance; } diff --git a/src/provider/openai.ts b/src/provider/openai.ts deleted file mode 100644 index c8650db..0000000 --- a/src/provider/openai.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * OpenAI provider - */ -import { createOpenAI, OpenAIProviderSettings } from "@ai-sdk/openai"; -import { generateText } from "ai"; -export class OpenAIProvider { - private static instance: OpenAIProvider; - private openai; - - private constructor(settings: OpenAIProviderSettings) { - this.openai = createOpenAI({ - apiKey: settings.apiKey || '', - }); - } - - public static getInstance(settings: OpenAIProviderSettings): OpenAIProvider { - if (!OpenAIProvider.instance) { - OpenAIProvider.instance = new OpenAIProvider(settings); - } - return OpenAIProvider.instance; - } - - async generateText(prompt: string, model: string): Promise { - const result = await generateText({ - model: this.openai(model), - prompt: prompt, - }); - return result.text; - } -} \ No newline at end of file diff --git a/src/synthesis/synthesizer.ts b/src/synthesis/synthesizer.ts index 8e17739..781c064 100644 --- a/src/synthesis/synthesizer.ts +++ b/src/synthesis/synthesizer.ts @@ -1,6 +1,7 @@ -import GeminiProvider from '../provider/gemini'; +import { AIProvider } from '../provider/aiProvider'; import { - FinalSynthesisInput, + ReportInput, + ReportConfig, SynthesisInput, SynthesisOutput, } from '../types/synthesis'; @@ -9,162 +10,149 @@ import { cleanJsonResponse } from '../utils/utils'; import 'dotenv/config'; import { generateSynthesisPrompt, - generateFinalSynthesisPrompt, + generateReportPrompt, } from '../prompts/synthesis'; -export class Synthesizer { - private geminiInstance: GeminiProvider; - - constructor() { - this.geminiInstance = GeminiProvider.getInstance({ - apiKey: process.env.GEMINI_API_KEY || '', - }); - } - - async synthesizeResults(input: SynthesisInput): Promise { - const { mainPrompt, results, currentDepth, parentSynthesis } = input; - - const { systemPrompt, userPrompt } = generateSynthesisPrompt({ - mainPrompt, - results, - currentDepth, - parentSynthesis, - }); - +/** + * Synthesize search results into a coherent analysis + */ +export async function synthesize( + input: SynthesisInput, + provider: AIProvider, + model: string = 'gemini-2.0-flash' +): Promise { + const { mainPrompt, results, currentDepth, parentSynthesis } = input; + + const { systemPrompt, userPrompt } = generateSynthesisPrompt({ + mainPrompt, + results, + currentDepth, + parentSynthesis, + }); + + try { + const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; + const response = await provider.generateText(combinedPrompt, model); + + let synthesis: SynthesisOutput; try { - const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; - const response = await this.geminiInstance.generateText( - combinedPrompt, - 'gemini-2.0-flash' + const cleanedResponse = cleanJsonResponse(response); + console.log(`Synthesis at depth ${currentDepth} completed`); + + synthesis = JSON.parse(cleanedResponse); + synthesis.depth = currentDepth; + } catch (parseError) { + console.error('Raw synthesis response:', response); + throw new Error( + `Failed to parse synthesis response as JSON: ${parseError}` ); - - let synthesis: SynthesisOutput; - try { - // Clean the response to handle markdown-formatted JSON - const cleanedResponse = cleanJsonResponse(response); - console.log(`Synthesis at depth ${currentDepth} completed`); - - synthesis = JSON.parse(cleanedResponse); - synthesis.depth = currentDepth; - } catch (parseError) { - console.error('Raw synthesis response:', response); - throw new Error( - `Failed to parse synthesis response as JSON: ${parseError}` - ); - } - - return synthesis; - } catch (error) { - console.error('Error generating synthesis:', error); - return this.generateDefaultSynthesis(mainPrompt, results, currentDepth); } - } - private generateDefaultSynthesis( - mainPrompt: string[], - results: WebSearchResult[], - currentDepth: number - ): SynthesisOutput { - return { - analysis: `Synthesis of ${ - results.length - } results related to ${mainPrompt.join(', ')}`, - keyThemes: ['Information insufficient for detailed synthesis'], - insights: ['Unable to generate insights due to processing error'], - knowledgeGaps: [ - 'Complete synthesis unavailable - further research needed', - ], - confidence: 0.3, - depth: currentDepth, - relatedQuestions: results.map((r) => r.question.question), - }; + return synthesis; + } catch (error) { + console.error('Error generating synthesis:', error); + return failedSynthesis(mainPrompt, results, currentDepth); } +} - async generateFinalSynthesis( - input: FinalSynthesisInput - ): Promise { - const { - mainPrompt, - allSyntheses = [], - maxOutputTokens, - targetOutputLength, - } = input; - - const { systemPrompt, userPrompt } = generateFinalSynthesisPrompt({ - mainPrompt, - allSyntheses, - maxOutputTokens, - targetOutputLength, - }); +/** + * Generate a default synthesis when the AI synthesis fails + */ +export function failedSynthesis( + mainPrompt: string[], + results: WebSearchResult[], + currentDepth: number +): SynthesisOutput { + return { + analysis: `Synthesis of ${ + results.length + } results related to ${mainPrompt.join(', ')}`, + keyThemes: ['Information insufficient for detailed synthesis'], + insights: ['Unable to generate insights due to processing error'], + knowledgeGaps: ['Complete synthesis unavailable - further research needed'], + confidence: 0.3, + depth: currentDepth, + relatedQuestions: results.map((r) => r.question.question), + }; +} +/** + * Generate a comprehensive research report from all syntheses + */ +export async function generateReport( + input: ReportInput, + config: ReportConfig, + provider: AIProvider, + model: string = 'gemini-2.0-flash' +): Promise { + const { mainPrompt, allSyntheses } = input; + const { maxOutputTokens, targetOutputLength } = config; + + const { systemPrompt, userPrompt } = generateReportPrompt({ + mainPrompt, + allSyntheses, + maxOutputTokens, + targetOutputLength, + }); + + try { + const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; + const response = await provider.generateText(combinedPrompt, model); + + let report: SynthesisOutput; try { - const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; - const response = await this.geminiInstance.generateText( - combinedPrompt, - 'gemini-2.0-flash' - ); - - console.log(`Raw synthesis response: ${response.substring(0, 200)}...`); - - let synthesis: SynthesisOutput; - try { - // Clean the response to handle markdown-formatted JSON - const cleanedResponse = cleanJsonResponse(response); - console.log(`Final synthesis completed`); - - synthesis = JSON.parse(cleanedResponse); - - // If we're getting a JSON metadata object without an analysis field, - // use the full markdown article as the analysis - if (!synthesis.analysis && response.length > 0) { - synthesis.analysis = response; - } - - synthesis.depth = 0; // 0 represents final synthesis - } catch (parseError) { - console.error('Error generating final synthesis:', parseError); - throw new Error( - `Failed to parse final synthesis response as JSON: ${parseError}` - ); - } - - return synthesis; - } catch (error) { - console.error('Error generating final synthesis:', error); - return this.generateDefaultSynthesis(mainPrompt, [], 0); + const cleanedResponse = cleanJsonResponse(response); + console.log('Research report generated'); + + report = JSON.parse(cleanedResponse); + report.depth = 0; // 0 represents final report + } catch (parseError) { + console.error('Error generating research report:', parseError); + throw new Error(`Failed to parse research report as JSON: ${parseError}`); } - } - async hasSufficientInformation( - input: SynthesisInput, - confidenceThreshold: number = 0.85 - ): Promise { - // If we have a parent synthesis with high confidence, we might have enough info - if ( - input.parentSynthesis && - input.parentSynthesis.confidence >= confidenceThreshold - ) { - // Check if we have a good variety of themes and insights - const hasSubstantiveContent = - input.parentSynthesis.keyThemes && - input.parentSynthesis.keyThemes.length >= 3 && - input.parentSynthesis.insights && - input.parentSynthesis.insights.length >= 3 && - input.parentSynthesis.knowledgeGaps && - input.parentSynthesis.knowledgeGaps.length <= 2; // Not too many knowledge gaps - - if (hasSubstantiveContent) { - return true; - } - } + return report; + } catch (error) { + console.error('Error generating research report:', error); + return failedSynthesis(mainPrompt, [], 0); + } +} - // If we have many results at the current depth, we might have enough info - if (input.results.length >= 5) { +/** + * Check if we have sufficient information to stop the research + * Does this content sufficient for the main questions asked + * Out of 5? + * Do you think that there can be more relevant questions that can be asked + * Should I go deeper or should + */ + +export async function hasSufficientInformation( + input: SynthesisInput, + confidenceThreshold: number = 0.85 +): Promise { + // If we have a parent synthesis with high confidence, we might have enough info + if ( + input.parentSynthesis && + input.parentSynthesis.confidence >= confidenceThreshold + ) { + // Check if we have a good variety of themes and insights + const hasSubstantiveContent = + input.parentSynthesis.keyThemes && + input.parentSynthesis.keyThemes.length >= 3 && + input.parentSynthesis.insights && + input.parentSynthesis.insights.length >= 3 && + input.parentSynthesis.knowledgeGaps && + input.parentSynthesis.knowledgeGaps.length <= 2; // Not too many knowledge gaps + + if (hasSubstantiveContent) { return true; } + } - return false; + // If we have many results at the current depth, we might have enough info + if (input.results.length >= 5) { + return true; } -} -export default Synthesizer; + return false; +} diff --git a/src/test-env.ts b/src/test-env.ts deleted file mode 100644 index 4643b1f..0000000 --- a/src/test-env.ts +++ /dev/null @@ -1,4 +0,0 @@ -import "dotenv/config"; -console.log("OPENAI_API_KEY:", process.env.OPENAI_API_KEY); -console.log("GOOGLE_AI_API_KEY:", process.env.GOOGLE_AI_API_KEY); -console.log("All env variables:", process.env); diff --git a/src/types/index.ts b/src/types/index.ts index b8265b9..0ea1090 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,6 @@ -import { SubQuestion, SubQuestionGeneratorResult } from './generators'; -import { SynthesisConfig, SynthesisOutput } from './synthesis'; +import { SubQuestion } from './generators'; +import { ReportConfig, SynthesisOutput } from './synthesis'; +import { LanguageModelV1 } from '@ai-sdk/provider'; export interface RecursiveResearchResult { isComplete: boolean; @@ -21,9 +22,11 @@ export interface ResearchResult { export type ModelType = 'default' | 'quick' | 'reasoning'; export interface ModelConfig { - default: string; - quick: string; - reasoning: string; + default?: string | LanguageModelV1; + quick?: string | LanguageModelV1; + reasoning?: string | LanguageModelV1; + output?: string | LanguageModelV1; + [key: string]: string | LanguageModelV1 | undefined; } export interface ResearchDepthConfig { @@ -41,27 +44,21 @@ export interface ResearchBreadthConfig { } export interface DeepResearchConfig { - prompt: string[]; depth?: Partial; breadth?: Partial; - format: 'json'; models?: Partial; - synthesis: SynthesisConfig; + synthesis: ReportConfig; + jigsawApiKey?: string; } export interface DeepResearchInstance { + prompts?: string[]; config: DeepResearchConfig; - generateSubQuestions(): Promise; - fireWebSearches( - subQuestions: SubQuestionGeneratorResult - ): Promise; - performRecursiveResearch( - initialResults: WebSearchResult[], - currentDepth?: number, - parentSynthesis?: SynthesisOutput - ): Promise; getSynthesis(): Map; - generateFinalSynthesis(): Promise; + generate( + prompt: string[], + format?: 'json' | 'markdown' + ): Promise; } export interface DeepResearchResponse { diff --git a/src/types/synthesis.ts b/src/types/synthesis.ts index d345bfa..99eebe0 100644 --- a/src/types/synthesis.ts +++ b/src/types/synthesis.ts @@ -1,43 +1,50 @@ import { WebSearchResult } from '.'; -export interface FinalSynthesisInput { +// Base interface for common synthesis properties +export interface BaseSynthesisInput { mainPrompt: string[]; - allSyntheses: SynthesisOutput[]; - maxOutputTokens?: number; - targetOutputLength: 'concise' | 'standard' | 'detailed' | number; } -export interface SynthesisInput { - mainPrompt: string[]; + +// Interface for synthesizing search results +export interface SynthesisInput extends BaseSynthesisInput { results: WebSearchResult[]; currentDepth: number; parentSynthesis?: SynthesisOutput; } +// Interface for generating the final research report +export interface ReportInput extends BaseSynthesisInput { + allSyntheses: SynthesisOutput[]; +} + +// Common output format for both synthesis and report export interface SynthesisOutput { analysis: string; keyThemes: string[]; insights: string[]; knowledgeGaps: string[]; - conflictingInformation?: { + conflictingInformation?: Array<{ topic: string; - conflicts: { + conflicts: Array<{ claim1: string; claim2: string; resolution?: string; - }[]; - }[]; + }>; + }>; confidence: number; depth: number; relatedQuestions: string[]; } -export interface SynthesisConfig { - maxOutputTokens: number; - targetOutputLength: FinalSynthesisInput['targetOutputLength']; +// Configuration for report generation +export interface ReportConfig { + maxOutputTokens?: number; + targetOutputLength: 'concise' | 'standard' | 'detailed' | number; formatAsMarkdown: boolean; } +// Overall synthesis result including all depth levels and final report export interface SynthesisResult { depthSynthesis: Map; - finalSynthesis: SynthesisOutput; + finalReport: SynthesisOutput; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index c6397d2..50e5719 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -6,8 +6,25 @@ export function cleanJsonResponse(response: string): string { try { // Extract and parse the JSON block const jsonContent = jsonBlockMatch[1].trim(); + // Test if it's valid JSON - JSON.parse(jsonContent); + const parsedJson = JSON.parse(jsonContent); + + // If we have a markdown report, add the full report content as the analysis + if (!parsedJson.analysis && response.indexOf('```json') > 0) { + // Extract the markdown content before the JSON block + const markdownContent = response + .substring(0, response.indexOf('```json')) + .trim(); + if (markdownContent.length > 0) { + console.log( + `Adding markdown content (${markdownContent.length} chars) as analysis` + ); + parsedJson.analysis = markdownContent; + return JSON.stringify(parsedJson); + } + } + return jsonContent; } catch (e) { console.error('Failed to parse JSON metadata block:', e); @@ -42,6 +59,21 @@ export function cleanJsonResponse(response: string): string { JSON.parse(potentialJson); return potentialJson; } + + // If we have a markdown report with no valid JSON, create a JSON with the content as analysis + if (response.length > 0 && !response.includes('')) { + console.log('Creating JSON with markdown content as analysis'); + const jsonWithContent = JSON.stringify({ + analysis: response, + keyThemes: ['Generated from markdown content'], + insights: ['Content extracted from markdown'], + knowledgeGaps: [], + confidence: 0.8, + relatedQuestions: [], + depth: 0, + }); + return jsonWithContent; + } } catch (e) { // If parsing fails, try to fix common issues with JSON strings console.log('JSON parsing failed, attempting to fix common issues...');