From e16c8a660b1e4a862296effd067687b83a128bbf Mon Sep 17 00:00:00 2001
From: Travis Fischer
- AI agent stdlib that works with any TypeScript AI SDK and LLM + AI agent stdlib that works with any LLM and TypeScript AI SDK
@@ -15,12 +15,12 @@ # Agentic -> [!WARNING] +> [!WARNING] > TODO: this project is not published yet and is an active WIP. The goal of this project is to create a **set of standard AI functions / tools** which are **optimized for both normal TS-usage as well as LLM-based apps** and that work with all of the major AI SDKs (LangChain, LlamaIndex, Vercel AI SDK, OpenAI SDK, etc). -For example, stdlib clients like `WeatherClient` can be used normally as a fully-typed TS client: +For example, stdlib clients like `WeatherClient` can be used as normal TS classes: ```ts import { WeatherClient } from '@agentic/stdlib' @@ -33,9 +33,9 @@ const result = await weather.getCurrentWeather({ console.log(result) ``` -Or you can use this same function an LLM-based tool which works across all of the major AI SDKs via adaptors. +Or you can use them as LLM-based tools where the LLM decides when and how to invoke the underlying functions for you. -Here's an example using [Vercel's AI SDK](https://github.com/vercel/ai): +This works across all of the major AI SDKs via adaptors. Here's an example using [Vercel's AI SDK](https://github.com/vercel/ai): ```ts // sdk-specific imports @@ -50,7 +50,7 @@ const weather = new WeatherClient() const result = await generateText({ model: openai('gpt-4o'), - // this is the key line which uses the ai-sdk adaptor + // this is the key line which uses the `@agentic/stdlib/ai-sdk` adaptor tools: createAISDKTools(weather), toolChoice: 'required', prompt: 'What is the weather in San Francisco?' @@ -59,7 +59,7 @@ const result = await generateText({ console.log(result.toolResults[0]) ``` -You can use all of our thoroughly tested stdlib AI functions with your favorite AI SDK – without having to write any glue code! +You can use our standard library of thoroughly tested AI functions with your favorite AI SDK – without having to write any glue code! Here's a slightly more complex example which uses multiple clients and selects a subset of their functions using the `AIFunctionSet.pick` method: @@ -72,6 +72,7 @@ import { createDexterFunctions } from '@agentic/stdlib/dexter' import { PerigonClient, SerperClient } from '@agentic/stdlib' async function main() { + // Perigon is a news API and Serper is a Google search API const perigon = new PerigonClient() const serper = new SerperClient() @@ -131,14 +132,16 @@ All heavy third-party imports are isolated as _optional peer dependencies_ to ke | [Slack](https://api.slack.com/docs) | `SlackClient` | Send and receive Slack messages. | | [Tavily](https://tavily.com) | `TavilyClient` | Web search API tailored for LLMs. 🔥 | | [Twilio](https://www.twilio.com/docs/conversations/api) | `TwilioClient` | Twilio conversation API to send and receive SMS messages. | -| [Twitter](https://developer.x.com/en/docs/twitter-api) | `TwitterClient` | Basic Twitter API methods for fetching users, tweets, and searching recent tweets. Includes support for plan-aware rate-limiting. | +| [Twitter](https://developer.x.com/en/docs/twitter-api) | `TwitterClient` | Basic Twitter API methods for fetching users, tweets, and searching recent tweets. Includes support for plan-aware rate-limiting. Uses [Nango](https://www.nango.dev) for OAuth support. | | [WeatherAPI](https://www.weatherapi.com) | `WeatherClient` | Basic access to current weather data based on location. | | [Wikipedia](https://www.mediawiki.org/wiki/API) | `WikipediaClient` | Wikipedia page search and summaries. | | [Wolfram Alpha](https://products.wolframalpha.com/llm-api/documentation) | `WolframAlphaClient` | Wolfram Alpha LLM API client for answering computational, mathematical, and scientific questions. | +Note that many of these clients expose multiple AI functions. + ## Compound Tools -- search and scrape +- `SearchAndCrawl` ## AI SDKs @@ -161,6 +164,7 @@ All heavy third-party imports are isolated as _optional peer dependencies_ to ke - clients should use `ky` and `zod` where possible - clients should have a strongly-typed TS DX - clients should expose select methods via the `@aiFunction(...)` decorator + - `inputSchema` zod schemas should be as minimal as possible with descriptions prompt engineered specifically for use with LLMs - clients and AIFunctions should be composable via `AIFunctionSet` - clients should work with all major TS AI SDKs - SDK adaptors should be as lightweight as possible and be optional peer dependencies of `@agentic/stdlib` @@ -171,7 +175,9 @@ All heavy third-party imports are isolated as _optional peer dependencies_ to ke - sdks - modelfusion - services + - [phantombuster](https://phantombuster.com) - perplexity + - valtown - replicate - huggingface - [skyvern](https://github.com/Skyvern-AI/skyvern) diff --git a/legacy/src/services/tavily-client.ts b/legacy/src/services/tavily-client.ts index 021707e3..95774757 100644 --- a/legacy/src/services/tavily-client.ts +++ b/legacy/src/services/tavily-client.ts @@ -1,12 +1,19 @@ import defaultKy, { type KyInstance } from 'ky' +import pThrottle from 'p-throttle' import { z } from 'zod' import { aiFunction, AIFunctionsProvider } from '../fns.js' -import { assert, getEnv, pruneNullOrUndefined } from '../utils.js' +import { assert, getEnv, pruneNullOrUndefined, throttleKy } from '../utils.js' export namespace tavily { export const API_BASE_URL = 'https://api.tavily.com' + // Allow up to 20 requests per minute by default. + export const throttle = pThrottle({ + limit: 20, + interval: 60 * 1000 + }) + export interface SearchOptions { /** Search query. (required) */ query: string @@ -86,10 +93,12 @@ export class TavilyClient extends AIFunctionsProvider { constructor({ apiKey = getEnv('TAVILY_API_KEY'), apiBaseUrl = tavily.API_BASE_URL, + throttle = true, ky = defaultKy }: { apiKey?: string apiBaseUrl?: string + throttle?: boolean ky?: KyInstance } = {}) { assert( @@ -101,7 +110,9 @@ export class TavilyClient extends AIFunctionsProvider { this.apiKey = apiKey this.apiBaseUrl = apiBaseUrl - this.ky = ky.extend({ + const throttledKy = throttle ? throttleKy(ky, tavily.throttle) : ky + + this.ky = throttledKy.extend({ prefixUrl: this.apiBaseUrl }) } diff --git a/legacy/src/tools/search-and-crawl.ts b/legacy/src/tools/search-and-crawl.ts index 8c29e473..db5866ad 100644 --- a/legacy/src/tools/search-and-crawl.ts +++ b/legacy/src/tools/search-and-crawl.ts @@ -7,6 +7,9 @@ import { SerpAPIClient } from '../services/serpapi-client.js' import { isValidCrawlableUrl, normalizeUrl } from '../url-utils.js' import { omit, pick } from '../utils.js' +// TODO: allow `search` tool to support other search clients +// (e.g. Bing, Exa, Searxng, Serper, Tavily) + export class SearchAndCrawl extends AIFunctionsProvider { readonly serpapi: SerpAPIClient readonly diffbot: DiffbotClient @@ -21,7 +24,7 @@ export class SearchAndCrawl extends AIFunctionsProvider { @aiFunction({ name: 'search_and_crawl', description: - 'Uses Google to search the web, crawls the results, and then summarizes the most relevant results.', + 'Uses Google to search the web, crawls the results, and then summarizes the most relevant results. Useful for creating in-depth summaries of topics along with sources.', inputSchema: z.object({ query: z.string().describe('search query') })