Skip to content

Commit 1c3d3d9

Browse files
committed
Finalize intelligent context management, improve formatting instructions
1 parent b52c301 commit 1c3d3d9

File tree

1 file changed

+50
-16
lines changed

1 file changed

+50
-16
lines changed

src/ai/chatbot.py

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Chatbot implementation using the new chat_completion function"""
22

3-
from typing import Dict, List, Any, Optional, Tuple
3+
from typing import Dict, List, Any, Optional
44
import json
55
from src.util.logging import Logger
66
from src.actions.registry import ActionRegistry
@@ -68,22 +68,22 @@ def _add_to_history(self, role: str, content: str) -> None:
6868
# Keep history within limits, but preserve system message
6969
if len(self.history) > self.max_history + 1: # +1 for system message
7070
# Remove oldest messages but keep system message
71-
self.history = [self.history[0]] + self.history[-(self.max_history):]
71+
self.history = [self.history[0]] + self.history[-(self.max_history) :]
7272

7373
# Check total token usage and trim if needed
7474
_, _, available = self.get_context_limits()
7575
current_tokens = sum(self.count_tokens(msg["content"]) for msg in self.history)
76-
76+
7777
if current_tokens > available:
7878
# Always keep system message and last 2 messages of conversation
7979
preserved = [self.history[0]] + self.history[-2:]
8080
older_messages = self.history[1:-2]
81-
81+
8282
# Remove older messages until we're under the limit
8383
while current_tokens > available and older_messages:
8484
removed = older_messages.pop()
8585
current_tokens -= self.count_tokens(removed["content"])
86-
86+
8787
# Reconstruct history with remaining messages
8888
self.history = preserved if not older_messages else [self.history[0]] + older_messages + self.history[-2:]
8989

@@ -108,12 +108,11 @@ def get_available_space(self) -> int:
108108
def _truncate_result(self, result: str) -> str:
109109
"""Dynamically truncate results based on available space"""
110110
available_space = self.get_available_space()
111-
111+
112112
# Use up to 25% of available space for results, but no more than 1/8 of total context
113113
max_tokens, _, _ = self.get_context_limits()
114114
max_result_tokens = min(
115-
available_space // 4, # 25% of available space
116-
max_tokens // 8 # Or 1/8 of total context, whichever is smaller
115+
available_space // 4, max_tokens // 8 # 25% of available space # Or 1/8 of total context, whichever is smaller
117116
)
118117

119118
result_tokens = self.count_tokens(result)
@@ -143,7 +142,7 @@ def _truncate_result(self, result: str) -> str:
143142
chars_to_keep = max_result_tokens * 4 # Convert tokens to chars
144143
first_part = int(chars_to_keep * 0.67)
145144
last_part = int(chars_to_keep * 0.33)
146-
145+
147146
return f"{result[:first_part]}\n...[truncated]...\n{result[-last_part:]}"
148147

149148
async def execute_command(self, command: str, args_str: str, update_callback=None) -> ActionResult:
@@ -169,13 +168,14 @@ async def execute_command(self, command: str, args_str: str, update_callback=Non
169168
job_manager = await JobManager.get_instance()
170169
job_result = await job_manager.wait_for_job_result(result.job_id)
171170
if job_result:
172-
return ActionResult.text(job_result.get_output())
171+
return ActionResult.text(self._truncate_result(job_result.get_output()))
173172
return ActionResult.text("No output available")
174173

175174
if not isinstance(result, ActionResult):
176175
raise ValueError(f"Action {command} returned {type(result)}, expected ActionResult")
177176

178-
return result
177+
# Truncate any result type after converting to string
178+
return ActionResult.text(self._truncate_result(str(result)))
179179

180180
except Exception as e:
181181
self.logger.error(f"Error executing command: {str(e)}")
@@ -207,12 +207,27 @@ async def _plan_next_step(self, current_state: Dict[str, Any]) -> Dict[str, Any]
207207
"is_final": boolean (true if this is your final response)
208208
}
209209
210+
FORMATTING RULES (STRICTLY ENFORCED):
211+
1. PLAIN TEXT ONLY - NO markdown, NO HTML, NO special formatting
212+
2. Use this format for lists:
213+
🔍 Found X items:
214+
215+
1. Name (Context) - Description
216+
→ address or link
217+
218+
2. Name (Context) - Description
219+
→ address or link
220+
221+
3. Use emojis for headers (🔍, ℹ️)
222+
4. Use → for links/addresses
223+
5. Use plain numbers and spaces for structure
224+
210225
CRITICAL INSTRUCTIONS:
211226
212227
1. NEVER truncate your output. Show ALL results completely.
213228
2. When formatting lists or results, include ALL items with their complete information.
214229
3. Try to be efficient - use as few database queries as possible.
215-
4. Do NOT use markdown or HTML tags in your responses.
230+
4. DO NOT use markdown or HTML tags in your responses.
216231
5. ALWAYS set is_final to true when you have all the information needed to answer the user's question.
217232
6. NEVER repeat the same command without a different purpose.
218233
7. If you have the information needed, format it and return it immediately with is_final=true.
@@ -223,16 +238,26 @@ async def _plan_next_step(self, current_state: Dict[str, Any]) -> Dict[str, Any]
223238
224239
RESULT HANDLING INSTRUCTIONS:
225240
226-
1. For commands that return raw data that the user explicitly requested, pass through the result directly:
241+
1. For commands that return raw data that the user explicitly requested, return the results directly:
227242
- /get_code: Return the code directly when user asks for code
228-
- /file_search: Return the matches directly when user searches for specific patterns
229-
- /semantic_search: Return the search results directly
230243
231244
2. For commands that return metadata or require interpretation, summarize the results:
232245
- Database queries that return project/asset information
233246
- Status updates
234247
- Job results that need explanation
235248
249+
3. For commands that return lists, tables or tree structures, format them concisely for the Telegram UI.
250+
251+
Example for list formatting:
252+
253+
🔍 Found 10 finalize functions:
254+
255+
1. finalizeBridgeERC721 (Optimism) - ERC721 token bridge finalization
256+
→ 0x3268ed09f76e619331528270b6267d4d2c5ab5c2
257+
258+
2. finalizeBridgeETH (Optimism) - ETH bridge transfers
259+
→ 0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d
260+
236261
Examples:
237262
238263
Good (direct code request):
@@ -259,6 +284,15 @@ async def _plan_next_step(self, current_state: Dict[str, Any]) -> Dict[str, Any]
259284
"output": "The latest asset is X from project Y, added on date Z",
260285
"is_final": true
261286
}
287+
288+
User: "Find me functions in bridge smart contracts that finalize transactions"
289+
// Execute the command first, then format the result for the user
290+
{
291+
"thought": "I'll format the list of finalize functions in a clear, plain text format with emojis for better readability",
292+
"command": "",
293+
"output": "🔍 Found 10 finalize functions:\n\n1. finalizeBridgeERC721 (Optimism) - ERC721 token bridge finalization\n → 0x3268ed09f76e619331528270b6267d4d2c5ab5c2\n\n2. finalizeBridgeETH (Optimism) - ETH bridge transfers\n → 0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d\n\n3. finalizeBridgeERC721 (Optimism) - ERC721 cross-bridge transfers\n → 0xc599fa757c2bcaa5ae3753ab129237f38c10da0b",
294+
"is_final": true
295+
}
262296
""",
263297
}
264298
)
@@ -381,7 +415,7 @@ async def process_message(self, message: str, update_callback=None, action_callb
381415
try:
382416
# Show command being executed if callback provided
383417
if update_callback:
384-
await update_callback(f"🛠️ Executing: /{plan['command']}")
418+
await update_callback(f"🛠️ Executing: {plan['command']}")
385419

386420
result = await self.execute_command(command, args_str)
387421
# Convert ActionResult to string when storing in state

0 commit comments

Comments
 (0)