1
1
"""Chatbot implementation using the new chat_completion function"""
2
2
3
- from typing import Dict , List , Any , Optional , Tuple
3
+ from typing import Dict , List , Any , Optional
4
4
import json
5
5
from src .util .logging import Logger
6
6
from src .actions .registry import ActionRegistry
@@ -68,22 +68,22 @@ def _add_to_history(self, role: str, content: str) -> None:
68
68
# Keep history within limits, but preserve system message
69
69
if len (self .history ) > self .max_history + 1 : # +1 for system message
70
70
# 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 ) :]
72
72
73
73
# Check total token usage and trim if needed
74
74
_ , _ , available = self .get_context_limits ()
75
75
current_tokens = sum (self .count_tokens (msg ["content" ]) for msg in self .history )
76
-
76
+
77
77
if current_tokens > available :
78
78
# Always keep system message and last 2 messages of conversation
79
79
preserved = [self .history [0 ]] + self .history [- 2 :]
80
80
older_messages = self .history [1 :- 2 ]
81
-
81
+
82
82
# Remove older messages until we're under the limit
83
83
while current_tokens > available and older_messages :
84
84
removed = older_messages .pop ()
85
85
current_tokens -= self .count_tokens (removed ["content" ])
86
-
86
+
87
87
# Reconstruct history with remaining messages
88
88
self .history = preserved if not older_messages else [self .history [0 ]] + older_messages + self .history [- 2 :]
89
89
@@ -108,12 +108,11 @@ def get_available_space(self) -> int:
108
108
def _truncate_result (self , result : str ) -> str :
109
109
"""Dynamically truncate results based on available space"""
110
110
available_space = self .get_available_space ()
111
-
111
+
112
112
# Use up to 25% of available space for results, but no more than 1/8 of total context
113
113
max_tokens , _ , _ = self .get_context_limits ()
114
114
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
117
116
)
118
117
119
118
result_tokens = self .count_tokens (result )
@@ -143,7 +142,7 @@ def _truncate_result(self, result: str) -> str:
143
142
chars_to_keep = max_result_tokens * 4 # Convert tokens to chars
144
143
first_part = int (chars_to_keep * 0.67 )
145
144
last_part = int (chars_to_keep * 0.33 )
146
-
145
+
147
146
return f"{ result [:first_part ]} \n ...[truncated]...\n { result [- last_part :]} "
148
147
149
148
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
169
168
job_manager = await JobManager .get_instance ()
170
169
job_result = await job_manager .wait_for_job_result (result .job_id )
171
170
if job_result :
172
- return ActionResult .text (job_result .get_output ())
171
+ return ActionResult .text (self . _truncate_result ( job_result .get_output () ))
173
172
return ActionResult .text ("No output available" )
174
173
175
174
if not isinstance (result , ActionResult ):
176
175
raise ValueError (f"Action { command } returned { type (result )} , expected ActionResult" )
177
176
178
- return result
177
+ # Truncate any result type after converting to string
178
+ return ActionResult .text (self ._truncate_result (str (result )))
179
179
180
180
except Exception as e :
181
181
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]
207
207
"is_final": boolean (true if this is your final response)
208
208
}
209
209
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
+
210
225
CRITICAL INSTRUCTIONS:
211
226
212
227
1. NEVER truncate your output. Show ALL results completely.
213
228
2. When formatting lists or results, include ALL items with their complete information.
214
229
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.
216
231
5. ALWAYS set is_final to true when you have all the information needed to answer the user's question.
217
232
6. NEVER repeat the same command without a different purpose.
218
233
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]
223
238
224
239
RESULT HANDLING INSTRUCTIONS:
225
240
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:
227
242
- /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
230
243
231
244
2. For commands that return metadata or require interpretation, summarize the results:
232
245
- Database queries that return project/asset information
233
246
- Status updates
234
247
- Job results that need explanation
235
248
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
+
236
261
Examples:
237
262
238
263
Good (direct code request):
@@ -259,6 +284,15 @@ async def _plan_next_step(self, current_state: Dict[str, Any]) -> Dict[str, Any]
259
284
"output": "The latest asset is X from project Y, added on date Z",
260
285
"is_final": true
261
286
}
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 \n 1. finalizeBridgeERC721 (Optimism) - ERC721 token bridge finalization\n → 0x3268ed09f76e619331528270b6267d4d2c5ab5c2\n \n 2. finalizeBridgeETH (Optimism) - ETH bridge transfers\n → 0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d\n \n 3. finalizeBridgeERC721 (Optimism) - ERC721 cross-bridge transfers\n → 0xc599fa757c2bcaa5ae3753ab129237f38c10da0b",
294
+ "is_final": true
295
+ }
262
296
""" ,
263
297
}
264
298
)
@@ -381,7 +415,7 @@ async def process_message(self, message: str, update_callback=None, action_callb
381
415
try :
382
416
# Show command being executed if callback provided
383
417
if update_callback :
384
- await update_callback (f"🛠️ Executing: / { plan ['command' ]} " )
418
+ await update_callback (f"🛠️ Executing: { plan ['command' ]} " )
385
419
386
420
result = await self .execute_command (command , args_str )
387
421
# Convert ActionResult to string when storing in state
0 commit comments