litellm fixes - message hoarding and missing user message
frdel committed
Jul 4, 2025 at 16:58 UTC
73778cdd34d1315838e2f2d78e7676f7aa069985
8 files changed
+48
-28
models.py
+3
-1
@@ -278,12 +278,14 @@ class LiteLLMChatWrapper(SimpleChatModel):
278
self,
279
system_message="",
280
user_message="",
281
- messages: List[BaseMessage] = [],
281
+ messages: List[BaseMessage]|None = None,
282
response_callback: Callable[[str, str], Awaitable[None]] | None = None,
283
reasoning_callback: Callable[[str, str], Awaitable[None]] | None = None,
284
tokens_callback: Callable[[str, int], Awaitable[None]] | None = None,
285
**kwargs: Any,
286
) -> Tuple[str, str]:
287
+ if not messages:
288
+ messages = []
289
# construct messages
290
if system_message:
291
messages.insert(0, SystemMessage(content=system_message))
python/extensions/message_loop_prompts_after/_50_recall_memories.py
+1
-1
@@ -58,7 +58,7 @@ class RecallMemories(Extension):
58
query = await self.agent.call_utility_model(
59
system=system,
60
message=(
61
- loop_data.user_message.output_text() if loop_data.user_message else ""
61
+ loop_data.user_message.output_text() if loop_data.user_message else "None"
62
),
63
callback=log_callback,
64
)
python/extensions/message_loop_prompts_after/_51_recall_solutions.py
+1
-1
@@ -56,7 +56,7 @@ class RecallSolutions(Extension):
56
57
# call util llm to summarize conversation
58
query = await self.agent.call_utility_model(
59
- system=system, message=loop_data.user_message.output_text() if loop_data.user_message else "", callback=log_callback
59
+ system=system, message=loop_data.user_message.output_text() if loop_data.user_message else "None", callback=log_callback
60
)
61
62
# get solutions database
python/extensions/monologue_start/_60_rename_chat.py
+3
-1
@@ -13,7 +13,9 @@ class RenameChat(Extension):
13
try:
14
# prepare history
15
history_text = self.agent.history.output_text()
16
- ctx_length = int(self.agent.config.utility_model.ctx_length * 0.3)
16
+ ctx_length = min(
17
+ int(self.agent.config.utility_model.ctx_length * 0.7), 5000
18
+ )
19
history_text = tokens.trim_to_tokens(history_text, ctx_length, "start")
20
# prepare system and user prompt
21
system = self.agent.read_prompt("fw.rename_chat.sys.md")
python/helpers/shell_ssh.py
+1
-1
@@ -41,7 +41,7 @@ class SSHInteractiveSession:
41
allow_agent=False,
42
look_for_keys=False,
43
)
44
- self.shell = self.client.invoke_shell(width=160, height=48)
44
+ self.shell = self.client.invoke_shell(width=80, height=40)
45
# self.shell.send(f'PS1="{SSHInteractiveSession.ps1_label}"'.encode())
46
# return
47
while True: # wait for end of initial output
python/tools/code_execution_tool.py
+32
-11
@@ -135,19 +135,22 @@ class CodeExecution(Tool):
135
async def execute_python_code(self, session: int, code: str, reset: bool = False):
136
escaped_code = shlex.quote(code)
137
command = f"ipython -c {escaped_code}"
138
- return await self.terminal_session(session, command, reset)
138
+ prefix = "python> "+self.format_command_for_output(code)+"\n\n"
139
+ return await self.terminal_session(session, command, reset, prefix)
140
141
async def execute_nodejs_code(self, session: int, code: str, reset: bool = False):
142
escaped_code = shlex.quote(code)
142
- command = f"node /exe/node_eval.js {escaped_code}"
143
- return await self.terminal_session(session, command, reset)
143
+ command = f"node /exe/node_eval.js {escaped_code}"
144
+ prefix = "node> "+self.format_command_for_output(code)+"\n\n"
145
+ return await self.terminal_session(session, command, reset, prefix)
146
147
async def execute_terminal_command(
148
self, session: int, command: str, reset: bool = False
149
):
148
- return await self.terminal_session(session, command, reset)
150
+ prefix = "bash> "+self.format_command_for_output(command)+"\n\n"
151
+ return await self.terminal_session(session, command, reset, prefix)
152
150
- async def terminal_session(self, session: int, command: str, reset: bool = False):
153
+ async def terminal_session(self, session: int, command: str, reset: bool = False, prefix: str = ""):
154
155
await self.agent.handle_intervention() # wait for intervention and handle it, if paused
156
# try again on lost connection
@@ -181,7 +184,7 @@ class CodeExecution(Tool):
184
PrintStyle(
185
background_color="white", font_color="#1B4F72", bold=True
186
).print(f"{self.agent.agent_name} code execution output")
184
- return await self.get_terminal_output(session)
187
+ return await self.get_terminal_output(session=session, prefix=prefix)
188
189
except Exception as e:
190
if i == 1:
@@ -192,6 +195,19 @@ class CodeExecution(Tool):
195
else:
196
raise e
197
198
+ def format_command_for_output(self, command: str):
199
+ # truncate long commands
200
+ short_cmd = command[:200]
201
+ # normalize whitespace for cleaner output
202
+ short_cmd = " ".join(short_cmd.split())
203
+ # replace any sequence of ', ", or ` with a single '
204
+ # short_cmd = re.sub(r"['\"`]+", "'", short_cmd) # no need anymore
205
+ # final length
206
+ short_cmd = truncate_text_string(short_cmd, 100)
207
+ return f"{short_cmd}"
208
+
209
+
210
+
211
async def get_terminal_output(
212
self,
213
session=0,
@@ -201,6 +217,7 @@ class CodeExecution(Tool):
217
dialog_timeout=5, # potential dialog detection timeout
218
max_exec_timeout=180, # hard cap on total runtime
219
sleep_time=0.1,
220
+ prefix=""
221
):
222
# Common shell prompt regex patterns (add more as needed)
223
prompt_patterns = [
@@ -223,6 +240,10 @@ class CodeExecution(Tool):
240
truncated_output = ""
241
got_output = False
242
243
+ # if prefix, log right away
244
+ if prefix:
245
+ self.log.update(content=prefix)
246
+
247
while True:
248
await asyncio.sleep(sleep_time)
249
full_output, partial_output = await self.state.shells[session].read_output(
@@ -238,7 +259,7 @@ class CodeExecution(Tool):
259
# full_output += partial_output # Append new output
260
truncated_output = self.fix_full_output(full_output)
261
heading = self.get_heading_from_output(truncated_output, 0)
241
- self.log.update(content=truncated_output, heading=heading)
262
+ self.log.update(content=prefix + truncated_output, heading=heading)
263
last_output_time = now
264
got_output = True
265
@@ -270,7 +291,7 @@ class CodeExecution(Tool):
291
response = truncated_output + "\n\n" + response
292
PrintStyle.warning(sysinfo)
293
heading = self.get_heading_from_output(truncated_output, 0)
273
- self.log.update(content=response, heading=heading)
294
+ self.log.update(content=prefix + response, heading=heading)
295
return response
296
297
# Waiting for first output
@@ -281,7 +302,7 @@ class CodeExecution(Tool):
302
)
303
response = self.agent.read_prompt("fw.code.info.md", info=sysinfo)
304
PrintStyle.warning(sysinfo)
284
- self.log.update(content=response)
305
+ self.log.update(content=prefix + response)
306
return response
307
else:
308
# Waiting for more output after first output
@@ -294,7 +315,7 @@ class CodeExecution(Tool):
315
response = truncated_output + "\n\n" + response
316
PrintStyle.warning(sysinfo)
317
heading = self.get_heading_from_output(truncated_output, 0)
297
- self.log.update(content=response, heading=heading)
318
+ self.log.update(content=prefix + response, heading=heading)
319
return response
320
321
# potential dialog detection
@@ -322,7 +343,7 @@ class CodeExecution(Tool):
343
heading = self.get_heading_from_output(
344
truncated_output, 0
345
)
325
- self.log.update(content=response, heading=heading)
346
+ self.log.update(content=prefix + response, heading=heading)
347
return response
348
349
async def reset_terminal(self, session=0, reason: str | None = None):
webui/css/messages.css
+1
-1
@@ -162,7 +162,7 @@
162
display: block;
163
width: 100%;
164
overflow-x: auto;
165
- margin-bottom: 1em;
165
+ padding-bottom: 1em;
166
}
167
168
.message-body .message-markdown-table-wrap table {
webui/js/messages.js
+6
-11
@@ -826,20 +826,15 @@ function convertPathsToLinks(str) {
826
}
827
828
function adjustMarkdownRender(element) {
829
- // find all tables in the element
830
- const tables = element.querySelectorAll("table");
829
+ // find all tables and code blocks in the element
830
+ const elements = element.querySelectorAll("table, code");
831
832
- // wrap each table with a div with class message-markdown-table-wrap
833
- tables.forEach((table) => {
834
- // create wrapper div
832
+ // wrap each with a div with class message-markdown-table-wrap
833
+ elements.forEach((el) => {
834
const wrapper = document.createElement("div");
835
wrapper.className = "message-markdown-table-wrap";
837
-
838
- // insert wrapper before table in the DOM
839
- table.parentNode.insertBefore(wrapper, table);
840
-
841
- // move table into wrapper
842
- wrapper.appendChild(table);
836
+ el.parentNode.insertBefore(wrapper, el);
837
+ wrapper.appendChild(el);
838
});
839
}
840