-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathagent.ts
118 lines (103 loc) · 3.73 KB
/
agent.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import s from 'dedent'
import { zodFunction, zodResponseFormat } from 'openai/helpers/zod.js'
import { z } from 'zod'
import { openai, Provider } from './models.js'
import { finish, request, response, WorkflowState } from './state.js'
import { Tool } from './tool.js'
import { Message } from './types.js'
import { Workflow } from './workflow.js'
export type AgentOptions = Partial<Agent>
export type Agent = {
description?: string
tools: {
[key: string]: Tool
}
provider: Provider
run: (state: WorkflowState, context: Message[], workflow: Workflow) => Promise<WorkflowState>
}
export const agent = (options: AgentOptions = {}): Agent => {
const { description, tools = {}, provider = openai() } = options
return {
description,
tools,
provider,
run:
options.run ??
(async (state, context) => {
const mappedTools = tools
? Object.entries(tools).map(([name, tool]) =>
zodFunction({
name,
parameters: tool.parameters,
description: tool.description,
})
)
: []
const res = await provider.completions({
messages: [
{
role: 'system',
content: s`
${description}
Your job is to complete the assigned task:
- You can break down complex tasks into multiple steps if needed.
- You can use available tools if needed.
If tool requires arguments, get them from the input, or use other tools to get them.
Do not fabricate or assume information not present in the input.
Try to complete the task on your own.
`,
},
response('What have been done so far?'),
request(`Here is all the work done so far by other agents: ${JSON.stringify(context)}`),
response('What do you want me to do now?'),
...state.messages,
],
tools: mappedTools.length > 0 ? mappedTools : undefined,
response_format: zodResponseFormat(
z.object({
response: z.discriminatedUnion('kind', [
z.object({
kind: z.literal('step'),
name: z.string().describe('The name of the step'),
result: z.string().describe('The result of the step'),
reasoning: z.string().describe('The reasoning for this step'),
nextStep: z
.string()
.nullable()
.describe('The next step to complete the task, or null if task is complete'),
}),
z.object({
kind: z.literal('error'),
reasoning: z.string().describe('The reason why you cannot complete the task'),
}),
]),
}),
'task_result'
),
})
if (res.choices[0].message.tool_calls.length > 0) {
return {
...state,
status: 'paused',
messages: state.messages.concat(res.choices[0].message),
}
}
const message = res.choices[0].message.parsed
if (!message) {
throw new Error('No parsed response received')
}
if (message.response.kind === 'error') {
throw new Error(message.response.reasoning)
}
const agentResponse = response(message.response.result)
if (message.response.nextStep) {
return {
...state,
status: 'running',
messages: [...state.messages, agentResponse, request(message.response.nextStep)],
}
}
return finish(state, agentResponse)
}),
}
}