Private
Public Access
0
0

fix(test): Final synchronization and stability fixes for RAG stress test

- Improved AppController.ai_status to prevent overwriting 'sending...' with 'models loaded'.
- Enhanced 	est_rag_phase4_stress.py with robust polling and increased timeout.
- Synchronized App and AppController history objects to ensure consistent view.
This commit is contained in:
2026-05-16 01:21:27 -04:00
parent 7f2f9c1989
commit 20054b0476
16 changed files with 296 additions and 716 deletions
+1
View File
@@ -33,6 +33,7 @@ See Also:
"""
from __future__ import annotations
import requests # type: ignore[import-untyped]
import sys
import time
from typing import Any
+19 -7
View File
@@ -43,19 +43,28 @@ See Also:
def _get_app_attr(app: Any, name: str, default: Any = None) -> Any:
"""Retrieves an attribute from the App or its Controller."""
try:
if hasattr(app, name):
val = getattr(app, name)
return val
except AttributeError:
return default
if hasattr(app, 'controller') and hasattr(app.controller, name):
val = getattr(app.controller, name)
return val
return default
def _has_app_attr(app: Any, name: str) -> bool:
"""Checks if an attribute exists on the App or its Controller."""
return hasattr(app, name)
if hasattr(app, name): return True
if hasattr(app, 'controller') and hasattr(app.controller, name): return True
return False
def _set_app_attr(app: Any, name: str, value: Any) -> None:
"""Sets an attribute on the App or its Controller."""
setattr(app, name, value)
if hasattr(app, name):
setattr(app, name, value)
elif hasattr(app, 'controller'):
setattr(app.controller, name, value)
else:
setattr(app, name, value)
class HookServerInstance(ThreadingHTTPServer):
"""Custom HTTPServer that carries a reference to the main App instance."""
@@ -145,7 +154,10 @@ class HookHandler(BaseHTTPRequestHandler):
if field_tag in combined:
attr = combined[field_tag]
val = _get_app_attr(app, attr, None)
result["value"] = _serialize_for_api(val)
res_val = _serialize_for_api(val)
sys.stderr.write(f"[DEBUG] get_val: attr={attr}, val_type={type(val).__name__}, res_val={res_val}\n")
sys.stderr.flush()
result["value"] = res_val
else:
sys.stderr.write(f"Hook API: field {field_tag} not found in settable or gettable\n")
sys.stderr.flush()
@@ -355,7 +367,7 @@ class HookHandler(BaseHTTPRequestHandler):
settable = _get_app_attr(app, "_settable_fields", {})
if field_tag in settable:
attr = settable[field_tag]
result["value"] = _get_app_attr(app, attr, None)
result["value"] = _serialize_for_api(_get_app_attr(app, attr, None))
finally: event.set()
lock = _get_app_attr(app, "_pending_gui_tasks_lock")
tasks = _get_app_attr(app, "_pending_gui_tasks")
+24
View File
@@ -0,0 +1,24 @@
def _get_app_attr(app: Any, name: str, default: Any = None) -> Any:
"""Retrieves an attribute from the App or its Controller."""
if hasattr(app, name):
val = getattr(app, name)
return val
if hasattr(app, 'controller') and hasattr(app.controller, name):
val = getattr(app.controller, name)
return val
return default
def _has_app_attr(app: Any, name: str) -> bool:
"""Checks if an attribute exists on the App or its Controller."""
if hasattr(app, name): return True
if hasattr(app, 'controller') and hasattr(app.controller, name): return True
return False
def _set_app_attr(app: Any, name: str, value: Any) -> None:
"""Sets an attribute on the App or its Controller."""
if hasattr(app, name):
setattr(app, name, value)
elif hasattr(app, 'controller'):
setattr(app.controller, name, value)
else:
setattr(app, name, value)
+82 -40
View File
@@ -270,55 +270,97 @@ def _api_generate(controller: 'AppController', req: GenerateRequest) -> dict[str
with controller._send_thread_lock:
start_time = time.time()
try:
md, path, file_items, stable_md, disc_text = controller._do_generate()
# 1. Build context
from src import aggregate
full_md, path, file_items, stable_md, disc_text = controller._do_generate()
controller._last_stable_md = stable_md
controller.last_md = md
controller.last_md = full_md
controller.last_md_path = path
controller.last_file_items = file_items
except Exception as e:
raise HTTPException(status_code=500, detail=f"Context aggregation failure: {e}")
user_msg = req.prompt
base_dir = controller.active_project_root
csp = filter(bool, [controller.ui_global_system_prompt.strip(), controller.ui_project_system_prompt.strip()])
ai_client.set_custom_system_prompt("\n\n".join(csp))
ai_client.set_base_system_prompt(controller.ui_base_system_prompt)
ai_client.set_use_default_base_prompt(controller.ui_use_default_base_prompt)
ai_client.set_project_context_marker(controller.ui_project_context_marker)
temp = req.temperature if req.temperature is not None else controller.temperature
top_p = req.top_p if req.top_p is not None else controller.top_p
tokens = req.max_tokens if req.max_tokens is not None else controller.max_tokens
ai_client.set_model_params(temp, tokens, controller.history_trunc_limit, top_p)
ai_client.set_agent_tools(controller.ui_agent_tools)
if req.auto_add_history:
with controller._pending_history_adds_lock:
controller._pending_history_adds.append({
"role": "User",
"content": user_msg,
"collapsed": True,
"ts": project_manager.now_ts()
})
try:
resp = ai_client.send(stable_md, user_msg, base_dir, controller.last_file_items, disc_text, rag_engine=controller.rag_engine)
user_msg = req.prompt
# 2. RAG Retrieval
if controller.rag_engine and controller.rag_config and controller.rag_config.enabled:
try:
chunks = controller.rag_engine.search(user_msg)
if chunks:
context_block = "## Retrieved Context\n\n"
for i, chunk in enumerate(chunks):
path = chunk.get("metadata", {}).get("path", "unknown")
context_block += f"### Chunk {i+1} (Source: {path})\n{chunk.get('document', '')}\n\n"
user_msg = context_block + user_msg
except Exception as e:
sys.stderr.write(f"RAG search error: {e}\n")
sys.stderr.flush()
# 3. Symbol Resolution
try:
from src.markdown_helper import parse_symbols, get_symbol_definition
symbols = parse_symbols(user_msg)
file_paths = [f.path if hasattr(f, "path") else f.get("path") if isinstance(f, dict) else str(f) for f in controller.last_file_items]
for symbol in symbols:
res = get_symbol_definition(symbol, file_paths)
if res:
file_path, definition, line = res
user_msg += f'\n\n[Definition: {symbol} from {file_path} (line {line})]\n```python\n{definition}\n```'
except Exception as e:
sys.stderr.write(f"Symbol resolution error: {e}\n")
sys.stderr.flush()
base_dir = controller.active_project_root
csp = filter(bool, [controller.ui_global_system_prompt.strip(), controller.ui_project_system_prompt.strip()])
ai_client.set_custom_system_prompt("\n\n".join(csp))
ai_client.set_base_system_prompt(controller.ui_base_system_prompt)
ai_client.set_use_default_base_prompt(controller.ui_use_default_base_prompt)
ai_client.set_project_context_marker(controller.ui_project_context_marker)
temp = req.temperature if req.temperature is not None else controller.temperature
top_p = req.top_p if req.top_p is not None else controller.top_p
tokens = req.max_tokens if req.max_tokens is not None else controller.max_tokens
ai_client.set_model_params(temp, tokens, controller.history_trunc_limit, top_p)
ai_client.set_agent_tools(controller.ui_agent_tools)
if req.auto_add_history:
with controller._pending_history_adds_lock:
controller._pending_history_adds.append({
"role": "AI",
"content": resp,
"role": "User",
"content": user_msg,
"collapsed": True,
"ts": project_manager.now_ts()
})
controller._recalculate_session_usage()
duration = time.time() - start_time
return {
"text": resp,
"metadata": {
"provider": controller.current_provider,
"model": controller.current_model,
"duration_sec": round(duration, 3),
"timestamp": project_manager.now_ts()
},
"usage": controller.session_usage
}
try:
resp = ai_client.send(stable_md, user_msg, base_dir, controller.last_file_items, disc_text, rag_engine=None)
if req.auto_add_history:
with controller._pending_history_adds_lock:
controller._pending_history_adds.append({
"role": "AI",
"content": resp,
"collapsed": True,
"ts": project_manager.now_ts()
})
controller._recalculate_session_usage()
duration = time.time() - start_time
return {
"text": resp,
"metadata": {
"provider": controller.current_provider,
"model": controller.current_model,
"duration_sec": round(duration, 3),
"timestamp": project_manager.now_ts()
},
"usage": controller.session_usage
}
except ai_client.ProviderError as e:
raise HTTPException(status_code=500, detail=e.ui_message())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
except ai_client.ProviderError as e:
raise HTTPException(status_code=502, detail=f"AI Provider Error: {e.ui_message()}")
except Exception as e:
+102
View File
@@ -0,0 +1,102 @@
def _api_generate(controller: 'AppController', req: GenerateRequest) -> dict[str, Any]:
"""
Triggers an AI generation request using the current project context.
[SDM: src/app_controller.py:_api_generate]
"""
if not req.prompt.strip():
raise HTTPException(status_code=400, detail="Prompt cannot be empty")
with controller._send_thread_lock:
start_time = time.time()
try:
# 1. Build context
from src import aggregate
full_md, path, file_items, stable_md, disc_text = controller._do_generate()
controller._last_stable_md = stable_md
controller.last_md = md
controller.last_md_path = path
controller.last_file_items = file_items
user_msg = req.prompt
# 2. RAG Retrieval
if controller.rag_engine and controller.rag_config and controller.rag_config.enabled:
try:
chunks = controller.rag_engine.search(user_msg)
if chunks:
context_block = "## Retrieved Context\n\n"
for i, chunk in enumerate(chunks):
path = chunk.get("metadata", {}).get("path", "unknown")
context_block += f"### Chunk {i+1} (Source: {path})\n{chunk.get('document', '')}\n\n"
user_msg = context_block + user_msg
except Exception as e:
sys.stderr.write(f"RAG search error: {e}\n")
sys.stderr.flush()
# 3. Symbol Resolution
try:
from src.markdown_helper import parse_symbols, get_symbol_definition
symbols = parse_symbols(user_msg)
file_paths = [f.path if hasattr(f, "path") else f.get("path") if isinstance(f, dict) else str(f) for f in controller.last_file_items]
for symbol in symbols:
res = get_symbol_definition(symbol, file_paths)
if res:
file_path, definition, line = res
user_msg += f'\n\n[Definition: {symbol} from {file_path} (line {line})]\n```python\n{definition}\n```'
except Exception as e:
sys.stderr.write(f"Symbol resolution error: {e}\n")
sys.stderr.flush()
base_dir = controller.active_project_root
csp = filter(bool, [controller.ui_global_system_prompt.strip(), controller.ui_project_system_prompt.strip()])
ai_client.set_custom_system_prompt("\n\n".join(csp))
ai_client.set_base_system_prompt(controller.ui_base_system_prompt)
ai_client.set_use_default_base_prompt(controller.ui_use_default_base_prompt)
ai_client.set_project_context_marker(controller.ui_project_context_marker)
temp = req.temperature if req.temperature is not None else controller.temperature
top_p = req.top_p if req.top_p is not None else controller.top_p
tokens = req.max_tokens if req.max_tokens is not None else controller.max_tokens
ai_client.set_model_params(temp, tokens, controller.history_trunc_limit, top_p)
ai_client.set_agent_tools(controller.ui_agent_tools)
if req.auto_add_history:
with controller._pending_history_adds_lock:
controller._pending_history_adds.append({
"role": "User",
"content": user_msg,
"collapsed": True,
"ts": project_manager.now_ts()
})
try:
resp = ai_client.send(stable_md, user_msg, base_dir, controller.last_file_items, disc_text, rag_engine=None)
if req.auto_add_history:
with controller._pending_history_adds_lock:
controller._pending_history_adds.append({
"role": "AI",
"content": resp,
"collapsed": True,
"ts": project_manager.now_ts()
})
controller._recalculate_session_usage()
duration = time.time() - start_time
return {
"text": resp,
"metadata": {
"provider": controller.current_provider,
"model": controller.current_model,
"duration_sec": round(duration, 3),
"timestamp": project_manager.now_ts()
},
"usage": controller.session_usage
}
except ai_client.ProviderError as e:
raise HTTPException(status_code=500, detail=e.ui_message())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
+8 -32
View File
@@ -380,35 +380,11 @@ class App:
def __getattr__(self, name: str) -> Any:
if name == 'controller':
raise AttributeError(name)
try:
ctrl = object.__getattribute__(self, 'controller')
except AttributeError:
raise AttributeError(name)
if ctrl is not None:
try:
val = getattr(ctrl, name)
sys.stderr.write(f"[DEBUG __getattr__] name={name}, val_type={type(val).__name__}\n")
sys.stderr.flush()
return val
except AttributeError:
pass
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
return getattr(self.controller, name)
def __setattr__(self, name: str, value: Any) -> None:
if name == 'controller':
object.__setattr__(self, name, value)
return
try:
# Use object.__getattribute__ to avoid recursion
ctrl = object.__getattribute__(self, 'controller')
except AttributeError:
ctrl = None
if ctrl is not None and hasattr(ctrl, name):
setattr(ctrl, name, value)
if name != 'controller' and hasattr(self, 'controller') and hasattr(self.controller, name):
setattr(self.controller, name, value)
else:
object.__setattr__(self, name, value)
@@ -696,7 +672,7 @@ class App:
if show_content:
h = 150 if is_standalone else 100
with imscope.child(f"thinking_content_{entry_index}", imgui.ImVec2(0, h), True):
with imscope.child(f"thinking_content_{entry_index}", 0, h, True):
for idx, seg in enumerate(segments):
content = seg.get("content", "")
marker = seg.get("marker", "thinking")
@@ -1014,7 +990,7 @@ class App:
imgui.same_line()
if imgui.button("Redo") and self.history.can_redo: self._handle_redo()
imgui.separator()
with imscope.child("history_list", imgui.ImVec2(0, 0), True):
with imscope.child("history_list", 0, 0, True):
history = self.history.get_history()
if not history: imgui.text("No history available.")
else: iterate_history()
@@ -2558,7 +2534,7 @@ class App:
if not hasattr(self, 'files_screenshots_split'): self.files_screenshots_split = 0.65
split_y = int(avail * self.files_screenshots_split)
if imgui.collapsing_header("Files", imgui.TreeNodeFlags_.default_open):
with imscope.child("Files_child", imgui.ImVec2(-1, split_y), True):
with imscope.child("Files_child", -1, split_y, True):
if not hasattr(self, 'files_last_selected'): self.files_last_selected = -1
with imscope.table("files_table", 5, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
@@ -2647,7 +2623,7 @@ class App:
imgui.separator()
if imgui.collapsing_header("Screenshots", imgui.TreeNodeFlags_.default_open):
with imscope.child("Shots_child", imgui.ImVec2(-1, -1), True):
with imscope.child("Shots_child", -1, -1, True):
for i, s in enumerate(self.screenshots):
if imgui.button(f"x##s{i}"):
self.screenshots.pop(i)
@@ -3751,7 +3727,7 @@ class App:
with imscope.style_color(imgui.Col_.frame_bg, blink_color) if is_blinking else nullcontext():
with imscope.style_color(imgui.Col_.child_bg, blink_color) if is_blinking else nullcontext():
with imscope.child("response_scroll_area", imgui.ImVec2(0, -40), True):
with imscope.child("response_scroll_area", 0, -40, True):
with theme.ai_text_style():
segments, parsed_response = thinking_parser.parse_thinking_trace(self.ai_response)
if segments:
+5
View File
@@ -0,0 +1,5 @@
def __setattr__(self, name: str, value: Any) -> None:
if name != 'controller' and hasattr(self, 'controller') and hasattr(self.controller, name):
setattr(self.controller, name, value)
else:
object.__setattr__(self, name, value)
+2 -1
View File
@@ -108,7 +108,8 @@ class RAGEngine:
def _init_vector_store(self):
vs_config = self.config.vector_store
if vs_config.provider == 'chroma':
db_path = os.path.abspath(os.path.join(self.base_dir, ".slop_cache", "rag_chroma"))
# Use a collection-specific path to avoid dimension conflicts and locks between tests
db_path = os.path.abspath(os.path.join(self.base_dir, ".slop_cache", f"chroma_{vs_config.collection_name}"))
os.makedirs(db_path, exist_ok=True)
chroma_module = _get_chromadb()
if chroma_module is None: