-
Notifications
You must be signed in to change notification settings - Fork 280
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor web search skill to use new web browsing tool
- Loading branch information
Showing
17 changed files
with
490 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { AgentMessage, AgentTask } from '@/types'; | ||
import { relevantInfoExtractionAgent } from './relevantInfoExtraction/agent'; | ||
|
||
export const largeTextExtract = async ( | ||
id: string, | ||
objective: string, | ||
largeString: string, | ||
task: AgentTask, | ||
callback?: (message: AgentMessage) => void, | ||
) => { | ||
const chunkSize = 15000; | ||
const overlap = 500; | ||
let notes = ''; | ||
|
||
// for status message | ||
const total = Math.ceil(largeString.length / (chunkSize - overlap)); | ||
|
||
for (let i = 0; i < largeString.length; i += chunkSize - overlap) { | ||
callback?.({ | ||
content: ` - chunk ${i / (chunkSize - overlap) + 1} of ${total}\n`, | ||
style: 'log', | ||
type: task.skill, | ||
id: id, | ||
taskId: task.id.toString(), | ||
status: 'running', | ||
}); | ||
|
||
const chunk = largeString.slice(i, i + chunkSize); | ||
|
||
const response = await relevantInfoExtractionAgent( | ||
objective, | ||
task.task, | ||
notes, | ||
chunk, | ||
); | ||
notes += response; | ||
} | ||
return notes; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
export const searchQueryPrompt = (task: string, dependent_task: string) => { | ||
return `You are an AI assistant tasked with generating a Google search query based on the following task: ${task}. | ||
${ | ||
dependent_task.length > 0 | ||
? `Consider the results of dependent tasks: ${dependent_task}.` | ||
: '' | ||
} | ||
If the task looks like a search query, return the identical search query as your response. | ||
Search Query:`; | ||
}; | ||
|
||
export const analystPrompt = (results: string, language: string) => { | ||
return `You are an expert analyst. Rewrite the following information as one report without removing any facts. | ||
Report must be answered in ${language}. | ||
\n###INFORMATION:${results}.\n###REPORT:`; | ||
}; | ||
|
||
export const textCompletionToolPrompt = ( | ||
objective: string, | ||
language: string, | ||
task: string, | ||
dependentTaskOutput: string, | ||
) => { | ||
let prompt = `Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. | ||
Your objective: ${objective}. Your task: ${task} | ||
Output must be answered in ${language}.\n | ||
`; | ||
if (dependentTaskOutput !== '') { | ||
prompt += `Your dependent tasks: ${dependentTaskOutput}\n OUTPUT:`; | ||
} else { | ||
prompt += '\nOUTPUT:'; | ||
} | ||
return prompt; | ||
}; |
44 changes: 44 additions & 0 deletions
44
src/agents/elf/tools/search/relevantInfoExtraction/agent.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { OpenAIChat } from 'langchain/llms/openai'; | ||
import { relevantInfoExtractionPrompt } from './prompt'; | ||
import { LLMChain } from 'langchain/chains'; | ||
import axios from 'axios'; | ||
|
||
// TODO: Only client-side requests are allowed. | ||
// To use the environment variable API key, the request must be implemented from the server side. | ||
|
||
export const relevantInfoExtractionAgent = async ( | ||
objective: string, | ||
task: string, | ||
notes: string, | ||
chunk: string, | ||
signal?: AbortSignal, | ||
) => { | ||
const modelName = 'gpt-3.5-turbo-16k-0613'; // use a fixed model | ||
const prompt = relevantInfoExtractionPrompt(); | ||
const llm = new OpenAIChat( | ||
{ | ||
modelName, | ||
temperature: 0.7, | ||
maxTokens: 800, | ||
topP: 1, | ||
stop: ['###'], | ||
}, | ||
{ baseOptions: { signal: signal } }, | ||
); | ||
const chain = new LLMChain({ llm: llm, prompt }); | ||
try { | ||
const response = await chain.call({ | ||
objective, | ||
task, | ||
notes, | ||
chunk, | ||
}); | ||
return response.text; | ||
} catch (error: any) { | ||
if (error.name === 'AbortError') { | ||
return null; | ||
} | ||
console.log('error: ', error); | ||
return 'Failed to extract relevant information.'; | ||
} | ||
}; |
18 changes: 18 additions & 0 deletions
18
src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { | ||
ChatPromptTemplate, | ||
HumanMessagePromptTemplate, | ||
SystemMessagePromptTemplate, | ||
} from 'langchain/prompts'; | ||
|
||
export const relevantInfoExtractionPrompt = () => { | ||
const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; | ||
const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes:`; | ||
const prompt = ChatPromptTemplate.fromPromptMessages([ | ||
SystemMessagePromptTemplate.fromTemplate(systemTemplate), | ||
HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), | ||
]); | ||
|
||
prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; | ||
|
||
return prompt; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { simplifySearchResults } from '@/agents/common/tools/webSearch'; | ||
import { AgentTask, AgentMessage } from '@/types'; | ||
import { analystPrompt, searchQueryPrompt } from './prompt'; | ||
import { textCompletionTool } from '../textCompletionTool'; | ||
import { largeTextExtract } from './largeTextExtract'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import { webSearch } from './webSearch'; | ||
import { webScrape } from '../webScrape'; | ||
|
||
export const webBrowsing = async ( | ||
objective: string, | ||
task: AgentTask, | ||
dependentTasksOutput: string, | ||
messageCallback: (message: AgentMessage) => void, | ||
verbose: boolean = false, | ||
modelName: string = 'gpt-3.5-turbo', | ||
language: string = 'en', | ||
) => { | ||
let id = uuidv4(); | ||
const prompt = searchQueryPrompt( | ||
task.task, | ||
dependentTasksOutput.slice(0, 3500), | ||
); | ||
const searchQuery = await textCompletionTool(prompt, id, task, modelName); | ||
const trimmedQuery = searchQuery?.replace(/^"|"$/g, ''); // remove quotes from the search query | ||
|
||
let message = `Search query: ${trimmedQuery}\n`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
const searchResults = await webSearch(trimmedQuery || ''); | ||
if (!searchResults) { | ||
return 'Failed to search.'; | ||
} | ||
|
||
const simplifiedSearchResults = simplifySearchResults(searchResults); | ||
message = `✅ Completed search. \nNow reading content.\n`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
|
||
let results = ''; | ||
let index = 1; | ||
let completedCount = 0; | ||
const MaxCompletedCount = 3; | ||
// Loop through search results | ||
for (const searchResult of simplifiedSearchResults) { | ||
if (completedCount >= MaxCompletedCount) break; | ||
|
||
// Extract the URL from the search result | ||
const url = searchResult.link; | ||
let title = `${index}. Reading: ${url} ...`; | ||
|
||
message = `${title}\n`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
|
||
const content = await webScrape(url); | ||
if (!content) { | ||
let message = ` - Failed to read content. Skipped. \n`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
continue; | ||
} | ||
|
||
title = `${index}. Extracting relevant info...`; | ||
message = ` - Content reading completed. Length:${content?.length}. Now extracting relevant info...\n`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
|
||
if (content?.length === 0) { | ||
let message = ` - Content too short. Skipped. \n`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
index += 1; | ||
continue; | ||
} | ||
|
||
message = ` - Extracting relevant information\n`; | ||
title = `${index}. Extracting relevant info...`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
const info = await largeTextExtract( | ||
id, | ||
objective, | ||
content.slice(0, 20000), | ||
task, | ||
messageCallback, | ||
); | ||
|
||
message = ` - Relevant info: ${info | ||
.slice(0, 100) | ||
.replace(/\r?\n/g, '')} ...\n`; | ||
|
||
title = `${index}. Relevant info...`; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
|
||
results += `${info}. `; | ||
index += 1; | ||
completedCount += 1; | ||
} | ||
|
||
message = 'Analyzing results...\n'; | ||
callbackSearchStatus(id, message, task, messageCallback, verbose); | ||
|
||
const outputId = uuidv4(); | ||
const ap = analystPrompt(results, language); | ||
const analyzedResults = await textCompletionTool( | ||
ap, | ||
outputId, | ||
task, | ||
modelName, | ||
messageCallback, | ||
); | ||
|
||
// callback to search logs | ||
const msg: AgentMessage = { | ||
id, | ||
taskId: task.id.toString(), | ||
type: task.skill, | ||
content: message, | ||
title: task.task, | ||
status: 'complete', | ||
}; | ||
messageCallback(msg); | ||
|
||
return analyzedResults; | ||
}; | ||
|
||
const callbackSearchStatus = ( | ||
id: string, | ||
message: string, | ||
task: AgentTask, | ||
messageCallback: (message: AgentMessage) => void, | ||
verbose: boolean = false, | ||
) => { | ||
if (verbose) { | ||
console.log(message); | ||
} | ||
|
||
messageCallback({ | ||
id, | ||
taskId: task.id.toString(), | ||
type: task.skill, | ||
icon: '🔎', | ||
style: 'log', | ||
content: message, | ||
title: task.task, | ||
status: 'running', | ||
}); | ||
}; |
Oops, something went wrong.