generator-begcode
Version:
Spring Boot + Angular/React/Vue in one handy generator
169 lines (165 loc) • 6.77 kB
JavaScript
import axios from 'axios';
import { AgentOutputType, ArrayRecombiner, ChatMessageBuilder, Prompt, Rag, TextChunker, trimText, } from '../agent-core/index.js';
import { FUNCTION_CALL_FAILED, FUNCTION_CALL_SUCCESS_CONTENT } from '../agents/Scripter/utils.js';
import { LlmAgentFunctionBase, processWebpage, searchOnGoogle } from './utils/index.js';
export class WebSearchFunction extends LlmAgentFunctionBase {
constructor(llm, tokenizer) {
super(llm, tokenizer);
}
name = 'web_search';
description = 'Searches the web for multiple queries.';
parameters = {
type: 'object',
properties: {
queries: {
type: 'array',
items: {
type: 'string',
},
description: 'Queries with all details to search the web for in parallel',
},
},
required: ['queries'],
additionalProperties: false,
};
buildExecutor(agent) {
return async (params) => {
const searches = [];
for (const query of params.queries) {
searches.push(this.runQuery(agent, query, JSON.stringify({ queries: [query] })));
}
const results = await Promise.all(searches);
return {
outputs: results.flatMap(x => x.outputs),
messages: results.flatMap(x => x.messages),
};
};
}
async runQuery({ context }, query, rawParams) {
try {
let googleResults;
if (typeof window === 'object') {
const results = await axios.get(`/api/search?query=${query}`);
googleResults = results.data.googleResults;
}
else {
if (!context.env.SERP_API_KEY) {
throw new Error('SERP_API_KEY environment variable is required to use the websearch plugin. See env.template for help');
}
googleResults = await searchOnGoogle(query, context.env.SERP_API_KEY);
}
const urls = googleResults.map(x => x.url).filter(u => !u.endsWith('.pdf'));
const searchMatches = await this.searchInPages({
urls,
query,
context,
});
const analyzeChunkMatchesPrompt = new Prompt()
.text(`
I will give you chunks of text from different webpages.
I want to extract ${query}. Keep in mind some information may not be properly formatted.
Do your best to extract as much information as you can.
Prioritize decimal precision. Aim for answers with 3 decimal places, if possible; if not settle for 2 decimal places.
Only take 1 decimal or rounded numbers when absolutely necessary.
Chunks: ${searchMatches.join('\n------------\n')}
`)
.line('Specify if the information is incomplete but still return it')
.toString();
const analysisFromChunks = await this.askLlm(analyzeChunkMatchesPrompt, {
maxResponseTokens: 200,
});
return this.onSuccess({ queries: [query] }, analysisFromChunks, rawParams, context.variables);
}
catch (err) {
return this.onError({ queries: [query] }, err.toString(), rawParams, context.variables);
}
}
onSuccess(params, result, rawParams, variables) {
return {
outputs: [
{
type: AgentOutputType.Success,
title: `Web search for '${params.queries}'`,
content: FUNCTION_CALL_SUCCESS_CONTENT(this.name, params, `Found the following result for the web search: '${params.queries}'` +
'\n--------------\n' +
`${result}\n` +
'\n--------------\n'),
},
],
messages: [
ChatMessageBuilder.functionCall(this.name, rawParams),
ChatMessageBuilder.functionCallResult(this.name, `Found the following result for the web search: '${params.queries}'\`\`\`
${result}\n\`\`\``),
],
};
}
onError(params, error, rawParams, variables) {
return {
outputs: [
{
type: AgentOutputType.Error,
title: `Web search for '${params.queries}'`,
content: FUNCTION_CALL_FAILED(params, this.name, error),
},
],
messages: [
ChatMessageBuilder.functionCall(this.name, rawParams),
ChatMessageBuilder.functionCallResult(this.name, `Error web searching for '${params.queries}'\n\`\`\`
${trimText(error, 300)}\n\`\`\``),
],
};
}
async searchInPages(params) {
const urlsContents = await Promise.all(params.urls.map(async (url) => {
try {
let response;
if (typeof window === 'object') {
const result = await axios.get(`/api/process-web-page?url=${url}`);
response = result.data.text;
}
else {
response = await processWebpage(url);
}
return {
url,
response,
};
}
catch (e) {
return {
url,
response: '',
};
}
}));
const webpagesChunks = urlsContents.map(webpageContent => ({
url: webpageContent.url,
chunks: TextChunker.fixedCharacterLength(webpageContent.response, { chunkLength: 550, overlap: 110 }),
}));
const results = await Promise.all(webpagesChunks.map(async (webpageChunks) => {
const items = webpageChunks.chunks.map(chunk => ({
chunk,
url: webpageChunks.url,
}));
const matches = await (await Rag.standard(params.context))
.addItems(items)
.selector(x => x.chunk)
.query(params.query)
.recombine(ArrayRecombiner.standard({
limit: 4,
unique: true,
}));
return matches;
}));
const otherResults = await (await Rag.standard(params.context))
.addItems(results.flat())
.selector(x => x.chunk)
.query(params.query)
.recombine(ArrayRecombiner.standard({
limit: 10,
unique: true,
sort: 'index',
}));
return otherResults.map(x => x.chunk);
}
}