1
1
from __future__ import annotations
2
2
3
+ import re
3
4
from abc import ABCMeta , abstractmethod
4
- from typing import TYPE_CHECKING , Any , Optional
5
+ from typing import TYPE_CHECKING , Any , Literal , Optional
5
6
6
7
if TYPE_CHECKING :
7
8
from autogpt .config import AIConfig , Config
23
24
class BaseAgent (metaclass = ABCMeta ):
24
25
"""Base class for all Auto-GPT agents."""
25
26
27
+ ThoughtProcessID = Literal ["one-shot" ]
28
+
26
29
def __init__ (
27
30
self ,
28
31
ai_config : AIConfig ,
@@ -91,6 +94,7 @@ def __init__(
91
94
def think (
92
95
self ,
93
96
instruction : Optional [str ] = None ,
97
+ thought_process_id : ThoughtProcessID = "one-shot" ,
94
98
) -> tuple [CommandName | None , CommandArgs | None , AgentThoughts ]:
95
99
"""Runs the agent for one cycle.
96
100
@@ -103,9 +107,8 @@ def think(
103
107
104
108
instruction = instruction or self .default_cycle_instruction
105
109
106
- prompt : ChatSequence = self .construct_prompt (instruction )
107
- prompt = self .on_before_think (prompt , instruction )
108
-
110
+ prompt : ChatSequence = self .construct_prompt (instruction , thought_process_id )
111
+ prompt = self .on_before_think (prompt , thought_process_id , instruction )
109
112
raw_response = create_chat_completion (
110
113
prompt ,
111
114
self .config ,
@@ -115,7 +118,7 @@ def think(
115
118
)
116
119
self .cycle_count += 1
117
120
118
- return self .on_response (raw_response , prompt , instruction )
121
+ return self .on_response (raw_response , thought_process_id , prompt , instruction )
119
122
120
123
@abstractmethod
121
124
def execute (
@@ -138,6 +141,7 @@ def execute(
138
141
139
142
def construct_base_prompt (
140
143
self ,
144
+ thought_process_id : ThoughtProcessID ,
141
145
prepend_messages : list [Message ] = [],
142
146
append_messages : list [Message ] = [],
143
147
reserve_tokens : int = 0 ,
@@ -179,7 +183,11 @@ def construct_base_prompt(
179
183
180
184
return prompt
181
185
182
- def construct_prompt (self , cycle_instruction : str ) -> ChatSequence :
186
+ def construct_prompt (
187
+ self ,
188
+ cycle_instruction : str ,
189
+ thought_process_id : ThoughtProcessID ,
190
+ ) -> ChatSequence :
183
191
"""Constructs and returns a prompt with the following structure:
184
192
1. System prompt
185
193
2. Message history of the agent, truncated & prepended with running summary as needed
@@ -196,14 +204,86 @@ def construct_prompt(self, cycle_instruction: str) -> ChatSequence:
196
204
cycle_instruction_tlength = count_message_tokens (
197
205
cycle_instruction_msg , self .llm .name
198
206
)
199
- prompt = self .construct_base_prompt (reserve_tokens = cycle_instruction_tlength )
207
+
208
+ append_messages : list [Message ] = []
209
+
210
+ response_format_instr = self .response_format_instruction (thought_process_id )
211
+ if response_format_instr :
212
+ append_messages .append (Message ("system" , response_format_instr ))
213
+
214
+ prompt = self .construct_base_prompt (
215
+ thought_process_id ,
216
+ append_messages = append_messages ,
217
+ reserve_tokens = cycle_instruction_tlength ,
218
+ )
200
219
201
220
# ADD user input message ("triggering prompt")
202
221
prompt .append (cycle_instruction_msg )
203
222
204
223
return prompt
205
224
206
- def on_before_think (self , prompt : ChatSequence , instruction : str ) -> ChatSequence :
225
+ # This can be expanded to support multiple types of (inter)actions within an agent
226
+ def response_format_instruction (self , thought_process_id : ThoughtProcessID ) -> str :
227
+ if thought_process_id != "one-shot" :
228
+ raise NotImplementedError (f"Unknown thought process '{ thought_process_id } '" )
229
+
230
+ RESPONSE_FORMAT_WITH_COMMAND = """```ts
231
+ interface Response {
232
+ thoughts: {
233
+ // Thoughts
234
+ text: string;
235
+ reasoning: string;
236
+ // Short markdown-style bullet list that conveys the long-term plan
237
+ plan: string;
238
+ // Constructive self-criticism
239
+ criticism: string;
240
+ // Summary of thoughts to say to the user
241
+ speak: string;
242
+ };
243
+ command: {
244
+ name: string;
245
+ args: Record<string, any>;
246
+ };
247
+ }
248
+ ```"""
249
+
250
+ RESPONSE_FORMAT_WITHOUT_COMMAND = """```ts
251
+ interface Response {
252
+ thoughts: {
253
+ // Thoughts
254
+ text: string;
255
+ reasoning: string;
256
+ // Short markdown-style bullet list that conveys the long-term plan
257
+ plan: string;
258
+ // Constructive self-criticism
259
+ criticism: string;
260
+ // Summary of thoughts to say to the user
261
+ speak: string;
262
+ };
263
+ }
264
+ ```"""
265
+
266
+ response_format = re .sub (
267
+ r"\n\s+" ,
268
+ "\n " ,
269
+ RESPONSE_FORMAT_WITHOUT_COMMAND
270
+ if self .config .openai_functions
271
+ else RESPONSE_FORMAT_WITH_COMMAND ,
272
+ )
273
+
274
+ use_functions = self .config .openai_functions and self .command_registry .commands
275
+ return (
276
+ f"Respond strictly with JSON{ ', and also specify a command to use through a function_call' if use_functions else '' } . "
277
+ "The JSON should be compatible with the TypeScript type `Response` from the following:\n "
278
+ f"{ response_format } \n "
279
+ )
280
+
281
+ def on_before_think (
282
+ self ,
283
+ prompt : ChatSequence ,
284
+ thought_process_id : ThoughtProcessID ,
285
+ instruction : str ,
286
+ ) -> ChatSequence :
207
287
"""Called after constructing the prompt but before executing it.
208
288
209
289
Calls the `on_planning` hook of any enabled and capable plugins, adding their
@@ -238,7 +318,11 @@ def on_before_think(self, prompt: ChatSequence, instruction: str) -> ChatSequenc
238
318
return prompt
239
319
240
320
def on_response (
241
- self , llm_response : ChatModelResponse , prompt : ChatSequence , instruction : str
321
+ self ,
322
+ llm_response : ChatModelResponse ,
323
+ thought_process_id : ThoughtProcessID ,
324
+ prompt : ChatSequence ,
325
+ instruction : str ,
242
326
) -> tuple [CommandName | None , CommandArgs | None , AgentThoughts ]:
243
327
"""Called upon receiving a response from the chat model.
244
328
@@ -261,7 +345,9 @@ def on_response(
261
345
) # FIXME: support function calls
262
346
263
347
try :
264
- return self .parse_and_process_response (llm_response , prompt , instruction )
348
+ return self .parse_and_process_response (
349
+ llm_response , thought_process_id , prompt , instruction
350
+ )
265
351
except SyntaxError as e :
266
352
logger .error (f"Response could not be parsed: { e } " )
267
353
# TODO: tune this message
@@ -276,7 +362,11 @@ def on_response(
276
362
277
363
@abstractmethod
278
364
def parse_and_process_response (
279
- self , llm_response : ChatModelResponse , prompt : ChatSequence , instruction : str
365
+ self ,
366
+ llm_response : ChatModelResponse ,
367
+ thought_process_id : ThoughtProcessID ,
368
+ prompt : ChatSequence ,
369
+ instruction : str ,
280
370
) -> tuple [CommandName | None , CommandArgs | None , AgentThoughts ]:
281
371
"""Validate, parse & process the LLM's response.
282
372
0 commit comments