Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion utils/llm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ All configuration is via environment variables:

| Variable | Default | Description |
|---|---|---|
| `LLM_PROVIDER` | `venice` | Provider name: `venice`, `openai`, `anthropic`, or custom |
| `LLM_PROVIDER` | `venice` | Provider name: `venice`, `groq`, `openai`, `anthropic`, or custom |
| `LLM_API_KEY` | *(required)* | API key for the LLM provider |
| `LLM_MODEL` | `grok-41-fast` | Model identifier |
| `LLM_BASE_URL` | *(per provider)* | API base URL (not needed for anthropic) |
Expand All @@ -154,6 +154,7 @@ All configuration is via environment variables:
| Provider | Base URL | Default Model | Package |
|---|---|---|---|
| Venice.ai | `https://api.venice.ai/api/v1` | `grok-41-fast` | `openai` |
| Groq | `https://api.groq.com/openai/v1` | `openai/gpt-oss-safeguard-20b` | `openai` |
| OpenAI | `https://api.openai.com/v1` | `gpt-4o-mini` | `openai` |
| Anthropic | *(native API)* | `claude-haiku-4-5-20251001` | `anthropic` |
| Custom | Set `LLM_BASE_URL` | Set `LLM_MODEL` | `openai` |
Expand Down
50 changes: 32 additions & 18 deletions utils/llm/ai_explainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
- Asset/token flow changes
- State changes and their impact
- Risk assessment (LOW/MEDIUM/HIGH/CRITICAL)
- Any concerns or notable observations
- Any concerns or notable observations"""

FORMAT_REMINDER = """
Format your response exactly as:
TLDR: <your short summary>

Expand Down Expand Up @@ -138,9 +139,27 @@ def _build_prompt(
if simulation:
parts.append(f"\n--- Simulation Results ---\n{_format_simulation_context(simulation)}")

parts.append(FORMAT_REMINDER)

return "\n".join(parts)


def _find_marker(text: str, keyword: str) -> tuple[int, int]:
"""Find a section marker like 'TLDR:' or '### DETAIL' and return (start_of_marker, start_of_content).

Handles variations: 'KEYWORD:', '## KEYWORD', '**KEYWORD**', '**KEYWORD:**', etc.
Returns (-1, -1) if not found.
"""
import re

heading = r"#{1,4}" # fmt: skip
pattern = rf"(?:^|\n)\s*(?:{heading}\s+)?(?:\*{{2}})?{keyword}(?:\*{{2}})?[:\s]*"
match = re.search(pattern, text, re.IGNORECASE)
if match:
return match.start(), match.end()
return -1, -1


def _parse_explanation(raw: str) -> Explanation:
"""Parse LLM response into summary and detail sections.

Expand All @@ -151,28 +170,23 @@ def _parse_explanation(raw: str) -> Explanation:
<detailed analysis>

Falls back gracefully if the LLM doesn't follow the format exactly.
Handles markdown-style headers like '### DETAIL' or '**TLDR:**'.
"""
# Try to split on DETAIL: marker
upper = raw.upper()
detail_idx = upper.find("DETAIL:")
tldr_idx = upper.find("TLDR:")

if tldr_idx != -1 and detail_idx != -1:
# Both markers found — extract each section
tldr_start = tldr_idx + len("TLDR:")
summary = raw[tldr_start:detail_idx].strip()
detail = raw[detail_idx + len("DETAIL:") :].strip()
tldr_start, tldr_content = _find_marker(raw, "TLDR")
detail_start, detail_content = _find_marker(raw, "DETAIL")

if tldr_start != -1 and detail_start != -1:
summary = raw[tldr_content:detail_start].strip()
detail = raw[detail_content:].strip()
return Explanation(summary=summary, detail=detail)

if tldr_idx != -1:
# Only TLDR found — everything after it is the summary, no detail
summary = raw[tldr_idx + len("TLDR:") :].strip()
if tldr_start != -1:
summary = raw[tldr_content:].strip()
return Explanation(summary=summary, detail="")

if detail_idx != -1:
# Only DETAIL found — everything before is summary
summary = raw[:detail_idx].strip()
detail = raw[detail_idx + len("DETAIL:") :].strip()
if detail_start != -1:
summary = raw[:detail_start].strip()
detail = raw[detail_content:].strip()
return Explanation(summary=summary, detail=detail)

# No markers — use full response as summary (backward compatible)
Expand Down
5 changes: 5 additions & 0 deletions utils/llm/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Provider defaults:
venice: base_url=https://api.venice.ai/api/v1, model=llama-3.3-70b
groq: base_url=https://api.groq.com/openai/v1, model=openai/gpt-oss-safeguard-20b
openai: base_url=https://api.openai.com/v1, model=gpt-4o-mini
anthropic: model=claude-haiku-4-5-20251001 (uses native Anthropic API)
Custom: Set LLM_BASE_URL and LLM_MODEL explicitly.
Expand All @@ -26,6 +27,10 @@
"base_url": "https://api.venice.ai/api/v1",
"model": "grok-41-fast",
},
"groq": {
"base_url": "https://api.groq.com/openai/v1",
"model": "openai/gpt-oss-safeguard-20b",
},
"openai": {
"base_url": "https://api.openai.com/v1",
"model": "gpt-4o-mini",
Expand Down