From 8b3520e4774a806ac01605bdac337af5cbb8ecc8 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 05:03:02 +0000 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20Tavily=20search?= =?UTF-8?q?=20API=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 21 +++ .../scripts/{web-search.md => web-search.mdx} | 8 +- packages/core/src/constants.ts | 8 ++ packages/core/src/promptcontext.ts | 30 +++-- packages/core/src/types/prompt_template.d.ts | 7 +- packages/core/src/websearch.ts | 124 ++++++++++++++---- .../genaisrc/copilot/dataanalyst.genai.mjs | 3 + .../sample/genaisrc/copilot/mermaid.genai.mjs | 4 + 8 files changed, 167 insertions(+), 38 deletions(-) rename docs/src/content/docs/reference/scripts/{web-search.md => web-search.mdx} (80%) create mode 100644 packages/sample/genaisrc/copilot/dataanalyst.genai.mjs diff --git a/.vscode/settings.json b/.vscode/settings.json index 57ecd38b4d..7565521627 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "Agentic", "AICI", "ANYJS", + "Apim", "arrayify", "Automatable", "bitindex", @@ -79,6 +80,7 @@ "structurify", "sysr", "tabletojson", + "TAVILY", "Textify", "treegrid", "treesitter", @@ -115,5 +117,24 @@ "mdx": { "parser": "markdown" } + }, + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#443c00", + "activityBar.background": "#443c00", + "activityBar.foreground": "#e7e7e7", + "activityBar.inactiveForeground": "#e7e7e799", + "activityBarBadge.background": "#008071", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#e7e7e799", + "sash.hoverBorder": "#443c00", + "statusBar.background": "#110f00", + "statusBar.foreground": "#e7e7e7", + "statusBarItem.hoverBackground": "#443c00", + "statusBarItem.remoteBackground": "#110f00", + "statusBarItem.remoteForeground": "#e7e7e7", + "titleBar.activeBackground": "#110f00", + "titleBar.activeForeground": "#e7e7e7", + "titleBar.inactiveBackground": "#110f0099", + "titleBar.inactiveForeground": "#e7e7e799" } } diff --git a/docs/src/content/docs/reference/scripts/web-search.md b/docs/src/content/docs/reference/scripts/web-search.mdx similarity index 80% rename from docs/src/content/docs/reference/scripts/web-search.md rename to docs/src/content/docs/reference/scripts/web-search.mdx index f36c2be912..f4e70c18da 100644 --- a/docs/src/content/docs/reference/scripts/web-search.md +++ b/docs/src/content/docs/reference/scripts/web-search.mdx @@ -1,12 +1,12 @@ --- title: Web Search description: Execute web searches with the Bing API using retrieval.webSearch in scripts. -keywords: web search, Bing API, search automation, API configuration, search function +keywords: web search, Bing API, Tavily, search automation, API configuration, search function sidebar: order: 15 --- -The `retrieval.webSearch` executes a web search using the Bing Web Search API. +The `retrieval.webSearch` executes a web search using Tavily or the Bing Web Search. ## Web Pages @@ -21,7 +21,9 @@ def("PAGES", webPages) You can use `fetchText` to download the full content of the web page. -## Bing Web Search configuration +## Tavily Configuration + +## Bing Web Search configuration The API uses [Bing Web Search v7](https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/overview) to search the web. To use the API, you need to create a Bing Web Search resource in the Azure portal and store the API key in the `.env` file. diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 7fa822b074..262d8f4cb2 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -87,7 +87,10 @@ export const TRACE_NODE_PREFIX = "genaiscript/trace/" export const EXTENSION_ID = "genaiscript.genaiscript-vscode" export const COPILOT_CHAT_PARTICIPANT_ID = TOOL_ID export const COPILOT_CHAT_PARTICIPANT_SCRIPT_ID = "copilotchat" + export const BING_SEARCH_ENDPOINT = "https://api.bing.microsoft.com/v7.0/search" +export const TAVILY_ENDPOINT = "https://api.tavily.com/search" + export const SYSTEM_FENCE = "\n" export const MAX_DATA_REPAIRS = 1 export const NPM_CLI_PACKAGE = "genaiscript" @@ -196,6 +199,11 @@ export const DOCS_CONFIGURATION_CONTENT_SAFETY_URL = "https://microsoft.github.io/genaiscript/reference/scripts/content-safety" export const DOCS_DEF_FILES_IS_EMPTY_URL = "https://microsoft.github.io/genaiscript/reference/scripts/context/#empty-files" +export const DOCS_WEB_SEARCH_URL = + "https://microsoft.github.io/genaiscript/reference/scripts/web-search/" +export const DOCS_WEB_SEARCH_BING_SEARCH_URL = + "https://microsoft.github.io/genaiscript/reference/scripts/web-search/#bingn" +export const DOCS_WEB_SEARCH_TAVILY_URL = "https://microsoft.github.io/genaiscript/reference/scripts/web-search/#tavily" export const MODEL_PROVIDERS = Object.freeze([ { diff --git a/packages/core/src/promptcontext.ts b/packages/core/src/promptcontext.ts index 9d465288fd..9f684118c1 100644 --- a/packages/core/src/promptcontext.ts +++ b/packages/core/src/promptcontext.ts @@ -7,7 +7,7 @@ import { arrayify, dotGenaiscriptPath } from "./util" import { runtimeHost } from "./host" import { MarkdownTrace } from "./trace" import { createParsers } from "./parsers" -import { bingSearch } from "./websearch" +import { bingSearch, tavilySearch } from "./websearch" import { RunPromptContextNode, createChatGenerationContext, @@ -27,6 +27,7 @@ import { HTMLEscape } from "./html" import { hash } from "./crypto" import { resolveModelConnectionInfo } from "./models" import { createAzureContentSafetyClient } from "./azurecontentsafety" +import { DOCS_WEB_SEARCH_URL } from "./constants" /** * Creates a prompt context for the given project, variables, trace, options, and model. @@ -108,20 +109,29 @@ export async function createPromptContext( // Define retrieval operations const retrieval: Retrieval = { - webSearch: async (q) => { + webSearch: async (q, options) => { + const { provider } = options || {} // Conduct a web search and return the results try { trace.startDetails( `🌐 web search ${HTMLEscape(q)}` ) - const { webPages } = (await bingSearch(q, { trace })) || {} - const files = webPages?.value?.map( - ({ url, snippet }) => - { - filename: url, - content: snippet, - } - ) + let files: WorkspaceFile[] + if (provider === "bing") files = await bingSearch(q, { trace }) + else if (provider === "tavily") + files = await tavilySearch(q, { trace }) + else { + for (const f of [bingSearch, tavilySearch]) { + try { + files = await f(q, { trace }) + break + } catch (e) {} + } + } + if (!files) + throw new Error( + `No search provider configured. See ${DOCS_WEB_SEARCH_URL}.` + ) trace.files(files, { model, secrets: env.secrets }) return files } finally { diff --git a/packages/core/src/types/prompt_template.d.ts b/packages/core/src/types/prompt_template.d.ts index b3b08c30b2..33919234a7 100644 --- a/packages/core/src/types/prompt_template.d.ts +++ b/packages/core/src/types/prompt_template.d.ts @@ -2122,10 +2122,13 @@ interface FuzzSearchOptions { interface Retrieval { /** - * Executers a Bing web search. Requires to configure the BING_SEARCH_API_KEY secret. + * Executers a web search with Tavily or Bing Search. * @param query */ - webSearch(query: string): Promise + webSearch( + query: string, + options?: { provider?: "tavily" | "bing" } + ): Promise /** * Search using similarity distance on embeddings diff --git a/packages/core/src/websearch.ts b/packages/core/src/websearch.ts index 576ee06d06..05a2042176 100644 --- a/packages/core/src/websearch.ts +++ b/packages/core/src/websearch.ts @@ -1,4 +1,9 @@ -import { BING_SEARCH_ENDPOINT } from "./constants" +import { + BING_SEARCH_ENDPOINT, + DOCS_WEB_SEARCH_BING_SEARCH_URL, + DOCS_WEB_SEARCH_TAVILY_URL, + TAVILY_ENDPOINT, +} from "./constants" import { createFetch } from "./fetch" import { runtimeHost } from "./host" import { MarkdownTrace } from "./trace" @@ -19,23 +24,6 @@ function toURLSearchParams(o: any) { return params.toString() } -/** - * Interface representing the response from a search query. - */ -export interface SearchResponse { - webPages?: { - value: WebpageResponse[] - } -} - -/** - * Interface representing a single webpage response. - */ -export interface WebpageResponse { - snippet: string - url: string -} - /** * Performs a Bing search using the given query and options. * Utilizes Bing Search API and constructs the request with query parameters. @@ -56,7 +44,7 @@ export async function bingSearch( responseFilter?: string safeSearch?: string } -): Promise { +): Promise { const { trace, endPoint = BING_SEARCH_ENDPOINT, @@ -68,13 +56,14 @@ export async function bingSearch( } = options || {} // Return an empty response if the query is empty. - if (!q) return {} + if (!q) return [] // Retrieve the API key from the runtime host. const apiKey = await runtimeHost.readSecret("BING_SEARCH_API_KEY") if (!apiKey) throw new Error( - "BING_SEARCH_API_KEY secret is required to use bing search. See https://microsoft.github.io/genaiscript/reference/scripts/web-search/#bing-web-search-configuration." + `BING_SEARCH_API_KEY secret is required to use bing search. See ${DOCS_WEB_SEARCH_BING_SEARCH_URL}.`, + { cause: "missing key" } ) // Construct the query string using provided and default parameters. @@ -109,7 +98,96 @@ export async function bingSearch( } // Parse and return the JSON response, logging the results. - const json = await res.json() + const json = (await res.json()) as { + webPages?: { + value: { + snippet: string + url: string + }[] + } + } + trace?.detailsFenced("results", json, "yaml") + return ( + json.webPages?.value?.map( + ({ snippet, url }) => + ({ filename: url, content: snippet }) satisfies WorkspaceFile + ) || [] + ) +} + +/** + * Performs a Tavily search using the given query and options. + * Utilizes Tavily Search API and constructs the request with query parameters. + * Handles API key retrieval and error management. + * @param q - The search query string. + * @param options - Optional search parameters such as trace, endpoint, count, etc. + * @returns A Promise resolving to a SearchResponse. + * @throws Error if the API key is missing or if the search request fails. + */ +export async function tavilySearch( + q: string, + options?: { + trace?: MarkdownTrace + endPoint?: string + topic?: "general" | "news" + days?: number + count?: number + } +): Promise { + const { + trace, + endPoint = TAVILY_ENDPOINT, + topic, + days, + count, + } = options || {} + + // Return an empty response if the query is empty. + if (!q) return [] + + // Retrieve the API key from the runtime host. + const apiKey = await runtimeHost.readSecret("TAVILY_API_KEY") + if (!apiKey) + throw new Error( + `TAVILY_API_KEY secret is required to use Tavily search. See ${DOCS_WEB_SEARCH_TAVILY_URL}.`, + { cause: "missing key" } + ) + + // Construct the query string using provided and default parameters. + const query = toURLSearchParams({ + query: q, + api_key: apiKey, + topic, + days, + max_results: count, + }) + + // Construct the full URL for the search request. + const url = endPoint + "?" + query + + // Create a fetch function for making the HTTP request. + const fetch = await createFetch() + const res = await fetch(url, { + method: "POST", + }) + + // Log the search response status for tracing purposes. + trace?.itemValue(`Tavily search`, res.statusText) + + // Throw an error if the response is not OK, and log details for debugging. + if (!res.ok) { + trace?.detailsFenced("error response", await res.text()) + throw new Error(`Tavily search failed: ${res.statusText}`) + } + + // Parse and return the JSON response, logging the results. + const json: { + query: string + results: { url: string; description: string }[] + } = await res.json() trace?.detailsFenced("results", json, "yaml") - return json + return json.results.map( + ({ url, description }) => + ({ filename: url, content: description }) satisfies WorkspaceFile + ) } diff --git a/packages/sample/genaisrc/copilot/dataanalyst.genai.mjs b/packages/sample/genaisrc/copilot/dataanalyst.genai.mjs new file mode 100644 index 0000000000..e0989b1ad5 --- /dev/null +++ b/packages/sample/genaisrc/copilot/dataanalyst.genai.mjs @@ -0,0 +1,3 @@ +def("DATA", env.files) +def("QUESTION", env.vars.question) +$`` \ No newline at end of file diff --git a/packages/sample/genaisrc/copilot/mermaid.genai.mjs b/packages/sample/genaisrc/copilot/mermaid.genai.mjs index ce9af3e7e6..173af6dc1a 100644 --- a/packages/sample/genaisrc/copilot/mermaid.genai.mjs +++ b/packages/sample/genaisrc/copilot/mermaid.genai.mjs @@ -1,2 +1,6 @@ +/** + * Inspired by mermAid + * @see https://marketplace.visualstudio.com/items?itemName=ms-vscode.copilot-mermaid-diagram + */ def("CODE", env.files) $`Generate a class diagram using mermaid of the code symbols in the CODE.` \ No newline at end of file From 5922f8826c470b2895890908706ef919987ed219 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 05:15:14 +0000 Subject: [PATCH 2/8] documentation --- .../content/docs/reference/scripts/web-search.mdx | 9 ++++++++- packages/core/src/websearch.ts | 13 ++++++------- packages/sample/genaisrc/bingsearch.genai.js | 6 ------ packages/sample/genaisrc/websearch.genai.js | 10 ++++++++++ 4 files changed, 24 insertions(+), 14 deletions(-) delete mode 100644 packages/sample/genaisrc/bingsearch.genai.js create mode 100644 packages/sample/genaisrc/websearch.genai.js diff --git a/docs/src/content/docs/reference/scripts/web-search.mdx b/docs/src/content/docs/reference/scripts/web-search.mdx index f4e70c18da..2088d8f8ba 100644 --- a/docs/src/content/docs/reference/scripts/web-search.mdx +++ b/docs/src/content/docs/reference/scripts/web-search.mdx @@ -6,7 +6,7 @@ sidebar: order: 15 --- -The `retrieval.webSearch` executes a web search using Tavily or the Bing Web Search. +The `retrieval.webSearch` executes a web search using [Tavily](https://docs.tavily.com/) or the Bing Web Search. ## Web Pages @@ -23,6 +23,13 @@ You can use `fetchText` to download the full content of the web page. ## Tavily Configuration +The [Tavily API](https://docs.tavily.com/docs/rest-api/api-reference#endpoint-post-search) +provides access to a powerfull search engine for LLM agents. + +```txt title=".env" +TAVILY_API_KEY="your-api-key" +``` + ## Bing Web Search configuration The API uses [Bing Web Search v7](https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/overview) to search the web. To use the API, you need to create a Bing Web Search resource in the Azure portal and store the API key in the `.env` file. diff --git a/packages/core/src/websearch.ts b/packages/core/src/websearch.ts index 05a2042176..26e15b4fe1 100644 --- a/packages/core/src/websearch.ts +++ b/packages/core/src/websearch.ts @@ -6,7 +6,7 @@ import { } from "./constants" import { createFetch } from "./fetch" import { runtimeHost } from "./host" -import { MarkdownTrace } from "./trace" +import { MarkdownTrace, TraceOptions } from "./trace" /** * Converts an object into a URL search parameters string. @@ -36,14 +36,13 @@ function toURLSearchParams(o: any) { export async function bingSearch( q: string, options?: { - trace?: MarkdownTrace endPoint?: string count?: number cc?: string freshness?: string responseFilter?: string safeSearch?: string - } + } & TraceOptions ): Promise { const { trace, @@ -80,7 +79,7 @@ export async function bingSearch( const url = endPoint + "?" + query // Create a fetch function for making the HTTP request. - const fetch = await createFetch() + const fetch = await createFetch({ trace }) const res = await fetch(url, { method: "GET", headers: { @@ -127,12 +126,11 @@ export async function bingSearch( export async function tavilySearch( q: string, options?: { - trace?: MarkdownTrace endPoint?: string topic?: "general" | "news" days?: number count?: number - } + } & TraceOptions ): Promise { const { trace, @@ -166,7 +164,8 @@ export async function tavilySearch( const url = endPoint + "?" + query // Create a fetch function for making the HTTP request. - const fetch = await createFetch() + const fetch = await createFetch({ trace }) + console.log({ url }) const res = await fetch(url, { method: "POST", }) diff --git a/packages/sample/genaisrc/bingsearch.genai.js b/packages/sample/genaisrc/bingsearch.genai.js deleted file mode 100644 index f9c2fb20aa..0000000000 --- a/packages/sample/genaisrc/bingsearch.genai.js +++ /dev/null @@ -1,6 +0,0 @@ -script({ - title: "bing search", -}) - -const webPages = await retrieval.webSearch("microsoft") -def("RES", webPages, { language: "json" }) diff --git a/packages/sample/genaisrc/websearch.genai.js b/packages/sample/genaisrc/websearch.genai.js new file mode 100644 index 0000000000..770e64df10 --- /dev/null +++ b/packages/sample/genaisrc/websearch.genai.js @@ -0,0 +1,10 @@ +script({ + title: "web search search", +}) + +const webPages = await retrieval.webSearch("microsoft", { + provider: env.vars.provider, +}) +console.log(webPages) +def("PAGES", webPages, { language: "json" }) +$`Summarize pages.` From 07fea1e1fa067f2896b06583f5685f8e050dd11d Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 05:53:27 +0000 Subject: [PATCH 3/8] updated tavily sampel --- .vscode/settings.json | 1 + packages/core/src/promptcontext.ts | 6 +-- packages/core/src/websearch.ts | 54 +++++++++++---------- packages/sample/genaisrc/websearch.genai.js | 11 +++-- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7565521627..4f571f6343 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -84,6 +84,7 @@ "Textify", "treegrid", "treesitter", + "tvly", "typecheck", "unfence", "urllib", diff --git a/packages/core/src/promptcontext.ts b/packages/core/src/promptcontext.ts index 9f684118c1..dfe9d84bd4 100644 --- a/packages/core/src/promptcontext.ts +++ b/packages/core/src/promptcontext.ts @@ -122,10 +122,8 @@ export async function createPromptContext( files = await tavilySearch(q, { trace }) else { for (const f of [bingSearch, tavilySearch]) { - try { - files = await f(q, { trace }) - break - } catch (e) {} + files = await f(q, { ignoreMissingApiKey: true, trace }) + if (files) break } } if (!files) diff --git a/packages/core/src/websearch.ts b/packages/core/src/websearch.ts index 26e15b4fe1..b9d6eece52 100644 --- a/packages/core/src/websearch.ts +++ b/packages/core/src/websearch.ts @@ -7,6 +7,7 @@ import { import { createFetch } from "./fetch" import { runtimeHost } from "./host" import { MarkdownTrace, TraceOptions } from "./trace" +import { deleteUndefinedValues, logVerbose } from "./util" /** * Converts an object into a URL search parameters string. @@ -36,6 +37,7 @@ function toURLSearchParams(o: any) { export async function bingSearch( q: string, options?: { + ignoreMissingApiKey?: boolean endPoint?: string count?: number cc?: string @@ -45,6 +47,7 @@ export async function bingSearch( } & TraceOptions ): Promise { const { + ignoreMissingApiKey, trace, endPoint = BING_SEARCH_ENDPOINT, count, @@ -59,11 +62,13 @@ export async function bingSearch( // Retrieve the API key from the runtime host. const apiKey = await runtimeHost.readSecret("BING_SEARCH_API_KEY") - if (!apiKey) + if (!apiKey) { + if (ignoreMissingApiKey) return undefined throw new Error( `BING_SEARCH_API_KEY secret is required to use bing search. See ${DOCS_WEB_SEARCH_BING_SEARCH_URL}.`, { cause: "missing key" } ) + } // Construct the query string using provided and default parameters. const query = toURLSearchParams({ @@ -88,12 +93,12 @@ export async function bingSearch( }) // Log the search response status for tracing purposes. - trace?.itemValue(`Bing search`, res.statusText) + trace?.itemValue(`Bing search`, res.status + " " + res.statusText) // Throw an error if the response is not OK, and log details for debugging. if (!res.ok) { trace?.detailsFenced("error response", await res.text()) - throw new Error(`Bing search failed: ${res.statusText}`) + throw new Error(`Bing search failed: ${res.status} ${res.statusText}`) } // Parse and return the JSON response, logging the results. @@ -126,18 +131,14 @@ export async function bingSearch( export async function tavilySearch( q: string, options?: { + ignoreMissingApiKey?: boolean endPoint?: string - topic?: "general" | "news" - days?: number - count?: number } & TraceOptions ): Promise { const { trace, + ignoreMissingApiKey, endPoint = TAVILY_ENDPOINT, - topic, - days, - count, } = options || {} // Return an empty response if the query is empty. @@ -145,48 +146,51 @@ export async function tavilySearch( // Retrieve the API key from the runtime host. const apiKey = await runtimeHost.readSecret("TAVILY_API_KEY") - if (!apiKey) + if (!apiKey) { + if (ignoreMissingApiKey) return undefined throw new Error( `TAVILY_API_KEY secret is required to use Tavily search. See ${DOCS_WEB_SEARCH_TAVILY_URL}.`, { cause: "missing key" } ) + } // Construct the query string using provided and default parameters. - const query = toURLSearchParams({ + const body = deleteUndefinedValues({ query: q, api_key: apiKey, - topic, - days, - max_results: count, }) - // Construct the full URL for the search request. - const url = endPoint + "?" + query - // Create a fetch function for making the HTTP request. const fetch = await createFetch({ trace }) - console.log({ url }) - const res = await fetch(url, { + const res = await fetch(endPoint, { method: "POST", + headers: { + ["Content-Type"]: "application/json", + Accept: "application/json", + }, + retryOn: [429], + body: JSON.stringify(body), }) // Log the search response status for tracing purposes. - trace?.itemValue(`Tavily search`, res.statusText) + trace?.itemValue(`Tavily search`, res.status + " " + res.statusText) // Throw an error if the response is not OK, and log details for debugging. if (!res.ok) { - trace?.detailsFenced("error response", await res.text()) - throw new Error(`Tavily search failed: ${res.statusText}`) + const err = await res.text() + trace?.detailsFenced("error response", err) + logVerbose(err) + throw new Error(`Tavily search failed: ${res.status} ${res.statusText}`) } // Parse and return the JSON response, logging the results. const json: { query: string - results: { url: string; description: string }[] + results: { url: string; content: string }[] } = await res.json() trace?.detailsFenced("results", json, "yaml") return json.results.map( - ({ url, description }) => - ({ filename: url, content: description }) satisfies WorkspaceFile + ({ url, content }) => + ({ filename: url, content }) satisfies WorkspaceFile ) } diff --git a/packages/sample/genaisrc/websearch.genai.js b/packages/sample/genaisrc/websearch.genai.js index 770e64df10..c2ab65eec9 100644 --- a/packages/sample/genaisrc/websearch.genai.js +++ b/packages/sample/genaisrc/websearch.genai.js @@ -2,9 +2,12 @@ script({ title: "web search search", }) -const webPages = await retrieval.webSearch("microsoft", { - provider: env.vars.provider, -}) +const webPages = await retrieval.webSearch( + "what are the last nvidia results?", + { + provider: env.vars.provider, + } +) console.log(webPages) -def("PAGES", webPages, { language: "json" }) +def("PAGES", webPages) $`Summarize pages.` From 6a715508bc4e896263e7f2aadc4d878b8828ffa1 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 05:59:48 +0000 Subject: [PATCH 4/8] docs --- docs/src/content/docs/index.mdx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 67ad5b664e..1a6387f1c9 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -258,7 +258,7 @@ The quick brown fox jumps over the lazy dog. -Grep or fuzz search [files](/genaiscript/referen/script/files) +Grep or fuzz search [files](/genaiscript/reference/script/files) ```js wrap const { files } = await workspace.grep(/[a-z][a-z0-9]+/, { globs: "*.md" }) @@ -266,7 +266,17 @@ const { files } = await workspace.grep(/[a-z][a-z0-9]+/, { globs: "*.md" }) - + + +[Web search](/genaiscript/reference/scripts/web-search) using Bing or Tavily. + +```js wrap +const pages = await retreival.webSearch("what are the latest news about AI?") +``` + + + + Browse and scrape the web with [Playwright](/genaiscript/reference/scripts/browse). From 08d6a2a8b9a4bd6bd129137068ca9658ab08016d Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 06:05:25 +0000 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=F0=9F=8E=89=20add=20count=20option?= =?UTF-8?q?=20to=20webSearch=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/promptcontext.ts | 8 ++++---- packages/core/src/types/prompt_template.d.ts | 2 +- packages/core/src/websearch.ts | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/src/promptcontext.ts b/packages/core/src/promptcontext.ts index dfe9d84bd4..a8a8123913 100644 --- a/packages/core/src/promptcontext.ts +++ b/packages/core/src/promptcontext.ts @@ -110,19 +110,19 @@ export async function createPromptContext( // Define retrieval operations const retrieval: Retrieval = { webSearch: async (q, options) => { - const { provider } = options || {} + const { provider, count } = options || {} // Conduct a web search and return the results try { trace.startDetails( `🌐 web search ${HTMLEscape(q)}` ) let files: WorkspaceFile[] - if (provider === "bing") files = await bingSearch(q, { trace }) + if (provider === "bing") files = await bingSearch(q, { trace, count }) else if (provider === "tavily") - files = await tavilySearch(q, { trace }) + files = await tavilySearch(q, { trace, count }) else { for (const f of [bingSearch, tavilySearch]) { - files = await f(q, { ignoreMissingApiKey: true, trace }) + files = await f(q, { ignoreMissingApiKey: true, trace, count }) if (files) break } } diff --git a/packages/core/src/types/prompt_template.d.ts b/packages/core/src/types/prompt_template.d.ts index 33919234a7..e568618b1c 100644 --- a/packages/core/src/types/prompt_template.d.ts +++ b/packages/core/src/types/prompt_template.d.ts @@ -2127,7 +2127,7 @@ interface Retrieval { */ webSearch( query: string, - options?: { provider?: "tavily" | "bing" } + options?: { count?: number; provider?: "tavily" | "bing" } ): Promise /** diff --git a/packages/core/src/websearch.ts b/packages/core/src/websearch.ts index b9d6eece52..f75d1f1b32 100644 --- a/packages/core/src/websearch.ts +++ b/packages/core/src/websearch.ts @@ -133,10 +133,12 @@ export async function tavilySearch( options?: { ignoreMissingApiKey?: boolean endPoint?: string + count?: number } & TraceOptions ): Promise { const { trace, + count, ignoreMissingApiKey, endPoint = TAVILY_ENDPOINT, } = options || {} @@ -158,6 +160,7 @@ export async function tavilySearch( const body = deleteUndefinedValues({ query: q, api_key: apiKey, + max_results: count, }) // Create a fetch function for making the HTTP request. From e59601bcf1efa7c4722d2424d3e823371e08f725 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 06:07:35 +0000 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=F0=9F=94=8D=20add=20Tavily=20as=20?= =?UTF-8?q?web=20search=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/components/BuiltinTools.mdx | 2 +- .../src/content/docs/reference/scripts/system.mdx | 15 +++++++++------ .../genaisrc/system.retrieval_web_search.genai.js | 13 ++++++++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/src/components/BuiltinTools.mdx b/docs/src/components/BuiltinTools.mdx index e0df464b06..fe46013fa2 100644 --- a/docs/src/components/BuiltinTools.mdx +++ b/docs/src/components/BuiltinTools.mdx @@ -41,7 +41,7 @@ import { LinkCard } from '@astrojs/starlight/components'; - + diff --git a/docs/src/content/docs/reference/scripts/system.mdx b/docs/src/content/docs/reference/scripts/system.mdx index bea904b0f7..c5ad86f8f3 100644 --- a/docs/src/content/docs/reference/scripts/system.mdx +++ b/docs/src/content/docs/reference/scripts/system.mdx @@ -2788,18 +2788,17 @@ Web Search Function to do a web search. -- tool `retrieval_web_search`: Search the web for a user query using Bing Search. +- tool `retrieval_web_search`: Search the web for a user query using Tavily or Bing Search. `````js wrap title="system.retrieval_web_search" system({ title: "Web Search", description: "Function to do a web search.", - secrets: ["BING_SEARCH_ENDPOINT"], }) defTool( "retrieval_web_search", - "Search the web for a user query using Bing Search.", + "Search the web for a user query using Tavily or Bing Search.", { type: "object", properties: { @@ -2807,16 +2806,20 @@ defTool( type: "string", description: "Search query.", }, + count: { + type: "integer", + description: "Number of results to return.", + }, }, required: ["query"], }, async (args) => { - const { query } = args - const webPages = await retrieval.webSearch(query) + const { query, count } = args + const webPages = await retrieval.webSearch(query, { count }) return YAML.stringify( webPages.map((f) => ({ url: f.filename, - snippet: f.content, + content: f.content, })) ) } diff --git a/packages/core/src/genaisrc/system.retrieval_web_search.genai.js b/packages/core/src/genaisrc/system.retrieval_web_search.genai.js index 15f8cea9d7..7b2d313c9c 100644 --- a/packages/core/src/genaisrc/system.retrieval_web_search.genai.js +++ b/packages/core/src/genaisrc/system.retrieval_web_search.genai.js @@ -1,12 +1,11 @@ system({ title: "Web Search", description: "Function to do a web search.", - secrets: ["BING_SEARCH_ENDPOINT"], }) defTool( "retrieval_web_search", - "Search the web for a user query using Bing Search.", + "Search the web for a user query using Tavily or Bing Search.", { type: "object", properties: { @@ -14,16 +13,20 @@ defTool( type: "string", description: "Search query.", }, + count: { + type: "integer", + description: "Number of results to return.", + }, }, required: ["query"], }, async (args) => { - const { query } = args - const webPages = await retrieval.webSearch(query) + const { query, count } = args + const webPages = await retrieval.webSearch(query, { count }) return YAML.stringify( webPages.map((f) => ({ url: f.filename, - snippet: f.content, + content: f.content, })) ) } From a85937801a118470d14f92ec528b8fa088d6c58d Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 06:11:04 +0000 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20web-search=20agen?= =?UTF-8?q?t=20to=20system=20and=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/components/BuiltinAgents.mdx | 1 + .../content/docs/reference/scripts/system.mdx | 29 +++++++++++++++++++ .../src/genaisrc/system.agent_web.genai.mjs | 16 ++++++++++ 3 files changed, 46 insertions(+) create mode 100644 packages/core/src/genaisrc/system.agent_web.genai.mjs diff --git a/docs/src/components/BuiltinAgents.mdx b/docs/src/components/BuiltinAgents.mdx index 8bedd1a505..590d5a5124 100644 --- a/docs/src/components/BuiltinAgents.mdx +++ b/docs/src/components/BuiltinAgents.mdx @@ -13,3 +13,4 @@ import { LinkCard } from '@astrojs/starlight/components'; + diff --git a/docs/src/content/docs/reference/scripts/system.mdx b/docs/src/content/docs/reference/scripts/system.mdx index c5ad86f8f3..221d8c0b0e 100644 --- a/docs/src/content/docs/reference/scripts/system.mdx +++ b/docs/src/content/docs/reference/scripts/system.mdx @@ -360,6 +360,35 @@ defAgent( ````` +### `system.agent_web` + +Agent that can search the web. + + + + + +`````js wrap title="system.agent_web" +system({ + title: "Agent that can search the web.", +}) + +const model = env.vars.agentWebSearchModel + +defAgent( + "web-search", + "search the web to accomplish tasks.", + `Your are a helpful LLM agent that can use web search. + Answer the question in QUERY.`, + { + model, + system: ["system.retrieval_fuzz_search", "system.retrieval_web_search"], + } +) + +````` + + ### `system.annotations` Emits annotations compatible with GitHub Actions diff --git a/packages/core/src/genaisrc/system.agent_web.genai.mjs b/packages/core/src/genaisrc/system.agent_web.genai.mjs new file mode 100644 index 0000000000..3dd1d2b617 --- /dev/null +++ b/packages/core/src/genaisrc/system.agent_web.genai.mjs @@ -0,0 +1,16 @@ +system({ + title: "Agent that can search the web.", +}) + +const model = env.vars.agentWebSearchModel + +defAgent( + "web-search", + "search the web to accomplish tasks.", + `Your are a helpful LLM agent that can use web search. + Answer the question in QUERY.`, + { + model, + system: ["system.retrieval_fuzz_search", "system.retrieval_web_search"], + } +) From f558baebae3c45f23f0a6b8de35e568c28b4ad11 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 14 Nov 2024 15:57:06 +0000 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20enhance=20error=20messages=20fo?= =?UTF-8?q?r=20Azure=20tokens=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/nodehost.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/nodehost.ts b/packages/cli/src/nodehost.ts index b86391c62a..67f28320ff 100644 --- a/packages/cli/src/nodehost.ts +++ b/packages/cli/src/nodehost.ts @@ -214,7 +214,9 @@ export class NodeHost implements RuntimeHost { options ) if (!azureToken) - throw new Error("Azure OpenAI token not available") + throw new Error( + `Azure OpenAI token not available for ${modelId}` + ) tok.token = "Bearer " + azureToken.token } else if ( tok.provider === MODEL_PROVIDER_AZURE_SERVERLESS_MODELS @@ -223,22 +225,27 @@ export class NodeHost implements RuntimeHost { tok.azureCredentialsType, options ) - if (!azureToken) throw new Error("Azure AI token not available") + if (!azureToken) + throw new Error( + `Azure AI token not available for ${modelId}` + ) tok.token = "Bearer " + azureToken.token } } if (!tok) { if (!modelId) throw new Error( - "could not determine default model from current configuration" + "Could not determine default model from current configuration" ) const { provider } = parseModelIdentifier(modelId) if (provider === MODEL_PROVIDER_AZURE_OPENAI) - throw new Error("Azure OpenAI not configured") + throw new Error(`Azure OpenAI not configured for ${modelId}`) else if (provider === MODEL_PROVIDER_AZURE_SERVERLESS_OPENAI) - throw new Error("Azure AI OpenAI Serverless not configured") + throw new Error( + `Azure AI OpenAI Serverless not configured for ${modelId}` + ) else if (provider === MODEL_PROVIDER_AZURE_SERVERLESS_MODELS) - throw new Error("Azure AI Models not configured") + throw new Error(`Azure AI Models not configured for ${modelId}`) } if (!tok && this.clientLanguageModel) { return {