daggerai
Version:
A simple and powerful Typescript based agent framework to help businesses thrive in the AI Agent revolution.
305 lines (249 loc) • 7.5 kB
text/typescript
import { LLM } from '../llm/base'
import { Agent } from './agent'
import {
convertToXmlTag,
convertZodSchemaToJson,
dedent,
interpolateVariablesIntoPrompt,
logWithColor,
removeDiacritics,
} from './helpers'
import { Node } from './node'
import {
AgentAction,
AgentFinish,
AgentStep,
ParserError,
SquadResponseParser,
} from './parser'
import { SQUAD_PROMPTS } from './prompts'
import { Squad } from './squad'
import { Tool, ToolError, ToolResponse, ToolRunner } from './tool'
export class Task extends Node {
override type: string = 'task'
agent: Agent
name: string
description: string
expectedOutput: string
tools: Tool[]
memories: string[]
inputs: TaskInput[]
llm: LLM
// the max attemps to execute this task
maxAttempts: number = 10
constructor(params: TaskParams) {
super()
this.id = params.id || ''
this.name = params.name
this.description = params.description
this.agent = params.agent
this.expectedOutput = params.expectedOutput
this.tools = params.tools || []
this.inputs = params.inputs || []
this.memories = params.memories || []
this.llm = params.llm
}
async execute(squad: Squad) {
let llmPrompt = this.buildInitialPrompt(squad)
const llmParser = new SquadResponseParser()
const toolRunner = new ToolRunner(this.tools, squad.events)
squad.events.emit('agent.started', {
agent: this.agent.id,
name: this.name,
task: this.id,
output: `Executing task *${this.name}*`,
})
let intermediateSteps: [AgentAction | null, ToolResponse | undefined][] = []
let iterations = 0
let sources: any[] = []
while (this.shouldContinue(iterations)) {
if (squad.verbose) {
logWithColor(`Running Iteration ${iterations}`, 'blue')
}
try {
let promptWithSteps =
llmPrompt + this.buildIntermediateStepsPrompt(intermediateSteps)
if (squad.verbose) {
logWithColor(`LLM Prompt: ${promptWithSteps}`, 'yellow')
}
squad.events.emit('agent.thinking', {
agent: this.agent.id,
name: this.name,
task: this.id,
output: '',
})
const response = await this.llm.invoke(promptWithSteps)
if (squad.verbose) {
logWithColor(response as string, 'green')
}
const nextStep = llmParser.parse(response as string)
if (squad.verbose) {
logWithColor(`Next step: ${JSON.stringify(nextStep)}`, 'magenta')
}
if (nextStep instanceof AgentFinish) {
this.output = nextStep.response
squad.events.emit('agent.finished', {
agent: this.agent.id,
name: this.name,
output: this.output,
task: this.id,
sources,
})
return this.output
}
if (nextStep instanceof AgentStep) {
intermediateSteps.push([
nextStep.action,
{ text: nextStep.observation! },
])
const action = nextStep.action
const output = await toolRunner.run({
tool: action.tool,
toolInput: action.toolInput,
})
if (output instanceof ToolError) {
intermediateSteps.push([null, { text: output.observation }])
} else {
logWithColor(`Tool output: ${JSON.stringify(output)}`, 'red')
if (output.sources) {
sources = [...sources, ...output.sources]
}
intermediateSteps.push([null, output])
}
}
} catch (error) {
const err = error as Error
if (squad.verbose) {
logWithColor(err.message, 'red')
}
if (error instanceof ParserError) {
intermediateSteps.push([null, { text: error.observation }])
} else {
intermediateSteps.push([null, { text: err.message }])
}
}
logWithColor(`Finished iteration`, 'red')
iterations++
}
return 'The agent exceeded the maximum number of attempts.'
}
shouldContinue(iterations: number) {
return iterations < this.maxAttempts
}
getToolsPrompt() {
return interpolateVariablesIntoPrompt(SQUAD_PROMPTS.tools, {
toolNames: this.tools.map(t => t.name).join(', '),
tools: this.tools
.map(
t =>
`${t.name}, ${t.description}. Params: ${convertZodSchemaToJson(
t.schema,
)}`,
)
.join('\n'),
})
}
buildIntermediateStepsPrompt(
intermediateSteps: [AgentAction | null, ToolResponse | undefined][],
) {
let prompt = ''
if (intermediateSteps.length) {
intermediateSteps.forEach(([action, response]) => {
if (action) {
prompt += `\n\n${action.log}`
}
if (response && response.text) {
prompt += `\nObservation: ${response.text}`
}
})
}
return prompt
}
getUserInputsPrompt() {
if (!this.inputs || !this.inputs.length) {
return ''
}
let finalUserInputs = '\nHere is additional data for your tasks:\n'
for (const input of this.inputs) {
if (!input.value) continue
const tag = convertToXmlTag(input.label, input.value)
finalUserInputs += `\n${tag}\n`
}
return finalUserInputs
}
getNoToolsPrompt() {
return SQUAD_PROMPTS.noTools
}
getExpectedOutputPrompt() {
return interpolateVariablesIntoPrompt(SQUAD_PROMPTS.expectedOutput, {
expectedOutput: this.expectedOutput,
})
}
getTaskKickoffPrompt() {
return SQUAD_PROMPTS.kickoff
}
getCurrentTaskPrompt(squad: Squad) {
let taskPrompt = interpolateVariablesIntoPrompt(SQUAD_PROMPTS.task, {
input: this.description,
})
let previousTasksIds = this.adjancentFrom
if (!previousTasksIds.length) {
return taskPrompt
}
const previousTasks = squad.nodesById(previousTasksIds)
const taskContext = previousTasks.map(t => t.nodeOutput()).join('\n')
if (taskContext) {
taskPrompt += interpolateVariablesIntoPrompt(
SQUAD_PROMPTS.taskWithContext,
{ context: taskContext },
)
}
return dedent`${taskPrompt}`
}
getAdditionalInstructions(squad: Squad) {
if (squad.instructions) {
return `\n\n<important_user_instructions>${squad.instructions}</important_user_instructions>`
}
return ''
}
/**
* Builds the initial prompt for the LLM.
* It uses different prompts if there are tools involved in the task.
*/
buildInitialPrompt(squad: Squad) {
const isTaskWithsTools = this.tools.length > 0
let prompt = this.agent.getRolePlaying()
if (isTaskWithsTools) {
prompt += this.getToolsPrompt()
} else {
prompt += this.getNoToolsPrompt()
}
prompt += this.getCurrentTaskPrompt(squad)
prompt += this.getUserInputsPrompt()
prompt += this.getExpectedOutputPrompt()
prompt += this.getAdditionalInstructions(squad)
prompt += this.getTaskKickoffPrompt()
return dedent`${prompt}`
}
override nodeOutput() {
return `${convertToXmlTag(
removeDiacritics(this.name).toLocaleLowerCase(),
this.output || '',
)}`
}
}
export interface TaskParams {
id?: string
name: string
description: string
expectedOutput: string
agent: Agent
llm: LLM
tools?: Tool[]
inputs?: TaskInput[]
memories?: string[]
}
export interface TaskInput {
label: string
value: string
}