mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(learn): pass explicit model in tests to avoid API key requirement
SessionAnalyzer() without a model calls _detect_default_model() which raises when no API keys are set (e.g., in CI). Pass model="test-model" in the three tests that mock _call_llm.
This commit is contained in:
parent
4d14012c2f
commit
da481a359b
3 changed files with 16 additions and 22 deletions
|
|
@ -62,9 +62,7 @@ class SessionAnalyzer:
|
|||
def __init__(self, model: str | None = None):
|
||||
self.model = model
|
||||
|
||||
def analyze(
|
||||
self, project: ProjectInfo, sessions: list[SessionData]
|
||||
) -> AnalysisResult:
|
||||
def analyze(self, project: ProjectInfo, sessions: list[SessionData]) -> AnalysisResult:
|
||||
"""Analyze sessions and produce recommendations via LLM."""
|
||||
all_calls = [tc for s in sessions for tc in s.tool_calls]
|
||||
failed_calls = [tc for tc in all_calls if tc.is_error]
|
||||
|
|
@ -134,7 +132,9 @@ def _build_digest(project: ProjectInfo, sessions: list[SessionData]) -> str:
|
|||
|
||||
for session in sessions:
|
||||
if chars_used > char_budget:
|
||||
lines.append(f"... (remaining {len(sessions) - sessions.index(session)} sessions truncated)")
|
||||
lines.append(
|
||||
f"... (remaining {len(sessions) - sessions.index(session)} sessions truncated)"
|
||||
)
|
||||
break
|
||||
|
||||
session_header = (
|
||||
|
|
@ -179,7 +179,7 @@ def _format_event(event: SessionEvent) -> str | None:
|
|||
|
||||
if event.type == "user_message" and event.text.strip():
|
||||
text = event.text.strip()[:300]
|
||||
return f" [{event.msg_index}] USER: \"{text}\""
|
||||
return f' [{event.msg_index}] USER: "{text}"'
|
||||
|
||||
if event.type == "interruption":
|
||||
return f" [{event.msg_index}] INTERRUPTED: {event.text[:150]}"
|
||||
|
|
@ -188,7 +188,7 @@ def _format_event(event: SessionEvent) -> str | None:
|
|||
return (
|
||||
f" [{event.msg_index}] SUBAGENT: {event.agent_tool_count} tool calls, "
|
||||
f"{event.agent_tokens:,} tokens, {event.agent_duration_ms / 1000:.1f}s "
|
||||
f"— prompt: \"{event.agent_prompt[:100]}\""
|
||||
f'— prompt: "{event.agent_prompt[:100]}"'
|
||||
)
|
||||
|
||||
return None
|
||||
|
|
@ -384,7 +384,5 @@ class FailureAnalyzer:
|
|||
def __init__(self) -> None:
|
||||
self._analyzer = SessionAnalyzer()
|
||||
|
||||
def analyze(
|
||||
self, project: ProjectInfo, sessions: list[SessionData]
|
||||
) -> AnalysisResult:
|
||||
def analyze(self, project: ProjectInfo, sessions: list[SessionData]) -> AnalysisResult:
|
||||
return self._analyzer.analyze(project, sessions)
|
||||
|
|
|
|||
|
|
@ -240,9 +240,7 @@ class ClaudeCodeScanner(ConversationScanner):
|
|||
total_input_tokens += usage.get("cache_creation_input_tokens", 0)
|
||||
total_output_tokens += usage.get("output_tokens", 0)
|
||||
elif line_type == "user":
|
||||
self._extract_tool_results(
|
||||
d, tool_uses, tool_calls, events, msg_index, ts
|
||||
)
|
||||
self._extract_tool_results(d, tool_uses, tool_calls, events, msg_index, ts)
|
||||
self._extract_user_events(d, events, msg_index, ts)
|
||||
|
||||
except (OSError, UnicodeDecodeError) as e:
|
||||
|
|
@ -251,12 +249,8 @@ class ClaudeCodeScanner(ConversationScanner):
|
|||
|
||||
# Also wrap tool_calls as events for unified access
|
||||
for tc in tool_calls:
|
||||
if not any(
|
||||
e.type == "tool_call" and e.tool_call is tc for e in events
|
||||
):
|
||||
events.append(
|
||||
SessionEvent(type="tool_call", msg_index=tc.msg_index, tool_call=tc)
|
||||
)
|
||||
if not any(e.type == "tool_call" and e.tool_call is tc for e in events):
|
||||
events.append(SessionEvent(type="tool_call", msg_index=tc.msg_index, tool_call=tc))
|
||||
events.sort(key=lambda e: e.msg_index)
|
||||
|
||||
return SessionData(
|
||||
|
|
@ -332,7 +326,9 @@ class ClaudeCodeScanner(ConversationScanner):
|
|||
)
|
||||
tool_calls.append(tc)
|
||||
events.append(
|
||||
SessionEvent(type="tool_call", msg_index=msg_index, timestamp=timestamp, tool_call=tc)
|
||||
SessionEvent(
|
||||
type="tool_call", msg_index=msg_index, timestamp=timestamp, tool_call=tc
|
||||
)
|
||||
)
|
||||
|
||||
# Extract subagent summary from toolUseResult metadata
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ class TestSessionAnalyzer:
|
|||
"memory_file_rules": [],
|
||||
}
|
||||
|
||||
analyzer = SessionAnalyzer()
|
||||
analyzer = SessionAnalyzer(model="test-model")
|
||||
sessions = [
|
||||
SessionData(
|
||||
session_id="s1",
|
||||
|
|
@ -283,7 +283,7 @@ class TestSessionAnalyzer:
|
|||
def test_handles_llm_failure_gracefully(self, mock_call_llm: MagicMock):
|
||||
mock_call_llm.side_effect = RuntimeError("API key not set")
|
||||
|
||||
analyzer = SessionAnalyzer()
|
||||
analyzer = SessionAnalyzer(model="test-model")
|
||||
sessions = [
|
||||
SessionData(
|
||||
session_id="s1",
|
||||
|
|
@ -309,7 +309,7 @@ class TestSessionAnalyzer:
|
|||
]
|
||||
sessions = [SessionData(session_id="s1", tool_calls=[tc], events=events)]
|
||||
|
||||
analyzer = SessionAnalyzer()
|
||||
analyzer = SessionAnalyzer(model="test-model")
|
||||
analyzer.analyze(_project(), sessions)
|
||||
|
||||
# Check that the digest passed to the LLM includes user message
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue