diff --git a/.changeset/seven-ligers-march.md b/.changeset/seven-ligers-march.md new file mode 100644 index 0000000000000000000000000000000000000000..55f6dcfcb79404e5a3e404f7ce688546c45aced6 --- /dev/null +++ b/.changeset/seven-ligers-march.md @@ -0,0 +1,5 @@ +--- +"@llamaindex/tools": patch +--- + +feat: @llamaindex/tools diff --git a/examples/agent/wiki.ts b/examples/agent/wiki.ts index 70d1cc1d9cab1489767edb46efec5796ae4bc486..f695943038258c44e074db6cb724d45c4b459a37 100644 --- a/examples/agent/wiki.ts +++ b/examples/agent/wiki.ts @@ -1,13 +1,12 @@ import { OpenAI } from "@llamaindex/openai"; +import { wiki } from "@llamaindex/tools"; import { AgentStream, agent } from "llamaindex"; -import { WikipediaTool } from "../wiki"; async function main() { const llm = new OpenAI({ model: "gpt-4-turbo" }); - const wikiTool = new WikipediaTool(); const workflow = agent({ - tools: [wikiTool], + tools: [wiki()], llm, verbose: false, }); diff --git a/examples/agentworkflow/blog-writer.ts b/examples/agentworkflow/blog-writer.ts index d72cfd778a9c395944f496733007d4d290ec8a31..154aceca7d7984da35e07ccf5b660b9d3a2311fa 100644 --- a/examples/agentworkflow/blog-writer.ts +++ b/examples/agentworkflow/blog-writer.ts @@ -10,7 +10,7 @@ import { import os from "os"; import { z } from "zod"; -import { WikipediaTool } from "../wiki"; +import { wiki } from "@llamaindex/tools"; const llm = openai({ model: "gpt-4o-mini", }); @@ -46,7 +46,7 @@ async function main() { description: "Responsible for gathering relevant information from the internet", systemPrompt: `You are a research agent. Your role is to gather information from the internet using the provided tools and then transfer this information to the report agent for content creation.`, - tools: [new WikipediaTool()], + tools: [wiki()], canHandoffTo: [reportAgent], llm, }); diff --git a/examples/anthropic/agent.ts b/examples/anthropic/agent.ts index 8f0cf2205c231c71ad35385a83f33ae93eed9ada..349beb6f19f4ce5d666fc3ad589f0744da6d76e6 100644 --- a/examples/anthropic/agent.ts +++ b/examples/anthropic/agent.ts @@ -1,7 +1,7 @@ import { anthropic } from "@llamaindex/anthropic"; +import { wiki } from "@llamaindex/tools"; import { agent, tool } from "llamaindex"; import { z } from "zod"; -import { WikipediaTool } from "../wiki"; const workflow = agent({ tools: [ @@ -13,7 +13,7 @@ const workflow = agent({ }), execute: ({ location }) => `The weather in ${location} is sunny`, }), - new WikipediaTool(), + wiki(), ], llm: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, diff --git a/examples/mistral/agent.ts b/examples/mistral/agent.ts index a8e4ee649232225967fced9342a104b0db2aebc9..c923d6a189949afd0943d742331103c91f336871 100644 --- a/examples/mistral/agent.ts +++ b/examples/mistral/agent.ts @@ -1,7 +1,7 @@ import { mistral } from "@llamaindex/mistral"; +import { wiki } from "@llamaindex/tools"; import { agent, tool } from "llamaindex"; import { z } from "zod"; -import { WikipediaTool } from "../wiki"; const workflow = agent({ tools: [ @@ -13,7 +13,7 @@ const workflow = agent({ }), execute: ({ location }) => `The weather in ${location} is sunny`, }), - new WikipediaTool(), + wiki(), ], llm: mistral({ apiKey: process.env.MISTRAL_API_KEY, diff --git a/examples/package.json b/examples/package.json index 912a7bf14ccedb13f433d463573927ee6ccea128..4b8eace580fafa3c80f1650407a1ce2238814065 100644 --- a/examples/package.json +++ b/examples/package.json @@ -49,6 +49,7 @@ "@llamaindex/together": "^0.0.5", "@llamaindex/jinaai": "^0.0.5", "@llamaindex/perplexity": "^0.0.2", + "@llamaindex/tools": "^0.0.1", "@notionhq/client": "^2.2.15", "@pinecone-database/pinecone": "^4.0.0", "@vercel/postgres": "^0.10.0", diff --git a/examples/vercel/llm.ts b/examples/vercel/llm.ts index 302a53358bee752dcbd92fff6344353a0a298982..850842ccacc2786e4783b711dc46cddcb813750d 100644 --- a/examples/vercel/llm.ts +++ b/examples/vercel/llm.ts @@ -1,7 +1,7 @@ import { openai } from "@ai-sdk/openai"; +import { wiki } from "@llamaindex/tools"; import { VercelLLM } from "@llamaindex/vercel"; import { LLMAgent } from "llamaindex"; -import { WikipediaTool } from "../wiki"; async function main() { // Create an instance of VercelLLM with the OpenAI model @@ -33,7 +33,7 @@ async function main() { console.log("\n=== Test 3: Using LLMAgent with WikipediaTool ==="); const agent = new LLMAgent({ llm: vercelLLM, - tools: [new WikipediaTool()], + tools: [wiki()], }); const { message } = await agent.chat({ diff --git a/examples/wiki.ts b/examples/wiki.ts deleted file mode 100644 index f522011ef33d52135f4bff0f14a85713e0b41f59..0000000000000000000000000000000000000000 --- a/examples/wiki.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** Example of a tool that uses Wikipedia */ - -import type { JSONSchemaType } from "ajv"; -import type { BaseTool, ToolMetadata } from "llamaindex"; -import { default as wiki } from "wikipedia"; - -type WikipediaParameter = { - query: string; - lang?: string; -}; - -type WikipediaToolParams = { - metadata?: ToolMetadata<JSONSchemaType<WikipediaParameter>>; -}; - -const DEFAULT_META_DATA: ToolMetadata<JSONSchemaType<WikipediaParameter>> = { - name: "wikipedia_search", - description: "A tool that uses a query engine to search Wikipedia.", - parameters: { - type: "object", - properties: { - query: { - type: "string", - description: "The query to search for", - }, - lang: { - type: "string", - description: "The language to search in", - nullable: true, - }, - }, - required: ["query"], - }, -}; - -export class WikipediaTool implements BaseTool<WikipediaParameter> { - private readonly DEFAULT_LANG = "en"; - metadata: ToolMetadata<JSONSchemaType<WikipediaParameter>>; - - constructor(params?: WikipediaToolParams) { - this.metadata = params?.metadata || DEFAULT_META_DATA; - } - - async loadData( - page: string, - lang: string = this.DEFAULT_LANG, - ): Promise<string> { - wiki.setLang(lang); - const pageResult = await wiki.page(page, { autoSuggest: false }); - const content = await pageResult.content(); - return content; - } - - async call({ - query, - lang = this.DEFAULT_LANG, - }: WikipediaParameter): Promise<string> { - const searchResult = await wiki.search(query); - if (searchResult.results.length === 0) return "No search results."; - return await this.loadData(searchResult.results[0].title, lang); - } -} diff --git a/packages/tools/.gitignore b/packages/tools/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9b1960e711fc1719053c5908bb5ff3cc321789e5 --- /dev/null +++ b/packages/tools/.gitignore @@ -0,0 +1 @@ +output/ \ No newline at end of file diff --git a/packages/tools/package.json b/packages/tools/package.json new file mode 100644 index 0000000000000000000000000000000000000000..8eec15934819bab93fef2ac7cc0c8cf761e39410 --- /dev/null +++ b/packages/tools/package.json @@ -0,0 +1,54 @@ +{ + "name": "@llamaindex/tools", + "description": "LlamaIndex Tools", + "version": "0.0.1", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "exports": { + ".": { + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + }, + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/run-llama/LlamaIndexTS.git", + "directory": "packages/tools" + }, + "scripts": { + "build": "bunchee", + "dev": "bunchee --watch", + "test": "vitest run", + "test:watch": "vitest watch" + }, + "devDependencies": { + "bunchee": "6.4.0", + "vitest": "^2.1.5", + "@types/node": "^22.9.0", + "@types/papaparse": "^5.3.15" + }, + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@llamaindex/core": "workspace:*", + "@llamaindex/env": "workspace:*", + "@e2b/code-interpreter": "^1.0.4", + "duck-duck-scrape": "^2.2.5", + "papaparse": "^5.4.1", + "marked": "^14.1.2", + "got": "^14.4.1", + "formdata-node": "^6.0.3", + "ajv": "^8.12.0", + "wikipedia": "^2.1.2", + "zod": "^3.23.8" + } +} diff --git a/packages/tools/src/helper.ts b/packages/tools/src/helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..c62c2abd53f540b5f26118c48ab9363f0903b1d6 --- /dev/null +++ b/packages/tools/src/helper.ts @@ -0,0 +1,35 @@ +import fs from "node:fs"; +import path from "node:path"; + +export async function saveDocument(filePath: string, content: string | Buffer) { + if (path.isAbsolute(filePath)) { + throw new Error("Absolute file paths are not allowed."); + } + + const dirPath = path.dirname(filePath); + await fs.promises.mkdir(dirPath, { recursive: true }); + + if (typeof content === "string") { + await fs.promises.writeFile(filePath, content, "utf-8"); + } else { + await fs.promises.writeFile(filePath, content); + } +} + +export function getFileUrl( + filePath: string, + options?: { + fileServerURLPrefix?: string | undefined; + }, +): string { + const fileServerURLPrefix = + options?.fileServerURLPrefix || process.env.FILESERVER_URL_PREFIX; + + if (!fileServerURLPrefix) { + throw new Error( + "To get a file URL, please provide a fileServerURLPrefix or set FILESERVER_URL_PREFIX environment variable.", + ); + } + + return `${fileServerURLPrefix}/${filePath}`; +} diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..61827f32bb1ea6d21c9c6d8e430485258a2a6432 --- /dev/null +++ b/packages/tools/src/index.ts @@ -0,0 +1,9 @@ +export * from "./tools/code-generator"; +export * from "./tools/document-generator"; +export * from "./tools/duckduckgo"; +export * from "./tools/form-filling"; +export * from "./tools/img-gen"; +export * from "./tools/interpreter"; +export * from "./tools/openapi-action"; +export * from "./tools/weather"; +export * from "./tools/wiki"; diff --git a/packages/tools/src/tools/code-generator.ts b/packages/tools/src/tools/code-generator.ts new file mode 100644 index 0000000000000000000000000000000000000000..e98cf20f1d1a10cf2024b0e538ceecbd205ee30c --- /dev/null +++ b/packages/tools/src/tools/code-generator.ts @@ -0,0 +1,126 @@ +import { Settings, type JSONValue } from "@llamaindex/core/global"; +import type { ChatMessage } from "@llamaindex/core/llms"; +import { tool } from "@llamaindex/core/tools"; +import { z } from "zod"; + +// prompt based on https://github.com/e2b-dev/ai-artifacts +const CODE_GENERATION_PROMPT = `You are a skilled software engineer. You do not make mistakes. Generate an artifact. You can install additional dependencies. You can use one of the following templates:\n + +1. code-interpreter-multilang: "Runs code as a Jupyter notebook cell. Strong data analysis angle. Can use complex visualisation to explain results.". File: script.py. Dependencies installed: python, jupyter, numpy, pandas, matplotlib, seaborn, plotly. Port: none. + +2. nextjs-developer: "A Next.js 13+ app that reloads automatically. Using the pages router.". File: pages/index.tsx. Dependencies installed: nextjs@14.2.5, typescript, @types/node, @types/react, @types/react-dom, postcss, tailwindcss, shadcn. Port: 3000. + +3. vue-developer: "A Vue.js 3+ app that reloads automatically. Only when asked specifically for a Vue app.". File: app.vue. Dependencies installed: vue@latest, nuxt@3.13.0, tailwindcss. Port: 3000. + +4. streamlit-developer: "A streamlit app that reloads automatically.". File: app.py. Dependencies installed: streamlit, pandas, numpy, matplotlib, request, seaborn, plotly. Port: 8501. + +5. gradio-developer: "A gradio app. Gradio Blocks/Interface should be called demo.". File: app.py. Dependencies installed: gradio, pandas, numpy, matplotlib, request, seaborn, plotly. Port: 7860. + +Provide detail information about the artifact you're about to generate in the following JSON format with the following keys: + +commentary: Describe what you're about to do and the steps you want to take for generating the artifact in great detail. +template: Name of the template used to generate the artifact. +title: Short title of the artifact. Max 3 words. +description: Short description of the artifact. Max 1 sentence. +additional_dependencies: Additional dependencies required by the artifact. Do not include dependencies that are already included in the template. +has_additional_dependencies: Detect if additional dependencies that are not included in the template are required by the artifact. +install_dependencies_command: Command to install additional dependencies required by the artifact. +port: Port number used by the resulted artifact. Null when no ports are exposed. +file_path: Relative path to the file, including the file name. +code: Code generated by the artifact. Only runnable code is allowed. + +Make sure to use the correct syntax for the programming language you're using. Make sure to generate only one code file. If you need to use CSS, make sure to include the CSS in the code file using Tailwind CSS syntax. +`; + +// detail information to execute code +export type CodeArtifact = { + commentary: string; + template: string; + title: string; + description: string; + additional_dependencies: string[]; + has_additional_dependencies: boolean; + install_dependencies_command: string; + port: number | null; + file_path: string; + code: string; + files?: string[]; +}; + +export type CodeGeneratorToolOutput = { + isError: boolean; + artifact?: CodeArtifact; +}; + +// Helper function +async function generateArtifact( + query: string, + oldCode?: string, + attachments?: string[], +): Promise<CodeArtifact> { + const userMessage = ` + ${query} + ${oldCode ? `The existing code is: \n\`\`\`${oldCode}\`\`\`` : ""} + ${attachments ? `The attachments are: \n${attachments.join("\n")}` : ""} + `; + const messages: ChatMessage[] = [ + { role: "system", content: CODE_GENERATION_PROMPT }, + { role: "user", content: userMessage }, + ]; + try { + const response = await Settings.llm.chat({ messages }); + const content = response.message.content.toString(); + const jsonContent = content + .replace(/^```json\s*|\s*```$/g, "") + .replace(/^`+|`+$/g, "") + .trim(); + const artifact = JSON.parse(jsonContent) as CodeArtifact; + return artifact; + } catch (error) { + console.log("Failed to generate artifact", error); + throw error; + } +} + +export const codeGenerator = () => { + return tool({ + name: "artifact", + description: + "Generate a code artifact based on the input. Don't call this tool if the user has not asked for code generation. E.g. if the user asks to write a description or specification, don't call this tool.", + parameters: z.object({ + requirement: z + .string() + .describe("The description of the application you want to build."), + oldCode: z + .string() + .optional() + .describe("The existing code to be modified"), + sandboxFiles: z + .array(z.string()) + .optional() + .describe( + "A list of sandbox file paths. Include these files if the code requires them.", + ), + }), + execute: async ({ requirement, oldCode, sandboxFiles }) => { + try { + const artifact = await generateArtifact( + requirement, + oldCode, + sandboxFiles, // help the generated code use exact files + ); + if (sandboxFiles) { + artifact.files = sandboxFiles; + } + return { + isError: false, + artifact, + } as JSONValue; + } catch (error) { + return { + isError: true, + }; + } + }, + }); +}; diff --git a/packages/tools/src/tools/document-generator.ts b/packages/tools/src/tools/document-generator.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b52ada6cc5d4d581ade9f74f4673c31f2e1c79f --- /dev/null +++ b/packages/tools/src/tools/document-generator.ts @@ -0,0 +1,112 @@ +import { tool } from "@llamaindex/core/tools"; +import { marked } from "marked"; +import path from "node:path"; +import { z } from "zod"; +import { getFileUrl, saveDocument } from "../helper"; + +const COMMON_STYLES = ` + body { + font-family: Arial, sans-serif; + line-height: 1.3; + color: #333; + } + h1, h2, h3, h4, h5, h6 { + margin-top: 1em; + margin-bottom: 0.5em; + } + p { + margin-bottom: 0.7em; + } + code { + background-color: #f4f4f4; + padding: 2px 4px; + border-radius: 4px; + } + pre { + background-color: #f4f4f4; + padding: 10px; + border-radius: 4px; + overflow-x: auto; + } + table { + border-collapse: collapse; + width: 100%; + margin-bottom: 1em; + } + th, td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; + } + th { + background-color: #f2f2f2; + font-weight: bold; + } + img { + max-width: 90%; + height: auto; + display: block; + margin: 1em auto; + border-radius: 10px; + } +`; + +const HTML_SPECIFIC_STYLES = ` + body { + max-width: 800px; + margin: 0 auto; + padding: 20px; + } +`; + +const HTML_TEMPLATE = ` +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <style> + ${COMMON_STYLES} + ${HTML_SPECIFIC_STYLES} + </style> +</head> +<body> + {{content}} +</body> +</html> +`; + +export type DocumentGeneratorParams = { + /** Directory where generated documents will be saved */ + outputDir: string; + /** Prefix for the file server URL */ + fileServerURLPrefix?: string; +}; + +export const documentGenerator = (params: DocumentGeneratorParams) => { + return tool({ + name: "document_generator", + description: + "Generate HTML document from markdown content. Return a file url to the document", + parameters: z.object({ + originalContent: z + .string() + .describe("The original markdown content to convert"), + fileName: z + .string() + .describe("The name of the document file (without extension)"), + }), + execute: async ({ originalContent, fileName }): Promise<string> => { + const { outputDir, fileServerURLPrefix } = params; + + const htmlContent = await marked(originalContent); + const fileContent = HTML_TEMPLATE.replace("{{content}}", htmlContent); + + const filePath = path.join(outputDir, `${fileName}.html`); + await saveDocument(filePath, fileContent); + const fileUrl = getFileUrl(filePath, { fileServerURLPrefix }); + + return `URL: ${fileUrl}`; + }, + }); +}; diff --git a/packages/tools/src/tools/duckduckgo.ts b/packages/tools/src/tools/duckduckgo.ts new file mode 100644 index 0000000000000000000000000000000000000000..b2fc585d77ee2568695e76dd34680c84ebfae274 --- /dev/null +++ b/packages/tools/src/tools/duckduckgo.ts @@ -0,0 +1,48 @@ +import { tool } from "@llamaindex/core/tools"; +import { search } from "duck-duck-scrape"; +import { z } from "zod"; + +export type DuckDuckGoToolOutput = Array<{ + title: string; + description: string; + url: string; +}>; + +export const duckduckgo = () => { + return tool({ + name: "duckduckgo_search", + description: + "Use this function to search for information (only text) in the internet using DuckDuckGo.", + parameters: z.object({ + query: z.string().describe("The query to search in DuckDuckGo."), + region: z + .string() + .optional() + .describe( + "Optional, The region to be used for the search in [country-language] convention, ex us-en, uk-en, ru-ru, etc...", + ), + maxResults: z + .number() + .default(10) + .optional() + .describe( + "Optional, The maximum number of results to be returned. Default is 10.", + ), + }), + execute: async ({ + query, + region, + maxResults = 10, + }): Promise<DuckDuckGoToolOutput> => { + const options = region ? { region } : {}; + const searchResults = await search(query, options); + return searchResults.results.slice(0, maxResults).map((result) => { + return { + title: result.title, + description: result.description, + url: result.url, + }; + }); + }, + }); +}; diff --git a/packages/tools/src/tools/form-filling.ts b/packages/tools/src/tools/form-filling.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8ab8e54eaa231f871285b3590a8752bf5a42133 --- /dev/null +++ b/packages/tools/src/tools/form-filling.ts @@ -0,0 +1,234 @@ +import { Settings } from "@llamaindex/core/global"; +import { tool } from "@llamaindex/core/tools"; +import fs from "fs"; +import Papa from "papaparse"; +import path from "path"; +import { z } from "zod"; +import { getFileUrl, saveDocument } from "../helper"; + +export type MissingCell = { + rowIndex: number; + columnIndex: number; + question: string; +}; + +const CSV_EXTRACTION_PROMPT = `You are a data analyst. You are given a table with missing cells. +Your task is to identify the missing cells and the questions needed to fill them. +IMPORTANT: Column indices should be 0-based + +# Instructions: +- Understand the entire content of the table and the topics of the table. +- Identify the missing cells and the meaning of the data in the cells. +- For each missing cell, provide the row index and the correct column index (remember: first data column is 1). +- For each missing cell, provide the question needed to fill the cell (it's important to provide the question that is relevant to the topic of the table). +- Since the cell's value should be concise, the question should request a numerical answer or a specific value. +- Finally, only return the answer in JSON format with the following schema: +{ + "missing_cells": [ + { + "rowIndex": number, + "columnIndex": number, + "question": string + } + ] +} +- If there are no missing cells, return an empty array. +- The answer is only the JSON object, nothing else and don't wrap it inside markdown code block. + +# Example: +# | | Name | Age | City | +# |----|------|-----|------| +# | 0 | John | | Paris| +# | 1 | Mary | | | +# | 2 | | 30 | | +# +# Your thoughts: +# - The table is about people's names, ages, and cities. +# - Row: 1, Column: 2 (Age column), Question: "How old is Mary? Please provide only the numerical answer." +# - Row: 1, Column: 3 (City column), Question: "In which city does Mary live? Please provide only the city name." +# Your answer: +# { +# "missing_cells": [ +# { +# "rowIndex": 1, +# "columnIndex": 2, +# "question": "How old is Mary? Please provide only the numerical answer." +# }, +# { +# "rowIndex": 1, +# "columnIndex": 3, +# "question": "In which city does Mary live? Please provide only the city name." +# } +# ] +# } + + +# Here is your task: + +- Table content: +{table_content} + +- Your answer: +`; + +export const extractMissingCells = () => { + return tool({ + name: "extract_missing_cells", + description: + "Use this tool to extract missing cells in a CSV file and generate questions to fill them. This tool only works with local file path.", + parameters: z.object({ + filePath: z.string().describe("The local file path to the CSV file."), + }), + execute: async ({ filePath }): Promise<MissingCell[]> => { + let tableContent: string[][]; + try { + tableContent = await readCsvFile(filePath); + } catch (error) { + throw new Error( + "Failed to read CSV file. Make sure that you are reading a local file path (not a sandbox path).", + ); + } + + const prompt = CSV_EXTRACTION_PROMPT.replace( + "{table_content}", + formatToMarkdownTable(tableContent), + ); + + const llm = Settings.llm; + const response = await llm.complete({ prompt }); + const parsedResponse = JSON.parse(response.text) as { + missing_cells: MissingCell[]; + }; + if (!parsedResponse.missing_cells) { + throw new Error( + "The answer is not in the correct format. There should be a missing_cells array.", + ); + } + return parsedResponse.missing_cells; + }, + }); +}; + +export type FillMissingCellsParams = { + /** Directory where generated documents will be saved */ + outputDir: string; + + /** Prefix for the file server URL */ + fileServerURLPrefix?: string; +}; + +export type FillMissingCellsToolOutput = { + isSuccess: boolean; + errorMessage?: string; + fileUrl?: string; +}; + +export const fillMissingCells = (params: FillMissingCellsParams) => { + return tool({ + name: "fill_missing_cells", + description: + "Use this tool to fill missing cells in a CSV file with provided answers. This tool only works with local file path.", + parameters: z.object({ + filePath: z.string().describe("The local file path to the CSV file."), + cells: z + .array( + z.object({ + rowIndex: z.number(), + columnIndex: z.number(), + answer: z.string(), + }), + ) + .describe("Array of cells to fill with their answers"), + }), + execute: async ({ filePath, cells }): Promise<string> => { + const { outputDir, fileServerURLPrefix } = params; + + // Read the CSV file + const fileContent = await fs.promises.readFile(filePath, "utf8"); + + // Parse CSV with PapaParse + const parseResult = Papa.parse<string[]>(fileContent, { + header: false, // Ensure the header is not treated as a separate object + skipEmptyLines: false, // Ensure empty lines are not skipped + }); + + if (parseResult.errors.length) { + throw new Error( + "Failed to parse CSV file: " + parseResult.errors[0]?.message, + ); + } + + const rows = parseResult.data; + + // Fill the cells with answers + for (const cell of cells) { + // Adjust rowIndex to start from 1 for data rows + const adjustedRowIndex = cell.rowIndex + 1; + if ( + adjustedRowIndex < rows.length && + cell.columnIndex < (rows[adjustedRowIndex]?.length ?? 0) && + rows[adjustedRowIndex] + ) { + rows[adjustedRowIndex][cell.columnIndex] = cell.answer; + } + } + + // Convert back to CSV format + const updatedContent = Papa.unparse(rows, { + delimiter: parseResult.meta.delimiter, + }); + + // Use the helper function to write the file + const parsedPath = path.parse(filePath); + const newFileName = `${parsedPath.name}-filled${parsedPath.ext}`; + const newFilePath = path.join(outputDir, newFileName); + + await saveDocument(newFilePath, updatedContent); + const newFileUrl = getFileUrl(newFilePath, { fileServerURLPrefix }); + + return ( + "Successfully filled missing cells in the CSV file. File URL to show to the user: " + + newFileUrl + ); + }, + }); +}; + +async function readCsvFile(filePath: string): Promise<string[][]> { + return new Promise((resolve, reject) => { + fs.readFile(filePath, "utf8", (err, data) => { + if (err) { + reject(err); + return; + } + + const parsedData = Papa.parse<string[]>(data, { + skipEmptyLines: false, + }); + + if (parsedData.errors.length) { + reject(parsedData.errors); + return; + } + + // Ensure all rows have the same number of columns as the header + const maxColumns = parsedData.data[0]?.length ?? 0; + const paddedRows = parsedData.data.map((row) => { + return [...row, ...Array(maxColumns - row.length).fill("")]; + }); + + resolve(paddedRows); + }); + }); +} + +function formatToMarkdownTable(data: string[][]): string { + if (data.length === 0) return ""; + + const maxColumns = data[0]?.length ?? 0; + const headerRow = `| ${data[0]?.join(" | ") ?? ""} |`; + const separatorRow = `| ${Array(maxColumns).fill("---").join(" | ")} |`; + const dataRows = data.slice(1).map((row) => `| ${row.join(" | ")} |`); + + return [headerRow, separatorRow, ...dataRows].join("\n"); +} diff --git a/packages/tools/src/tools/img-gen.ts b/packages/tools/src/tools/img-gen.ts new file mode 100644 index 0000000000000000000000000000000000000000..b46de53d338fde6d85017acc3db91459745fd3e7 --- /dev/null +++ b/packages/tools/src/tools/img-gen.ts @@ -0,0 +1,76 @@ +import { tool } from "@llamaindex/core/tools"; +import { FormData } from "formdata-node"; +import got from "got"; +import path from "path"; +import { Readable } from "stream"; +import { z } from "zod"; +import { getFileUrl, saveDocument } from "../helper"; + +export type ImgGeneratorToolOutput = { + isSuccess: boolean; + imageUrl?: string; + errorMessage?: string; +}; + +export type ImgGeneratorToolParams = { + /** Directory where generated images will be saved */ + outputDir: string; + /** STABILITY_API_KEY key is required to run image generator. Get it here: https://platform.stability.ai/account/keys */ + apiKey: string; + /** Output format of the generated image */ + outputFormat?: string; + /** Prefix for the file server URL */ + fileServerURLPrefix?: string | undefined; +}; + +export const imageGenerator = (params: ImgGeneratorToolParams) => { + return tool({ + name: "image_generator", + description: "Use this function to generate an image based on the prompt.", + parameters: z.object({ + prompt: z.string().describe("The prompt to generate the image"), + }), + execute: async ({ prompt }): Promise<ImgGeneratorToolOutput> => { + const { outputDir, apiKey, fileServerURLPrefix } = params; + const outputFormat = params.outputFormat ?? "webp"; + + try { + const buffer = await promptToImgBuffer(prompt, apiKey, outputFormat); + const filename = `${crypto.randomUUID()}.${outputFormat}`; + const filePath = path.join(outputDir, filename); + await saveDocument(filePath, buffer); + const imageUrl = getFileUrl(filePath, { fileServerURLPrefix }); + return { isSuccess: true, imageUrl }; + } catch (error) { + console.error(error); + return { + isSuccess: false, + errorMessage: "Failed to generate image. Please try again.", + }; + } + }, + }); +}; + +async function promptToImgBuffer( + prompt: string, + apiKey: string, + outputFormat: string, +): Promise<Buffer> { + const form = new FormData(); + form.append("prompt", prompt); + form.append("output_format", outputFormat); + + const apiUrl = "https://api.stability.ai/v2beta/stable-image/generate/core"; + const buffer = await got + .post(apiUrl, { + body: form as unknown as Buffer | Readable | string, + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: "image/*", + }, + }) + .buffer(); + + return buffer; +} diff --git a/packages/tools/src/tools/interpreter.ts b/packages/tools/src/tools/interpreter.ts new file mode 100644 index 0000000000000000000000000000000000000000..364cdf6f425dc65c9acab0126717b37b28c88f85 --- /dev/null +++ b/packages/tools/src/tools/interpreter.ts @@ -0,0 +1,161 @@ +import { type Logs, Result, Sandbox } from "@e2b/code-interpreter"; +import { tool } from "@llamaindex/core/tools"; +import fs from "fs"; +import path from "node:path"; +import { z } from "zod"; +import { getFileUrl, saveDocument } from "../helper"; + +export type InterpreterExtraType = + | "html" + | "markdown" + | "svg" + | "png" + | "jpeg" + | "pdf" + | "latex" + | "json" + | "javascript"; + +export type InterpreterExtraResult = { + type: InterpreterExtraType; + content?: string; + filename?: string; + url?: string; +}; + +export type InterpreterToolOutput = { + isError: boolean; + logs: Logs; + text?: string; + extraResult: InterpreterExtraResult[]; + retryCount?: number; +}; + +export type InterpreterToolParams = { + /** E2B API key required for authentication. Get yours at https://e2b.dev/docs/legacy/getting-started/api-key */ + apiKey: string; + /** Directory where output files (charts, images, etc.) will be saved when code is executed */ + outputDir: string; + /** Local directory containing files that need to be uploaded to the sandbox environment before code execution */ + uploadedFilesDir: string; + /** Prefix for the file server URL */ + fileServerURLPrefix?: string; +}; + +export const interpreter = (params: InterpreterToolParams) => { + const { apiKey, outputDir, uploadedFilesDir, fileServerURLPrefix } = params; + + return tool({ + name: "interpreter", + description: + "Execute python code in a Jupyter notebook cell and return any result, stdout, stderr, display_data, and error.", + parameters: z.object({ + code: z.string().describe("The python code to execute in a single cell"), + sandboxFiles: z + .array(z.string()) + .optional() + .describe("List of local file paths to be used by the code"), + retryCount: z + .number() + .default(0) + .optional() + .describe("The number of times the tool has been retried"), + }), + execute: async ({ code, sandboxFiles, retryCount = 0 }) => { + if (retryCount >= 3) { + return { + isError: true, + logs: { stdout: [], stderr: [] }, + text: "Max retries reached", + extraResult: [], + }; + } + + const interpreter = await Sandbox.create({ apiKey }); + await uploadFilesToSandbox(interpreter, uploadedFilesDir, sandboxFiles); + const exec = await interpreter.runCode(code); + const extraResult = await getExtraResult( + outputDir, + exec.results[0], + fileServerURLPrefix, + ); + + return { + isError: !!exec.error, + logs: exec.logs, + text: exec.text, + extraResult, + retryCount: retryCount + 1, + } as InterpreterToolOutput; + }, + }); +}; + +async function uploadFilesToSandbox( + codeInterpreter: Sandbox, + uploadedFilesDir: string, + sandboxFiles: string[] = [], +) { + try { + for (const filePath of sandboxFiles) { + const fileName = path.basename(filePath); + const localFilePath = path.join(uploadedFilesDir, fileName); + const content = fs.readFileSync(localFilePath); + const arrayBuffer = new Uint8Array(content).buffer; + await codeInterpreter.files.write(filePath, arrayBuffer); + } + } catch (error) { + console.error("Got error when uploading files to sandbox", error); + } +} + +async function getExtraResult( + outputDir: string, + res?: Result, + fileServerURLPrefix?: string, +): Promise<InterpreterExtraResult[]> { + if (!res) return []; + const output: InterpreterExtraResult[] = []; + + try { + const formats = res.formats(); + const results = formats.map((f) => res[f as keyof Result]); + + for (let i = 0; i < formats.length; i++) { + const ext = formats[i]; + const data = results[i]; + switch (ext) { + case "png": + case "jpeg": + case "svg": + case "pdf": { + const { filename, filePath } = await saveToDisk(outputDir, data, ext); + const fileUrl = getFileUrl(filePath, { fileServerURLPrefix }); + output.push({ + type: ext as InterpreterExtraType, + filename, + url: fileUrl, + }); + break; + } + default: + output.push({ + type: ext as InterpreterExtraType, + content: data, + }); + break; + } + } + } catch (error) { + console.error("Error when parsing e2b response", error); + } + return output; +} + +async function saveToDisk(outputDir: string, base64Data: string, ext: string) { + const filename = `${crypto.randomUUID()}.${ext}`; + const buffer = Buffer.from(base64Data, "base64"); + const filePath = path.join(outputDir, filename); + await saveDocument(filePath, buffer); + return { filename, filePath }; +} diff --git a/packages/tools/src/tools/openapi-action.ts b/packages/tools/src/tools/openapi-action.ts new file mode 100644 index 0000000000000000000000000000000000000000..506094a62c1b644125359d2f6eae226f076d7f31 --- /dev/null +++ b/packages/tools/src/tools/openapi-action.ts @@ -0,0 +1,177 @@ +import SwaggerParser from "@apidevtools/swagger-parser"; +import type { JSONValue } from "@llamaindex/core/global"; +import type { ToolMetadata } from "@llamaindex/core/llms"; +import { FunctionTool } from "@llamaindex/core/tools"; +import { type JSONSchemaType } from "ajv"; +import got from "got"; + +interface DomainHeaders { + [key: string]: { [header: string]: string }; +} + +type Input = { + url: string; + params: object; +}; + +type APIInfo = { + description: string; + title: string; +}; + +export class OpenAPIActionTool { + // cache the loaded specs by URL + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private static specs: Record<string, any> = {}; + + private readonly INVALID_URL_PROMPT = + "This url did not include a hostname or scheme. Please determine the complete URL and try again."; + + private createLoadSpecMetaData = (info: APIInfo) => { + return { + name: "load_openapi_spec", + description: `Use this to retrieve the OpenAPI spec for the API named ${info.title} with the following description: ${info.description}. Call it before making any requests to the API.`, + }; + }; + + private readonly createMethodCallMetaData = ( + method: "POST" | "PATCH" | "GET", + info: APIInfo, + ) => { + return { + name: `${method.toLowerCase()}_request`, + description: `Use this to call the ${method} method on the API named ${info.title}`, + parameters: { + type: "object", + properties: { + url: { + type: "string", + description: `The url to make the ${method} request against`, + }, + params: { + type: "object", + description: + method === "GET" + ? "the URL parameters to provide with the get request" + : `the key-value pairs to provide with the ${method} request`, + }, + }, + required: ["url"], + }, + } as ToolMetadata<JSONSchemaType<Input>>; + }; + + constructor( + public openapi_uri: string, + public domainHeaders: DomainHeaders = {}, + ) {} + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async loadOpenapiSpec(url: string): Promise<any> { + const api = await SwaggerParser.validate(url); + return { + servers: "servers" in api ? api.servers : "", + info: { description: api.info.description, title: api.info.title }, + endpoints: api.paths, + }; + } + + async getRequest(input: Input): Promise<JSONValue> { + if (!this.validUrl(input.url)) { + return this.INVALID_URL_PROMPT; + } + try { + const data = await got + .get(input.url, { + headers: this.getHeadersForUrl(input.url), + searchParams: input.params as URLSearchParams, + }) + .json(); + return data as JSONValue; + } catch (error) { + return error as JSONValue; + } + } + + async postRequest(input: Input): Promise<JSONValue> { + if (!this.validUrl(input.url)) { + return this.INVALID_URL_PROMPT; + } + try { + const res = await got.post(input.url, { + headers: this.getHeadersForUrl(input.url), + json: input.params, + }); + return res.body as JSONValue; + } catch (error) { + return error as JSONValue; + } + } + + async patchRequest(input: Input): Promise<JSONValue> { + if (!this.validUrl(input.url)) { + return this.INVALID_URL_PROMPT; + } + try { + const res = await got.patch(input.url, { + headers: this.getHeadersForUrl(input.url), + json: input.params, + }); + return res.body as JSONValue; + } catch (error) { + return error as JSONValue; + } + } + + public async toToolFunctions() { + if (!OpenAPIActionTool.specs[this.openapi_uri]) { + console.log(`Loading spec for URL: ${this.openapi_uri}`); + const spec = await this.loadOpenapiSpec(this.openapi_uri); + OpenAPIActionTool.specs[this.openapi_uri] = spec; + } + const spec = OpenAPIActionTool.specs[this.openapi_uri]; + // TODO: read endpoints with parameters from spec and create one tool for each endpoint + // For now, we just create a tool for each HTTP method which does not work well for passing parameters + return [ + FunctionTool.from(() => { + return spec; + }, this.createLoadSpecMetaData(spec.info)), + FunctionTool.from( + this.getRequest.bind(this), + this.createMethodCallMetaData("GET", spec.info), + ), + FunctionTool.from( + this.postRequest.bind(this), + this.createMethodCallMetaData("POST", spec.info), + ), + FunctionTool.from( + this.patchRequest.bind(this), + this.createMethodCallMetaData("PATCH", spec.info), + ), + ]; + } + + private validUrl(url: string): boolean { + const parsed = new URL(url); + return !!parsed.protocol && !!parsed.hostname; + } + + private getDomain(url: string): string { + const parsed = new URL(url); + return parsed.hostname; + } + + private getHeadersForUrl(url: string): { [header: string]: string } { + const domain = this.getDomain(url); + return this.domainHeaders[domain] || {}; + } +} + +export const getOpenAPIActionTools = async (params: { + openapiUri: string; + domainHeaders: DomainHeaders; +}) => { + const { openapiUri, domainHeaders } = params; + const openAPIActionTool = new OpenAPIActionTool(openapiUri, domainHeaders); + return await openAPIActionTool.toToolFunctions(); +}; diff --git a/packages/tools/src/tools/weather.ts b/packages/tools/src/tools/weather.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e07e6cd91020057891e9dcc2bdbd238cf23be6e --- /dev/null +++ b/packages/tools/src/tools/weather.ts @@ -0,0 +1,122 @@ +import { tool } from "@llamaindex/core/tools"; +import { z } from "zod"; + +export type WeatherToolOutput = { + latitude: number; + longitude: number; + generationtime_ms: number; + utc_offset_seconds: number; + timezone: string; + timezone_abbreviation: string; + elevation: number; + current_units: { + time: string; + interval: string; + temperature_2m: string; + weather_code: string; + }; + current: { + time: string; + interval: number; + temperature_2m: number; + weather_code: number; + }; + hourly_units: { + time: string; + temperature_2m: string; + weather_code: string; + }; + hourly: { + time: string[]; + temperature_2m: number[]; + weather_code: number[]; + }; + daily_units: { + time: string; + weather_code: string; + }; + daily: { + time: string[]; + weather_code: number[]; + }; +}; + +export const weather = () => { + return tool({ + name: "weather", + description: ` + Use this function to get the weather of any given location. + Note that the weather code should follow WMO Weather interpretation codes (WW): + 0: Clear sky + 1, 2, 3: Mainly clear, partly cloudy, and overcast + 45, 48: Fog and depositing rime fog + 51, 53, 55: Drizzle: Light, moderate, and dense intensity + 56, 57: Freezing Drizzle: Light and dense intensity + 61, 63, 65: Rain: Slight, moderate and heavy intensity + 66, 67: Freezing Rain: Light and heavy intensity + 71, 73, 75: Snow fall: Slight, moderate, and heavy intensity + 77: Snow grains + 80, 81, 82: Rain showers: Slight, moderate, and violent + 85, 86: Snow showers slight and heavy + 95: Thunderstorm: Slight or moderate + 96, 99: Thunderstorm with slight and heavy hail + `, + parameters: z.object({ + location: z.string().describe("The location to get the weather"), + }), + execute: async ({ location }): Promise<WeatherToolOutput> => { + return await getWeatherByLocation(location); + }, + }); +}; + +async function getWeatherByLocation( + location: string, +): Promise<WeatherToolOutput> { + const { latitude, longitude } = await getGeoLocation(location); + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + const params = new URLSearchParams({ + latitude: latitude.toString(), + longitude: longitude.toString(), + current: "temperature_2m,weather_code", + hourly: "temperature_2m,weather_code", + daily: "weather_code", + timezone, + }); + + const apiUrl = `https://api.open-meteo.com/v1/forecast?${params}`; + + const response = await fetch(apiUrl); + if (!response.ok) { + throw new Error(`Weather API request failed: ${response.statusText}`); + } + + return (await response.json()) as WeatherToolOutput; +} + +async function getGeoLocation( + location: string, +): Promise<{ latitude: number; longitude: number }> { + const params = new URLSearchParams({ + name: location, + count: "10", + language: "en", + format: "json", + }); + + const apiUrl = `https://geocoding-api.open-meteo.com/v1/search?${params}`; + + const response = await fetch(apiUrl); + if (!response.ok) { + throw new Error(`Geocoding API request failed: ${response.statusText}`); + } + + const data = await response.json(); + if (!data.results?.length) { + throw new Error(`No location found for: ${location}`); + } + + const { latitude, longitude } = data.results[0]; + return { latitude, longitude }; +} diff --git a/packages/tools/src/tools/wiki.ts b/packages/tools/src/tools/wiki.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5eff4718e2d654d5cde1d0ec44ab54c329ac551 --- /dev/null +++ b/packages/tools/src/tools/wiki.ts @@ -0,0 +1,27 @@ +import { tool } from "@llamaindex/core/tools"; +import { default as wikipedia } from "wikipedia"; +import { z } from "zod"; + +export type WikiToolOutput = { + title: string; + content: string; +}; + +export const wiki = () => { + return tool({ + name: "wikipedia", + description: "Use this function to search Wikipedia", + parameters: z.object({ + query: z.string().describe("The query to search for"), + lang: z.string().describe("The language to search in").default("en"), + }), + execute: async ({ query, lang }): Promise<WikiToolOutput> => { + wikipedia.setLang(lang); + const searchResult = await wikipedia.search(query); + const pageTitle = searchResult?.results[0]?.title; + if (!pageTitle) return { title: "No search results.", content: "" }; + const result = await wikipedia.page(pageTitle, { autoSuggest: false }); + return { title: pageTitle, content: await result.content() }; + }, + }); +}; diff --git a/packages/tools/tests/code-generator.test.ts b/packages/tools/tests/code-generator.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..80fe5f52e6b7b2f2655caceb5d7dbbd20a5ca4a3 --- /dev/null +++ b/packages/tools/tests/code-generator.test.ts @@ -0,0 +1,37 @@ +import { Settings } from "@llamaindex/core/global"; +import { MockLLM } from "@llamaindex/core/utils"; +import { describe, expect, test } from "vitest"; +import { + codeGenerator, + type CodeGeneratorToolOutput, +} from "../src/tools/code-generator"; + +Settings.llm = new MockLLM({ + responseMessage: `{ + "commentary": "Creating a simple Next.js page with a hello world message", + "template": "nextjs-developer", + "title": "Hello World App", + "description": "A simple Next.js hello world application", + "additional_dependencies": [], + "has_additional_dependencies": false, + "install_dependencies_command": "", + "port": 3000, + "file_path": "pages/index.tsx", + "code": "export default function Home() { return <h1>Hello World</h1> }" + }`, +}); + +describe("Code Generator Tool", () => { + test("generates Next.js application code", async () => { + const generator = codeGenerator(); + const result = (await generator.call({ + requirement: "Create a simple Next.js hello world page", + })) as CodeGeneratorToolOutput; + + expect(result.isError).toBe(false); + expect(result.artifact).toBeDefined(); + expect(result.artifact?.template).toBe("nextjs-developer"); + expect(result.artifact?.file_path).toBe("pages/index.tsx"); + expect(result.artifact?.code).toContain("export default"); + }); +}); diff --git a/packages/tools/tests/document-generator.test.ts b/packages/tools/tests/document-generator.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..94b5e0e247b8e65ab848b699a439bd73c06847e1 --- /dev/null +++ b/packages/tools/tests/document-generator.test.ts @@ -0,0 +1,24 @@ +import path from "path"; +import { describe, expect, test, vi } from "vitest"; +import { documentGenerator } from "../src/tools/document-generator"; + +// Mock the helper functions +vi.mock("../src/helper", () => ({ + saveDocument: vi.fn().mockResolvedValue(undefined), + getFileUrl: vi.fn().mockReturnValue("http://example.com/test-doc.html"), +})); + +describe("Document Generator Tool", () => { + test("converts markdown to html document", async () => { + const docGen = documentGenerator({ + outputDir: path.join(__dirname, "output"), + }); + + const result = await docGen.call({ + originalContent: "# Hello World\nThis is a test", + fileName: "test-doc", + }); + + expect(result).toBe("URL: http://example.com/test-doc.html"); + }); +}); diff --git a/packages/tools/tests/duckduckgo.test.ts b/packages/tools/tests/duckduckgo.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..14e3c67a4d4179ff8f76d61c6f1ea8325a3153af --- /dev/null +++ b/packages/tools/tests/duckduckgo.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "vitest"; +import { duckduckgo, type DuckDuckGoToolOutput } from "../src/tools/duckduckgo"; + +describe("DuckDuckGo Tool", () => { + test("performs search and returns results", async () => { + const searchTool = duckduckgo(); + const results = (await searchTool.call({ + query: "OpenAI ChatGPT", + maxResults: 3, + })) as DuckDuckGoToolOutput; + + expect(Array.isArray(results)).toBe(true); + expect(results.length).toBeLessThanOrEqual(3); + const firstResult = results[0]; + expect(firstResult).toHaveProperty("title"); + expect(firstResult).toHaveProperty("description"); + expect(firstResult).toHaveProperty("url"); + }); +}); diff --git a/packages/tools/tests/form-filling.test.ts b/packages/tools/tests/form-filling.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6dfcf986809ddaa8bf3486cd041f066e699e1ae --- /dev/null +++ b/packages/tools/tests/form-filling.test.ts @@ -0,0 +1,44 @@ +import fs from "fs"; +import path from "path"; +import { describe, expect, test, vi } from "vitest"; +import { fillMissingCells } from "../src/tools/form-filling"; + +vi.mock("fs", () => ({ + default: { + readFile: vi.fn(), + promises: { + readFile: vi.fn(), + }, + }, +})); + +vi.mock("../src/helper", () => ({ + saveDocument: vi.fn(), + getFileUrl: vi.fn().mockReturnValue("http://example.com/filled.csv"), +})); + +describe("Form Filling Tools", () => { + test("fillMissingCells fills cells with provided answers", async () => { + // Mock CSV content + const mockCsvContent = "Name,Age,City\nJohn,,Paris\nMary,,"; + vi.mocked(fs.promises.readFile).mockResolvedValue(mockCsvContent); + + const filler = fillMissingCells({ + outputDir: path.join(__dirname, "output"), + }); + + const result = await filler.call({ + filePath: "test.csv", + cells: [ + { + rowIndex: 1, + columnIndex: 1, + answer: "25", + }, + ], + }); + + expect(result).toContain("Successfully filled missing cells"); + expect(result).toContain("http://example.com/filled.csv"); + }); +}); diff --git a/packages/tools/tests/img-gen.test.ts b/packages/tools/tests/img-gen.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f96cb44059e856646f957db10a23cbfd95aea4f --- /dev/null +++ b/packages/tools/tests/img-gen.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test, vi } from "vitest"; +import { + imageGenerator, + type ImgGeneratorToolOutput, +} from "../src/tools/img-gen"; + +vi.mock("got", () => ({ + default: { + post: vi.fn().mockReturnValue({ + buffer: vi.fn().mockResolvedValue(Buffer.from("mock-image-data")), + }), + }, +})); + +describe("Image Generator Tool", () => { + test("generates image from prompt", async () => { + const imgTool = imageGenerator({ + apiKey: process.env.STABILITY_API_KEY!, + outputDir: "output", + fileServerURLPrefix: "http://localhost:3000", + }); + + const result = (await imgTool.call({ + prompt: "a cute cat playing with yarn", + })) as ImgGeneratorToolOutput; + + expect(result.isSuccess).toBe(true); + expect(result.imageUrl).toBeDefined(); + expect(result.errorMessage).toBeUndefined(); + }); +}); diff --git a/packages/tools/tests/interpreter.test.ts b/packages/tools/tests/interpreter.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fbe4fd266cb778256063616e505b6e55b52dac5 --- /dev/null +++ b/packages/tools/tests/interpreter.test.ts @@ -0,0 +1,50 @@ +import { Sandbox } from "@e2b/code-interpreter"; +import path from "path"; +import { describe, expect, test, vi } from "vitest"; +import { + interpreter, + type InterpreterToolOutput, +} from "../src/tools/interpreter"; + +vi.mock("@e2b/code-interpreter", () => ({ + Sandbox: { + create: vi.fn().mockImplementation(() => ({ + runCode: vi.fn().mockResolvedValue({ + error: null, + logs: { + stdout: ["Hello, World!", "x = 2"], + stderr: [], + }, + text: "Hello, World!\nx = 2", + results: [ + { + formats: () => [], + }, + ], + }), + files: { + write: vi.fn().mockResolvedValue(undefined), + }, + })), + }, +})); + +describe("Code Interpreter Tool", () => { + test("executes simple python code", async () => { + const interpreterTool = interpreter({ + apiKey: "mock-api-key", + outputDir: path.join(__dirname, "output"), + uploadedFilesDir: path.join(__dirname, "files"), + }); + + const result = (await interpreterTool.call({ + code: "print('Hello, World!')\nx = 1 + 1\nprint(f'x = {x}')", + })) as InterpreterToolOutput; + + expect(Sandbox.create).toHaveBeenCalledWith({ apiKey: "mock-api-key" }); + expect(result.isError).toBe(false); + expect(result.logs.stdout).toEqual(["Hello, World!", "x = 2"]); + expect(result.retryCount).toBe(1); + expect(result.extraResult).toEqual([]); + }); +}); diff --git a/packages/tools/tests/openapi-action.test.ts b/packages/tools/tests/openapi-action.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f2d2a86b75072654b8882d38ea1360754326485 --- /dev/null +++ b/packages/tools/tests/openapi-action.test.ts @@ -0,0 +1,70 @@ +import SwaggerParser from "@apidevtools/swagger-parser"; +import got from "got"; +import { describe, expect, test, vi } from "vitest"; +import { OpenAPIActionTool } from "../src/tools/openapi-action"; + +// Mock SwaggerParser and got +vi.mock("@apidevtools/swagger-parser", () => ({ + default: { + validate: vi.fn(), + }, +})); + +vi.mock("got", () => ({ + default: { + get: vi.fn(), + post: vi.fn(), + patch: vi.fn(), + }, +})); + +describe("OpenAPI Action Tool", () => { + test("loads and executes API requests", async () => { + // Mock swagger spec + vi.mocked(SwaggerParser.validate).mockResolvedValue({ + openapi: "3.0.0", + info: { + title: "Test API", + description: "Test API Description", + version: "1.0.0", + }, + paths: { + "/test": { + get: { + description: "Test endpoint", + responses: { + "200": { + description: "Successful response", + }, + }, + }, + }, + }, + }); + + // Mock API response + vi.mocked(got.get).mockReturnValue({ + json: () => Promise.resolve({ data: "test response" }), + } as ReturnType<typeof got.get>); + + const tool = new OpenAPIActionTool("https://api.test.com/openapi.json"); + const tools = await tool.toToolFunctions(); + + // Verify tools were created + expect(tools).toHaveLength(4); // load_spec, get, post, patch + + // Test GET request + const result = await tool.getRequest({ + url: "https://api.test.com/test", + params: { key: "value" }, + }); + + expect(got.get).toHaveBeenCalledWith( + "https://api.test.com/test", + expect.objectContaining({ + searchParams: { key: "value" }, + }), + ); + expect(result).toEqual({ data: "test response" }); + }); +}); diff --git a/packages/tools/tests/weather.test.ts b/packages/tools/tests/weather.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c57691ffd27c6031ad4d14a0c5c6d7d4d265ca91 --- /dev/null +++ b/packages/tools/tests/weather.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test } from "vitest"; +import { weather } from "../src/tools/weather"; + +describe("Weather Tool", () => { + test("weather tool returns data for valid location", async () => { + const weatherTool = weather(); + const result = await weatherTool.call({ + location: "London", + }); + + expect(result).toHaveProperty("current"); + expect(result).toHaveProperty("hourly"); + expect(result).toHaveProperty("daily"); + }); +}); diff --git a/packages/tools/tests/wiki.test.ts b/packages/tools/tests/wiki.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ac22913680432b6014d0e166833ec994c929441 --- /dev/null +++ b/packages/tools/tests/wiki.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test } from "vitest"; +import { wiki } from "../src/tools/wiki"; + +describe("Wikipedia Tool", () => { + test("wiki tool returns content for valid query", async () => { + const wikipediaTool = wiki(); + const result = await wikipediaTool.call({ + query: "Albert Einstein", + lang: "en", + }); + + expect(result).toHaveProperty("title"); + expect(result).toHaveProperty("content"); + }); +}); diff --git a/packages/tools/tsconfig.json b/packages/tools/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..a93775d954ab510bbae6d3aaabe2c7204f557e2b --- /dev/null +++ b/packages/tools/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist/type", + "tsBuildInfoFile": "./dist/.tsbuildinfo", + "emitDeclarationOnly": true, + "moduleResolution": "Bundler", + "skipLibCheck": true, + "strict": true, + "types": ["node"] + }, + "include": ["./src"], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5ff7b477b6d45f3edb78fdb85be764b78697b8a..9fa0108c14cb5c6f7b466653021c360ca8204646 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -680,6 +680,9 @@ importers: '@llamaindex/together': specifier: ^0.0.5 version: link:../packages/providers/together + '@llamaindex/tools': + specifier: ^0.0.1 + version: link:../packages/tools '@llamaindex/upstash': specifier: ^0.0.13 version: link:../packages/providers/storage/upstash @@ -1718,6 +1721,58 @@ importers: specifier: ^13.4.8 version: 13.4.8 + packages/tools: + dependencies: + '@apidevtools/swagger-parser': + specifier: ^10.1.0 + version: 10.1.1(openapi-types@12.1.3) + '@e2b/code-interpreter': + specifier: ^1.0.4 + version: 1.0.4 + '@llamaindex/core': + specifier: workspace:* + version: link:../core + '@llamaindex/env': + specifier: workspace:* + version: link:../env + ajv: + specifier: ^8.12.0 + version: 8.17.1 + duck-duck-scrape: + specifier: ^2.2.5 + version: 2.2.7 + formdata-node: + specifier: ^6.0.3 + version: 6.0.3 + got: + specifier: ^14.4.1 + version: 14.4.6 + marked: + specifier: ^14.1.2 + version: 14.1.4 + papaparse: + specifier: ^5.4.1 + version: 5.5.2 + wikipedia: + specifier: ^2.1.2 + version: 2.1.2 + zod: + specifier: ^3.23.8 + version: 3.24.2 + devDependencies: + '@types/node': + specifier: ^22.9.0 + version: 22.9.0 + '@types/papaparse': + specifier: ^5.3.15 + version: 5.3.15 + bunchee: + specifier: 6.4.0 + version: 6.4.0(typescript@5.7.3) + vitest: + specifier: ^2.1.5 + version: 2.1.5(@edge-runtime/vm@4.0.4)(@types/node@22.9.0)(happy-dom@15.11.7)(lightningcss@1.29.1)(msw@2.7.0(@types/node@22.9.0)(typescript@5.7.3))(terser@5.38.2) + packages/wasm-tools: dependencies: '@assemblyscript/loader': @@ -1937,10 +1992,26 @@ packages: '@anthropic-ai/sdk@0.37.0': resolution: {integrity: sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw==} + '@apidevtools/json-schema-ref-parser@11.7.2': + resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==} + engines: {node: '>= 16'} + '@apidevtools/json-schema-ref-parser@11.9.3': resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} engines: {node: '>= 16'} + '@apidevtools/openapi-schemas@2.1.0': + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + + '@apidevtools/swagger-methods@3.0.2': + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + + '@apidevtools/swagger-parser@10.1.1': + resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==} + peerDependencies: + openapi-types: '>=7' + '@assemblyscript/loader@0.27.34': resolution: {integrity: sha512-aUc12nBRN04dHn439RiI0G9X8CeOzbVuKspiVL1O8yS82neU3zTYjVFi9mz2+ZpSD2Laa7H0sUJ91jfhzQERMw==} @@ -2243,6 +2314,9 @@ packages: resolution: {integrity: sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==} engines: {node: '>=6.9.0'} + '@bufbuild/protobuf@2.2.3': + resolution: {integrity: sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==} + '@bundled-es-modules/cookie@2.0.1': resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} @@ -2424,6 +2498,17 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} + '@connectrpc/connect-web@2.0.0-rc.3': + resolution: {integrity: sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==} + peerDependencies: + '@bufbuild/protobuf': ^2.2.0 + '@connectrpc/connect': 2.0.0-rc.3 + + '@connectrpc/connect@2.0.0-rc.3': + resolution: {integrity: sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==} + peerDependencies: + '@bufbuild/protobuf': ^2.2.0 + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -2455,6 +2540,10 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} + '@e2b/code-interpreter@1.0.4': + resolution: {integrity: sha512-8y82UMXBdf/hye8bX2Fn04JlL72rvOenVgsvMZ+cAJqo6Ijdl4EmzzuFpM4mz9s+EJ29+34lGHBp277tiLWuiA==} + engines: {node: '>=18'} + '@edge-runtime/primitives@5.1.1': resolution: {integrity: sha512-osrHE4ObQ3XFkvd1sGBLkheV2mcHUqJI/Bum2AWA0R3U78h9lif3xZAdl6eLD/XnW4xhsdwjPUejLusXbjvI4Q==} engines: {node: '>=16'} @@ -4762,6 +4851,10 @@ packages: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} + '@sindresorhus/is@7.0.1': + resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==} + engines: {node: '>=18'} + '@sitespeed.io/tracium@0.3.3': resolution: {integrity: sha512-dNZafjM93Y+F+sfwTO5gTpsGXlnc/0Q+c2+62ViqP3gkMWvHEMSKkaEHgVJLcLg3i/g19GSIPziiKpgyne07Bw==} engines: {node: '>=8'} @@ -5406,6 +5499,9 @@ packages: '@types/node@22.9.0': resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} + '@types/papaparse@5.3.15': + resolution: {integrity: sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==} + '@types/pg@8.11.11': resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==} @@ -6258,6 +6354,10 @@ packages: resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} engines: {node: '>=14.16'} + cacheable-request@12.0.1: + resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==} + engines: {node: '>=18'} + call-bind-apply-helpers@1.0.1: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} @@ -6270,6 +6370,9 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + callguard@2.0.0: resolution: {integrity: sha512-I3nd+fuj20FK1qu00ImrbH+II+8ULS6ioYr9igqR1xyqySoqc3DiHEyUM0mkoAdKeLGg2CtGnO8R3VRQX5krpQ==} @@ -6523,6 +6626,9 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} @@ -6846,6 +6952,9 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + duck-duck-scrape@2.2.7: + resolution: {integrity: sha512-BEcglwnfx5puJl90KQfX+Q2q5vCguqyMpZcSRPBWk8OY55qWwV93+E+7DbIkrGDW4qkqPfUvtOUdi0lXz6lEMQ==} + duck@0.1.12: resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} @@ -6856,6 +6965,10 @@ packages: duplexify@4.1.3: resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + e2b@1.0.7: + resolution: {integrity: sha512-7msagBbQ8tm51qaGp+hdaaaMjGG3zCzZtUS8bnz+LK7wdwtVTA1PmX+1Br9E3R7v6XIchnNWRpei+VjvGcfidA==} + engines: {node: '>=18'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -7724,6 +7837,10 @@ packages: resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} engines: {node: '>=16'} + got@14.4.6: + resolution: {integrity: sha512-rnhwfM/PhMNJ1i17k3DuDqgj0cKx3IHxBKVv/WX1uDKqrhi2Gv3l7rhPThR/Cc6uU++dD97W9c8Y0qyw9x0jag==} + engines: {node: '>=20'} + gpt-tokenizer@2.8.1: resolution: {integrity: sha512-8+a9ojzqfgiF3TK4oivGYjlycD8g5igLt8NQw3ndOIgLVKSGJDhUDNAfYSbtyyuTkha3R/R9F8XrwC7/B5TKfQ==} @@ -8727,6 +8844,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@14.1.4: + resolution: {integrity: sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -9331,6 +9453,11 @@ packages: resolution: {integrity: sha512-VVw8O5KrfvwqAFeNZEgBbdgA+AQaBlHcXEootWU7TWDaFWFI0VLfzyKMsRjnfdS3cVCpWmI04xLJonCvEv11VQ==} engines: {node: '>=0.4.10'} + needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} + engines: {node: '>= 4.4.x'} + hasBin: true + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} @@ -9611,9 +9738,18 @@ packages: zod: optional: true + openapi-fetch@0.9.8: + resolution: {integrity: sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==} + openapi-sampler@1.6.1: resolution: {integrity: sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + + openapi-typescript-helpers@0.0.8: + resolution: {integrity: sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g==} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -9658,6 +9794,10 @@ packages: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} + p-cancelable@4.0.1: + resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==} + engines: {node: '>=14.16'} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -9707,6 +9847,9 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + papaparse@5.5.2: + resolution: {integrity: sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -12239,12 +12382,33 @@ snapshots: transitivePeerDependencies: - encoding + '@apidevtools/json-schema-ref-parser@11.7.2': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + '@apidevtools/json-schema-ref-parser@11.9.3': dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 js-yaml: 4.1.0 + '@apidevtools/openapi-schemas@2.1.0': {} + + '@apidevtools/swagger-methods@3.0.2': {} + + '@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)': + dependencies: + '@apidevtools/json-schema-ref-parser': 11.7.2 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + '@assemblyscript/loader@0.27.34': {} '@aws-crypto/crc32@3.0.0': @@ -13037,6 +13201,8 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@bufbuild/protobuf@2.2.3': {} + '@bundled-es-modules/cookie@2.0.1': dependencies: cookie: 0.7.2 @@ -13348,6 +13514,15 @@ snapshots: '@colors/colors@1.6.0': {} + '@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.2.3)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.2.3))': + dependencies: + '@bufbuild/protobuf': 2.2.3 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.2.3) + + '@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.2.3)': + dependencies: + '@bufbuild/protobuf': 2.2.3 + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -13388,6 +13563,10 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} + '@e2b/code-interpreter@1.0.4': + dependencies: + e2b: 1.0.7 + '@edge-runtime/primitives@5.1.1': {} '@edge-runtime/vm@4.0.4': @@ -15638,6 +15817,8 @@ snapshots: '@sindresorhus/is@5.6.0': {} + '@sindresorhus/is@7.0.1': {} + '@sitespeed.io/tracium@0.3.3': dependencies: debug: 4.4.0 @@ -16383,6 +16564,10 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/papaparse@5.3.15': + dependencies: + '@types/node': 22.9.0 + '@types/pg@8.11.11': dependencies: '@types/node': 22.9.0 @@ -17472,6 +17657,16 @@ snapshots: normalize-url: 8.0.1 responselike: 3.0.0 + cacheable-request@12.0.1: + dependencies: + '@types/http-cache-semantics': 4.0.4 + get-stream: 9.0.1 + http-cache-semantics: 4.1.1 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.0.1 + responselike: 3.0.0 + call-bind-apply-helpers@1.0.1: dependencies: es-errors: 1.3.0 @@ -17489,6 +17684,8 @@ snapshots: call-bind-apply-helpers: 1.0.1 get-intrinsic: 1.2.7 + call-me-maybe@1.0.2: {} + callguard@2.0.0: {} callsites@3.1.0: {} @@ -17746,6 +17943,8 @@ snapshots: commondir@1.0.1: {} + compare-versions@6.1.1: {} + compute-scroll-into-view@3.1.1: {} concat-map@0.0.1: {} @@ -18034,6 +18233,11 @@ snapshots: dotenv@16.4.7: {} + duck-duck-scrape@2.2.7: + dependencies: + html-entities: 2.5.2 + needle: 3.3.1 + duck@0.1.12: dependencies: underscore: 1.13.7 @@ -18051,6 +18255,15 @@ snapshots: readable-stream: 3.6.2 stream-shift: 1.0.3 + e2b@1.0.7: + dependencies: + '@bufbuild/protobuf': 2.2.3 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.2.3) + '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.2.3)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.2.3)) + compare-versions: 6.1.1 + openapi-fetch: 0.9.8 + platform: 1.3.6 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -19484,6 +19697,20 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 + got@14.4.6: + dependencies: + '@sindresorhus/is': 7.0.1 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 12.0.1 + decompress-response: 6.0.0 + form-data-encoder: 4.0.2 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 4.0.1 + responselike: 3.0.0 + type-fest: 4.34.1 + gpt-tokenizer@2.8.1: {} graceful-fs@4.2.11: {} @@ -20645,6 +20872,8 @@ snapshots: markdown-table@3.0.4: {} + marked@14.1.4: {} + math-intrinsics@1.1.0: {} md-utils-ts@2.0.0: {} @@ -21734,6 +21963,11 @@ snapshots: - socks - supports-color + needle@3.3.1: + dependencies: + iconv-lite: 0.6.3 + sax: 1.4.1 + negotiator@1.0.0: {} neo-async@2.6.2: {} @@ -22048,12 +22282,20 @@ snapshots: transitivePeerDependencies: - encoding + openapi-fetch@0.9.8: + dependencies: + openapi-typescript-helpers: 0.0.8 + openapi-sampler@1.6.1: dependencies: '@types/json-schema': 7.0.15 fast-xml-parser: 4.5.3 json-pointer: 0.6.2 + openapi-types@12.1.3: {} + + openapi-typescript-helpers@0.0.8: {} + opener@1.5.2: {} option@0.2.4: {} @@ -22125,6 +22367,8 @@ snapshots: p-cancelable@3.0.0: {} + p-cancelable@4.0.1: {} + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -22177,6 +22421,8 @@ snapshots: pako@1.0.11: {} + papaparse@5.5.2: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 diff --git a/tsconfig.json b/tsconfig.json index 0dbe6a06115202cb5baf0b8c4459df920092d628..9de0bcdbe5ea19a8e21337af06ea7568f895ac4d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -190,6 +190,9 @@ }, { "path": "./packages/providers/perplexity/tsconfig.json" + }, + { + "path": "./packages/tools/tsconfig.json" } ] }