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
21 changes: 21 additions & 0 deletions app/services/ai/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,27 @@ def get_best_embedding_provider(self) -> Optional['EmbeddingProvider']:
_config: Optional[AIConfig] = None


# Symbolic tiers a prompt YAML can name instead of a concrete model id.
# Hardcoding ids in ~20 prompt files made retiring a model a sweep across all of
# them; naming a tier keeps the id in AIConfig, where one edit moves everything.
MODEL_TIERS = ('default', 'quality', 'fast')


def resolve_model_name(name: str) -> 'AIModel':
"""Resolve a prompt's ``model`` field to an AIModel.

Accepts a tier (``default`` / ``quality`` / ``fast``), which is looked up on
the active config, or a concrete model id / alias.

Raises ValueError or KeyError for anything unrecognised, matching
``AIModel.from_string`` so callers keep one error path.
"""
key = (name or '').strip().lower()
if key in MODEL_TIERS:
return getattr(get_ai_config(), f'{key}_model')
return AIModel.from_string(name)


def get_ai_config() -> AIConfig:
"""
Get the singleton AI configuration instance.
Expand Down
33 changes: 29 additions & 4 deletions app/services/ai/prompt_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,28 @@
import logging
from typing import Dict, Any, Optional, List, Tuple
from pathlib import Path
from app.services.ai.config import AIModel, AIProvider as AIProviderEnum, get_ai_config
from app.services.ai.config import (AIModel, AIProvider as AIProviderEnum,
get_ai_config, resolve_model_name)
from app.models.user_ai_preferences import UserAIPreference

logger = logging.getLogger(__name__)


def _provider_for_display(prompt_data: Dict[str, Any]) -> Optional[str]:
"""Provider name for the settings UI, derived from the prompt's model.

Prompts no longer declare a provider — the model carries it — but the
settings screens still want to show one.
"""
model_str = prompt_data.get('model')
if model_str:
try:
return resolve_model_name(model_str).provider.value
except (ValueError, KeyError):
pass
return prompt_data.get('preferred_provider')


class PromptService:
"""
Central service for managing and loading LLM prompts.
Expand Down Expand Up @@ -206,7 +222,7 @@ def get_prompt_info(self, category: str, name: str) -> Dict[str, Any]:
'category': category,
'max_tokens': prompt_data.get('max_tokens', 2000),
'temperature': prompt_data.get('temperature', 0.7),
'preferred_provider': prompt_data.get('preferred_provider'),
'preferred_provider': _provider_for_display(prompt_data),
'model': prompt_data.get('model'),
'required_variables': self._extract_variables(template),
'has_system_context': 'system_context' in prompt_data,
Expand Down Expand Up @@ -373,10 +389,19 @@ def resolve_model_provider(
model_str = metadata.get('model')
if model_str:
try:
model_enum = AIModel.from_string(model_str)
# Tier names ('quality', 'fast', ...) resolve through AIConfig, so a
# prompt does not have to name a concrete model id.
model_enum = resolve_model_name(model_str)
except (ValueError, KeyError):
logger.warning(f"Unknown model in prompt metadata: {model_str}")

# The provider comes from the model, which already carries it. A prompt that
# also declared `preferred_provider` could contradict its own model, and once
# prompts name a tier the YAML cannot know what that tier resolves to.
if model_enum is not None:
return model_enum, model_enum.provider

# No model named: a bare provider still selects one, using its default model.
provider_str = metadata.get('preferred_provider')
if provider_str:
try:
Expand Down Expand Up @@ -445,7 +470,7 @@ def get_effective_model_display(
info = prompt_service.get_prompt_info(prompt_category, prompts[0])
model_str = info.get('model')
if model_str:
model = AIModel.from_string(model_str)
model = resolve_model_name(model_str)
return _MODEL_DISPLAY_NAMES.get(model.model_id, model.model_id)
except (ValueError, KeyError):
pass
Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/argos/explanation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Generates detailed explanation for a specific Argos insight"
version: "1.0"
category: "argos"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 5500
temperature: 0.5

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/argos/relevance_scoring.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Scores insight candidates for relevance to current research"
version: "1.0"
category: "argos"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 7000
temperature: 0.3

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/checkpoint/checkpoint_analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ version: "1.0"
category: "checkpoint"

# AI Provider Configuration
preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 1500
temperature: 0.4

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/companion/companion_agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ description: "Global agentic companion — answers grounded in the user's own ac
version: "1.0"
category: "companion"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
# Per-turn output budget. Tool-call turns are naturally short; this headroom is for
# the final answer when a question spans several sources and needs real synthesis.
max_tokens: 4096
Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/companion/counter_evidence.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ description: "Surface disconfirming evidence and questions that challenge a rese
version: "1.0"
category: "companion"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 6000
temperature: 0.3

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/companion/knowledge_summary.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ description: "Compress one piece of a user's research knowledge into a short, em
version: "1.0"
category: "companion"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "fast"
max_tokens: 120
temperature: 0.2

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/companion/live_companion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ description: "Answer research questions during a session, surfacing facts not op
version: "1.0"
category: "companion"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 6000
temperature: 0.3

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/companion/research_brief.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ description: "Generate a pre-session research brief for a research step"
version: "1.0"
category: "companion"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 8000
temperature: 0.3

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/companion/session_wrapup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ description: "Summarize a research session: accomplishments, gaps, next steps"
version: "1.0"
category: "companion"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 6000
temperature: 0.3

Expand Down
1 change: 0 additions & 1 deletion app/services/ai/prompts/intelligence/decision_review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ version: "1.0"
category: "intelligence"

# AI Provider Configuration
preferred_provider: "claude"
model: "claude-sonnet-4-20250514"
max_tokens: 1000
temperature: 0.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ version: "1.0"
category: "intelligence"

# AI Provider Configuration
preferred_provider: "claude"
model: "claude-sonnet-4-20250514"
max_tokens: 500
temperature: 0.5
Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/intelligence/research_qa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ version: "1.0"
category: "intelligence"

# AI Provider Configuration
preferred_provider: "gemini" # Cost-effective for document Q&A
model: "gemini-2.5-flash"
model: "fast"
max_tokens: 1000
temperature: 0.2

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/intelligence/text_summarization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ version: "1.0"
category: "intelligence"

# AI Provider Configuration
preferred_provider: "gemini" # Cost-effective for summarization
model: "gemini-2.5-flash"
model: "fast"
max_tokens: 800
temperature: 0.3

Expand Down
1 change: 0 additions & 1 deletion app/services/ai/prompts/intelligence/thesis_analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ version: "1.0"
category: "intelligence"

# AI Provider Configuration
preferred_provider: "claude" # Can be overridden in ai_routing.yaml
model: "claude-sonnet-4-20250514"
max_tokens: 2000
temperature: 0.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ version: "1.0"
category: "intelligence"

# AI Provider Configuration
preferred_provider: "claude"
model: "claude-sonnet-4-20250514"
max_tokens: 2000
temperature: 0.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ version: "1.0"
category: "intelligence"

# AI Provider Configuration
preferred_provider: "claude"
model: "claude-sonnet-4-20250514"
max_tokens: 800
temperature: 0.5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "AI-powered analysis of portfolio data to extract behavioral insigh
version: "1.0"
category: "intelligence"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.5

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Deep behavioral analysis from raw transactions with tax context, F
version: "1.0"
category: "portfolio"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 65000
temperature: 0.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Detect sector momentum chasing patterns using AI knowledge of hist
version: "1.0"
category: "portfolio"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ description: "Tax optimization analysis adapted to investor's tax residence, wit
version: "1.0"
category: "portfolio"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.3

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/research/bias_check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Analyze investment thesis for cognitive biases (Munger framework)"
version: "1.0"
category: "research"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.3

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/research/challenge_mode.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Generates counter-arguments and critical questions to test user's
version: "1.0"
category: "research"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.7

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/research/checklist_item_analyze.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Analyzes financial documents and available sources to answer a spe
version: "1.0"
category: "research"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.4

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/research/elaboration_mode.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Generates follow-up questions and suggests deeper areas to explore
version: "1.0"
category: "research"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.4

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/research/factcheck_mode.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Verifies claims in user research with evidence-based verdicts"
version: "2.0"
category: "research"

preferred_provider: "gemini"
model: "gemini-3-flash-preview"
model: "quality"
max_tokens: 10000
temperature: 0.2

Expand Down
3 changes: 1 addition & 2 deletions app/services/ai/prompts/screening/screening_analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ description: "Analyze kill checklist history to surface patterns and improvement
version: "1.0"
category: "screening"

preferred_provider: "gemini"
model: "gemini-2.5-flash"
model: "fast"
max_tokens: 4000
temperature: 0.3

Expand Down
19 changes: 9 additions & 10 deletions app/services/ai_research_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

from app.services.ai import ai_service
from app.services.ai.prompt_service import prompt_service
from app.services.ai.config import AIProvider, AIModel
from app.services.ai.config import AIProvider, AIModel, resolve_model_name

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -292,21 +292,20 @@ def _generate_response(
prompt_text = prompt_data['prompt']
metadata = prompt_data['metadata']

# Convert provider and model from strings to enums
provider_str = metadata.get('preferred_provider')
# Convert the prompt's model to an enum. The provider is not read
# from the prompt — the model already carries it, and a separately
# declared provider could contradict it.
model_str = metadata.get('model')

provider_enum = None
model_enum = None

if provider_str:
try:
provider_enum = AIProvider(provider_str)
except ValueError:
logger.warning(f"Invalid provider '{provider_str}', using default")

if model_str:
model_enum = AIModel.from_string(model_str)
# Prompts name a tier ('quality'/'fast'), not a model id.
model_enum = resolve_model_name(model_str)
provider_enum = model_enum.provider

provider_str = provider_enum.value if provider_enum else None

logger.info(
f"Generating {mode} response for question: {question_text[:50]}... "
Expand Down
Loading