1+ import json
2+ from operator import attrgetter
13from typing import List
24
3- from steamship import Block
5+ from steamship import Block , MimeTypes , Tag
46from steamship .agents .functional .output_parser import FunctionsBasedOutputParser
5- from steamship .agents .schema import Action , AgentContext , ChatAgent , ChatLLM , Tool
6- from steamship .data .tags .tag_constants import RoleTag
7+ from steamship .agents .schema import Action , AgentContext , ChatAgent , ChatLLM , FinishAction , Tool
8+ from steamship .data .tags .tag_constants import ChatTag , RoleTag , TagKind , TagValueKey
9+ from steamship .data .tags .tag_utils import get_tag
710
811
912class FunctionsBasedAgent (ChatAgent ):
@@ -54,6 +57,8 @@ def build_chat_history_for_tool(self, context: AgentContext) -> List[Block]:
5457 # get most recent context
5558 messages_from_memory .extend (context .chat_history .select_messages (self .message_selector ))
5659
60+ messages_from_memory .sort (key = attrgetter ("index_in_file" ))
61+
5762 # de-dupe the messages from memory
5863 ids = [context .chat_history .last_user_message .id ]
5964 for msg in messages_from_memory :
@@ -67,10 +72,8 @@ def build_chat_history_for_tool(self, context: AgentContext) -> List[Block]:
6772 # this should happen BEFORE any agent/assistant messages related to tool selection
6873 messages .append (context .chat_history .last_user_message )
6974
70- # get completed steps
71- actions = context .completed_steps
72- for action in actions :
73- messages .extend (action .to_chat_messages ())
75+ # get working history (completed actions)
76+ messages .extend (self ._function_calls_since_last_user_message (context ))
7477
7578 return messages
7679
@@ -81,4 +84,71 @@ def next_action(self, context: AgentContext) -> Action:
8184 # Run the default LLM on those messages
8285 output_blocks = self .llm .chat (messages = messages , tools = self .tools )
8386
84- return self .output_parser .parse (output_blocks [0 ].text , context )
87+ future_action = self .output_parser .parse (output_blocks [0 ].text , context )
88+ if not isinstance (future_action , FinishAction ):
89+ # record the LLM's function response in history
90+ self ._record_action_selection (future_action , context )
91+ return future_action
92+
93+ def _function_calls_since_last_user_message (self , context : AgentContext ) -> List [Block ]:
94+ function_calls = []
95+ for block in context .chat_history .messages [::- 1 ]: # is this too inefficient at scale?
96+ if block .chat_role == RoleTag .USER :
97+ return reversed (function_calls )
98+ if get_tag (block .tags , kind = TagKind .ROLE , name = RoleTag .FUNCTION ):
99+ function_calls .append (block )
100+ elif get_tag (block .tags , kind = TagKind .FUNCTION_SELECTION ):
101+ function_calls .append (block )
102+ return reversed (function_calls )
103+
104+ def _to_openai_function_selection (self , action : Action ) -> str :
105+ """NOTE: Temporary placeholder. Should be refactored"""
106+ fc = {"name" : action .tool }
107+ args = {}
108+ for block in action .input :
109+ for t in block .tags :
110+ if t .kind == TagKind .FUNCTION_ARG :
111+ args [t .name ] = block .as_llm_input (exclude_block_wrapper = True )
112+
113+ fc ["arguments" ] = json .dumps (args ) # the arguments must be a string value NOT a dict
114+ return json .dumps (fc )
115+
116+ def _record_action_selection (self , action : Action , context : AgentContext ):
117+ tags = [
118+ Tag (
119+ kind = TagKind .CHAT ,
120+ name = ChatTag .ROLE ,
121+ value = {TagValueKey .STRING_VALUE : RoleTag .ASSISTANT },
122+ ),
123+ Tag (kind = TagKind .FUNCTION_SELECTION , name = action .tool ),
124+ ]
125+ context .chat_history .file .append_block (
126+ text = self ._to_openai_function_selection (action ), tags = tags , mime_type = MimeTypes .TXT
127+ )
128+
129+ def record_action_run (self , action : Action , context : AgentContext ):
130+ super ().record_action_run (action , context )
131+
132+ if isinstance (action , FinishAction ):
133+ return
134+
135+ tags = [
136+ Tag (
137+ kind = TagKind .ROLE ,
138+ name = RoleTag .FUNCTION ,
139+ value = {TagValueKey .STRING_VALUE : action .tool },
140+ ),
141+ # need the following tag for backwards compatibility with older gpt-4 plugin
142+ Tag (
143+ kind = "name" ,
144+ name = action .tool ,
145+ ),
146+ ]
147+ # TODO(dougreid): I'm not convinced this is correct for tools that return multiple values.
148+ # It _feels_ like these should be named and inlined as a single message in history, etc.
149+ for block in action .output :
150+ context .chat_history .file .append_block (
151+ text = block .as_llm_input (exclude_block_wrapper = True ),
152+ tags = tags ,
153+ mime_type = block .mime_type ,
154+ )
0 commit comments