Files
ALwrity/backend/api/research/models.py

238 lines
9.8 KiB
Python

"""
Research API Models
All Pydantic request/response models for research endpoints.
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
# ============================================================================
# Research Execution Models
# ============================================================================
class ResearchRequest(BaseModel):
"""API request for research."""
query: str = Field(..., description="Main research query or topic")
keywords: List[str] = Field(default_factory=list, description="Additional keywords")
# Research configuration
goal: Optional[str] = Field(default="factual", description="Research goal: factual, trending, competitive, etc.")
depth: Optional[str] = Field(default="standard", description="Research depth: quick, standard, comprehensive, expert")
provider: Optional[str] = Field(default="auto", description="Provider preference: auto, exa, tavily, google")
# Personalization
content_type: Optional[str] = Field(default="general", description="Content type: blog, podcast, video, etc.")
industry: Optional[str] = None
target_audience: Optional[str] = None
tone: Optional[str] = None
# Constraints
max_sources: int = Field(default=10, ge=1, le=25)
recency: Optional[str] = None # day, week, month, year
# Domain filtering
include_domains: List[str] = Field(default_factory=list)
exclude_domains: List[str] = Field(default_factory=list)
# Advanced mode
advanced_mode: bool = False
# Raw provider parameters (only if advanced_mode=True)
exa_category: Optional[str] = None
exa_search_type: Optional[str] = None
tavily_topic: Optional[str] = None
tavily_search_depth: Optional[str] = None
tavily_include_answer: bool = False
tavily_time_range: Optional[str] = None
class ResearchResponse(BaseModel):
"""API response for research."""
success: bool
task_id: Optional[str] = None # For async requests
# Results (if synchronous)
sources: List[Dict[str, Any]] = Field(default_factory=list)
keyword_analysis: Dict[str, Any] = Field(default_factory=dict)
competitor_analysis: Dict[str, Any] = Field(default_factory=dict)
suggested_angles: List[str] = Field(default_factory=list)
# Metadata
provider_used: Optional[str] = None
search_queries: List[str] = Field(default_factory=list)
# Error handling
error_message: Optional[str] = None
error_code: Optional[str] = None
class ProviderStatusResponse(BaseModel):
"""Response for provider status check."""
exa: Dict[str, Any]
tavily: Dict[str, Any]
google: Dict[str, Any]
# ============================================================================
# Intent-Driven Research Models
# ============================================================================
class AnalyzeIntentRequest(BaseModel):
"""Request to analyze user research intent."""
user_input: str = Field(..., description="User's keywords, question, or goal")
keywords: List[str] = Field(default_factory=list, description="Extracted keywords")
use_persona: bool = Field(True, description="Use research persona for context")
use_competitor_data: bool = Field(True, description="Use competitor data for context")
# User-provided intent settings (optional - if provided, use these instead of inferring)
user_provided_purpose: Optional[str] = Field(None, description="User-selected purpose (learn, create_content, etc.)")
user_provided_content_output: Optional[str] = Field(None, description="User-selected content output (blog, podcast, etc.)")
user_provided_depth: Optional[str] = Field(None, description="User-selected depth (overview, detailed, expert)")
class AnalyzeIntentResponse(BaseModel):
"""Response from intent analysis with optimized provider parameters."""
success: bool
intent: Dict[str, Any]
analysis_summary: str
suggested_queries: List[Dict[str, Any]]
suggested_keywords: List[str]
suggested_angles: List[str]
quick_options: List[Dict[str, Any]]
confidence_reason: Optional[str] = None
great_example: Optional[str] = None
error_message: Optional[str] = None
# Unified: Optimized provider parameters based on intent
optimized_config: Optional[Dict[str, Any]] = None # Provider settings auto-configured from intent
recommended_provider: Optional[str] = None # Best provider for this intent (exa, tavily, google)
# Google Trends configuration (if trends in deliverables)
trends_config: Optional[Dict[str, Any]] = None # Trends keywords and settings with justifications
class IntentDrivenResearchRequest(BaseModel):
"""Request for intent-driven research."""
# Intent from previous analyze step, or minimal input for auto-inference
user_input: str = Field(..., description="User's original input")
# Optional: Confirmed intent from UI (if user modified the inferred intent)
confirmed_intent: Optional[Dict[str, Any]] = None
# Optional: Specific queries to run (if user selected from suggested)
selected_queries: Optional[List[Dict[str, Any]]] = None
# Research configuration
max_sources: int = Field(default=10, ge=1, le=25)
include_domains: List[str] = Field(default_factory=list)
exclude_domains: List[str] = Field(default_factory=list)
# Google Trends configuration (from intent analysis)
trends_config: Optional[Dict[str, Any]] = None # Trends keywords and settings
# Skip intent inference (for re-runs with same intent)
skip_inference: bool = False
class IntentDrivenResearchResponse(BaseModel):
"""Response from intent-driven research."""
success: bool
# Direct answers
primary_answer: str = ""
secondary_answers: Dict[str, Optional[str]] = Field(default_factory=dict)
focus_areas_coverage: Dict[str, Optional[str]] = Field(default_factory=dict)
also_answering_coverage: Dict[str, Optional[str]] = Field(default_factory=dict)
# Deliverables
statistics: List[Dict[str, Any]] = Field(default_factory=list)
expert_quotes: List[Dict[str, Any]] = Field(default_factory=list)
case_studies: List[Dict[str, Any]] = Field(default_factory=list)
trends: List[Dict[str, Any]] = Field(default_factory=list)
comparisons: List[Dict[str, Any]] = Field(default_factory=list)
best_practices: List[str] = Field(default_factory=list)
step_by_step: List[str] = Field(default_factory=list)
pros_cons: Optional[Dict[str, Any]] = None
definitions: Dict[str, str] = Field(default_factory=dict)
examples: List[str] = Field(default_factory=list)
predictions: List[str] = Field(default_factory=list)
# Content-ready outputs
executive_summary: str = ""
key_takeaways: List[str] = Field(default_factory=list)
suggested_outline: List[str] = Field(default_factory=list)
# Sources and metadata
sources: List[Dict[str, Any]] = Field(default_factory=list)
confidence: float = 0.8
gaps_identified: List[str] = Field(default_factory=list)
follow_up_queries: List[str] = Field(default_factory=list)
intent: Optional[Dict[str, Any]] = None
google_trends_data: Optional[Dict[str, Any]] = None
error_message: Optional[str] = None
# ============================================================================
# Research Project Models
# ============================================================================
class SaveResearchProjectRequest(BaseModel):
"""Request to save a research project to database."""
project_id: Optional[str] = Field(None, description="Project ID for updates (optional, auto-generated if not provided)")
title: Optional[str] = Field(None, description="Project title")
keywords: List[str] = Field(..., description="Research keywords")
industry: str = Field(..., description="Industry")
target_audience: str = Field(..., description="Target audience")
research_mode: str = Field(..., description="Research mode (comprehensive, targeted, basic)")
config: Dict[str, Any] = Field(..., description="Research configuration")
intent_analysis: Optional[Dict[str, Any]] = Field(None, description="Intent analysis result")
confirmed_intent: Optional[Dict[str, Any]] = Field(None, description="Confirmed research intent")
intent_result: Optional[Dict[str, Any]] = Field(None, description="Intent-driven research result")
legacy_result: Optional[Dict[str, Any]] = Field(None, description="Legacy research result")
current_step: int = Field(1, description="Current wizard step")
description: Optional[str] = Field(None, description="Project description")
class SaveResearchProjectResponse(BaseModel):
"""Response after saving research project."""
success: bool
asset_id: Optional[int] = None # Database ID (for backward compatibility)
project_id: Optional[str] = None # Project UUID (for lookups)
message: str
class ResearchProjectResponse(BaseModel):
"""Response model for research project."""
id: int
project_id: str
user_id: str
title: Optional[str] = None
keywords: List[str]
industry: Optional[str] = None
target_audience: Optional[str] = None
research_mode: Optional[str] = None
config: Optional[Dict[str, Any]] = None
intent_analysis: Optional[Dict[str, Any]] = None
confirmed_intent: Optional[Dict[str, Any]] = None
intent_result: Optional[Dict[str, Any]] = None
legacy_result: Optional[Dict[str, Any]] = None
trends_config: Optional[Dict[str, Any]] = None
current_step: int = 1
status: str = "draft"
is_favorite: bool = False
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class ResearchProjectListResponse(BaseModel):
"""Response model for listing research projects."""
projects: List[ResearchProjectResponse]
total: int
limit: int
offset: int