Manoj Thapa commited on
Commit
5b1b7c1
·
1 Parent(s): e59469c

Fix: Improve language context switching by appending explicit instruction to user prompt

Browse files
backend/agent/graph.py CHANGED
@@ -201,6 +201,14 @@ class AgentRunner:
201
  # Append Language Instruction
202
  if language and language != "English":
203
  formatted_system_prompt += f"\n\nIMPORTANT: You must respond in {language}. Translate your internal reasoning if necessary, but the final output must be in {language}."
 
 
 
 
 
 
 
 
204
  messages = [{"role": "system", "content": formatted_system_prompt}]
205
 
206
  # Smart Context Management
@@ -208,49 +216,36 @@ class AgentRunner:
208
  # Github Models Free Tier has a strict 8k token limit for ALL models
209
  # 8k tokens ~= 32k chars. We use 30k to be safe.
210
  MAX_HISTORY_CHARS = 30000
211
-
212
- current_chars = 0
213
- selected_history = []
214
 
215
  if conversation_history:
216
- # Iterate backwards to keep most recent first
217
- for msg in reversed(conversation_history):
218
- content = msg.get("content") or ""
219
-
220
- # Truncate extremely long individual text messages
221
- if content and len(content) > 2000:
222
- content = content[:2000] + "... [truncated]"
223
-
224
- # Estimate size (including tool call overhead)
225
- msg_len = len(content) + 200 # Buffer for metadata
226
-
227
- if current_chars + msg_len > MAX_HISTORY_CHARS:
228
- # Soft limit hit - stop adding history
229
  break
 
 
 
 
 
 
 
230
 
231
- # Reconstruct message preserving CRITICAL fields for API validity
232
- clean_msg = {
233
- "role": msg["role"],
 
 
 
234
  "content": content
235
- }
236
- if "tool_calls" in msg:
237
- clean_msg["tool_calls"] = msg["tool_calls"]
238
- if "tool_call_id" in msg:
239
- clean_msg["tool_call_id"] = msg["tool_call_id"]
240
- if "name" in msg:
241
- clean_msg["name"] = msg["name"]
242
-
243
- selected_history.insert(0, clean_msg)
244
- current_chars += msg_len
245
-
246
- # SAFETY: Ensure history doesn't start with a 'tool' result (orphan)
247
- # API requires: User/System -> Assistant -> Tool -> Assistant ...
248
- # If we cut in the middle, we might start with 'tool'.
249
- while selected_history and selected_history[0].get("role") == "tool":
250
- selected_history.pop(0)
251
-
252
- # Add trimmed history to messages
253
- messages.extend(selected_history)
254
 
255
  # Add file context if any
256
  file_context = ""
 
201
  # Append Language Instruction
202
  if language and language != "English":
203
  formatted_system_prompt += f"\n\nIMPORTANT: You must respond in {language}. Translate your internal reasoning if necessary, but the final output must be in {language}."
204
+
205
+ # Override History Bias:
206
+ # If the conversation history has a different language, the model might get confused.
207
+ # We explicitly append the instruction to the *current* user message to force the switch.
208
+ current_user_content = user_message
209
+ if language and language != "English":
210
+ current_user_content += f"\n\n(Please answer in {language})"
211
+
212
  messages = [{"role": "system", "content": formatted_system_prompt}]
213
 
214
  # Smart Context Management
 
216
  # Github Models Free Tier has a strict 8k token limit for ALL models
217
  # 8k tokens ~= 32k chars. We use 30k to be safe.
218
  MAX_HISTORY_CHARS = 30000
 
 
 
219
 
220
  if conversation_history:
221
+ # Sort by timestamp just in case
222
+ sorted_history = sorted(conversation_history, key=lambda x: x.get("timestamp", ""))
223
+
224
+ # Simple truncation strategy
225
+ truncated_history = []
226
+ current_chars = 0
227
+
228
+ # Add history from newest to oldest until limit
229
+ for msg in reversed(sorted_history):
230
+ content_len = len(msg.get("documents", [""])[0] if isinstance(msg.get("documents"), list) else str(msg.get("content", "")))
231
+ if current_chars + content_len > MAX_HISTORY_CHARS:
 
 
232
  break
233
+ truncated_history.insert(0, msg)
234
+ current_chars += content_len
235
+
236
+ # Convert to OpenAI format
237
+ for msg in truncated_history:
238
+ # Handle stored document format vs raw content
239
+ content = msg.get("documents", [""])[0] if isinstance(msg.get("documents"), list) else msg.get("content", "")
240
 
241
+ # Skip system messages in history if any
242
+ if msg.get("role") == "system":
243
+ continue
244
+
245
+ messages.append({
246
+ "role": msg.get("role"),
247
  "content": content
248
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
  # Add file context if any
251
  file_context = ""
backend/tools/code_executor.py CHANGED
@@ -66,7 +66,7 @@ def code_executor_tool(code: str, language: str = "python") -> str:
66
 
67
 
68
  def _execute_python(code: str, result: Dict[str, Any]) -> str:
69
- """Executes Python code in-process using exec()."""
70
  stdout_capture = io.StringIO()
71
  stderr_capture = io.StringIO()
72
 
@@ -83,14 +83,40 @@ def _execute_python(code: str, result: Dict[str, Any]) -> str:
83
  exec_globals = {'__builtins__': safe_builtins}
84
  exec_locals = {}
85
 
 
 
86
  try:
87
  with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
 
88
  try:
89
- exec_result = eval(code, exec_globals, exec_locals)
90
- result["result"] = str(exec_result) if exec_result is not None else None
91
  except SyntaxError:
 
92
  exec(code, exec_globals, exec_locals)
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  result["stdout"] = stdout_capture.getvalue()
95
  result["stderr"] = stderr_capture.getvalue()
96
  result["success"] = True
 
66
 
67
 
68
  def _execute_python(code: str, result: Dict[str, Any]) -> str:
69
+ """Executes Python code in-process using AST to capture last expression."""
70
  stdout_capture = io.StringIO()
71
  stderr_capture = io.StringIO()
72
 
 
83
  exec_globals = {'__builtins__': safe_builtins}
84
  exec_locals = {}
85
 
86
+ import ast
87
+
88
  try:
89
  with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
90
+ # Parse the code into an AST
91
  try:
92
+ tree = ast.parse(code)
 
93
  except SyntaxError:
94
+ # If parsing fails, fall back to simple exec to let it raise the error naturally
95
  exec(code, exec_globals, exec_locals)
96
 
97
+ # Check if likely an expression at the end
98
+ last_node = None
99
+ if tree.body and isinstance(tree.body[-1], ast.Expr):
100
+ last_node = tree.body.pop()
101
+
102
+ # Execute the main block (all statements except the last expression)
103
+ if tree.body:
104
+ # Compile as a module
105
+ module = ast.Module(body=tree.body, type_ignores=[])
106
+ # We must fix locations for the new AST to be compilable
107
+ ast.fix_missing_locations(module)
108
+ compiled_module = compile(module, filename="<string>", mode="exec")
109
+ exec(compiled_module, exec_globals, exec_locals)
110
+
111
+ # Evaluate the last expression (if any)
112
+ if last_node:
113
+ expr = ast.Expression(body=last_node.value)
114
+ ast.fix_missing_locations(expr)
115
+ compiled_expr = compile(expr, filename="<string>", mode="eval")
116
+ exec_result = eval(compiled_expr, exec_globals, exec_locals)
117
+ # Store the result
118
+ result["result"] = str(exec_result) if exec_result is not None else None
119
+
120
  result["stdout"] = stdout_capture.getvalue()
121
  result["stderr"] = stderr_capture.getvalue()
122
  result["success"] = True