Merge pull request 'feat: add multi-model support for per-analysis LLM selection' (#64) from feature/multi-model into main
Test and Lint / test (pull_request) Has been cancelled
Test and Lint / test (pull_request) Has been cancelled
This commit is contained in:
@@ -41,6 +41,7 @@ class CompanyAnalysisResponse(BaseModel):
|
||||
patent_count: int
|
||||
success: bool
|
||||
error: str | None = None
|
||||
model: str | None = None
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
@@ -54,6 +55,15 @@ class BatchAnalysisResponse(BaseModel):
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
class CompanyAnalysisRequest(BaseModel):
|
||||
"""Request model for single company analysis with optional model selection."""
|
||||
|
||||
model: str | None = Field(
|
||||
default=None,
|
||||
description="LLM model to use (e.g. 'anthropic/claude-3.5-sonnet', 'openai/gpt-4o'). Defaults to server config.",
|
||||
)
|
||||
|
||||
|
||||
class BatchAnalysisRequest(BaseModel):
|
||||
"""Request model for batch company analysis."""
|
||||
|
||||
@@ -63,6 +73,10 @@ class BatchAnalysisRequest(BaseModel):
|
||||
max_workers: int = Field(
|
||||
default=3, ge=1, le=5, description="Max concurrent analyses"
|
||||
)
|
||||
model: str | None = Field(
|
||||
default=None,
|
||||
description="LLM model to use for all analyses in this batch. Defaults to server config.",
|
||||
)
|
||||
|
||||
|
||||
class JobStatus(BaseModel):
|
||||
@@ -140,6 +154,7 @@ def _convert_result(result: CompanyAnalysisResult) -> CompanyAnalysisResponse:
|
||||
patent_count=result.patent_count,
|
||||
success=result.success,
|
||||
error=result.error,
|
||||
model=result.model,
|
||||
timestamp=result.timestamp,
|
||||
)
|
||||
|
||||
@@ -453,6 +468,32 @@ async def get_analytics(
|
||||
)
|
||||
|
||||
|
||||
# ============== Model Selection Endpoints ==============
|
||||
|
||||
# Supported models via OpenRouter
|
||||
SUPPORTED_MODELS = [
|
||||
{"id": "anthropic/claude-3.5-sonnet", "name": "Claude 3.5 Sonnet", "provider": "Anthropic"},
|
||||
{"id": "openai/gpt-4o", "name": "GPT-4o", "provider": "OpenAI"},
|
||||
{"id": "openai/gpt-4o-mini", "name": "GPT-4o Mini", "provider": "OpenAI"},
|
||||
{"id": "google/gemini-pro-1.5", "name": "Gemini Pro 1.5", "provider": "Google"},
|
||||
{"id": "meta-llama/llama-3.1-70b-instruct", "name": "Llama 3.1 70B", "provider": "Meta"},
|
||||
]
|
||||
|
||||
|
||||
@app.get("/models", tags=["System"])
|
||||
async def list_models():
|
||||
"""List supported LLM models for analysis.
|
||||
|
||||
Returns the available models that can be passed as the `model` field
|
||||
in analysis requests. The default model is determined by the `MODEL`
|
||||
environment variable on the server.
|
||||
"""
|
||||
return {
|
||||
"models": SUPPORTED_MODELS,
|
||||
"default": config.model,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/analytics/trends", tags=["Analytics"])
|
||||
async def get_analytics_trends(
|
||||
days: int = Query(default=90, ge=7, le=365),
|
||||
|
||||
+17
-11
@@ -40,12 +40,13 @@ class LLMAnalyzer:
|
||||
else:
|
||||
self.client = None
|
||||
|
||||
def analyze_patent_content(self, patent_content: str, company_name: str) -> str:
|
||||
def analyze_patent_content(self, patent_content: str, company_name: str, model: str | None = None) -> str:
|
||||
"""Analyze patent content to estimate company innovation and performance.
|
||||
|
||||
Args:
|
||||
patent_content: Minimized patent text (abstract, claims, summary)
|
||||
company_name: Name of the company for context
|
||||
model: Optional model override (e.g. "openai/gpt-4o"). Defaults to config.
|
||||
|
||||
Returns:
|
||||
Analysis text describing innovation quality and potential impact
|
||||
@@ -63,6 +64,8 @@ Patent Content:
|
||||
|
||||
Provide a concise analysis (2-3 paragraphs) focusing on what this patent reveals about the company's technical direction and competitive advantage."""
|
||||
|
||||
effective_model = model or self.model
|
||||
|
||||
if self.test_mode:
|
||||
logger.debug("TEST MODE - Prompt that would be sent to LLM:\n%s", prompt)
|
||||
return "[TEST MODE - No API call made]"
|
||||
@@ -81,7 +84,7 @@ Provide a concise analysis (2-3 paragraphs) focusing on what this patent reveals
|
||||
response=cached["response"],
|
||||
company_name=company_name,
|
||||
analysis_type="single_patent",
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
metadata={
|
||||
"patent_content_length": len(patent_content),
|
||||
"cache_hit": True,
|
||||
@@ -94,7 +97,7 @@ Provide a concise analysis (2-3 paragraphs) focusing on what this patent reveals
|
||||
# Call API if no cache hit and client is available
|
||||
if self.client:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
max_tokens=1024,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
)
|
||||
@@ -106,7 +109,7 @@ Provide a concise analysis (2-3 paragraphs) focusing on what this patent reveals
|
||||
response=response_text,
|
||||
company_name=company_name,
|
||||
analysis_type="single_patent",
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
metadata={"patent_content_length": len(patent_content)},
|
||||
token_usage={
|
||||
"prompt_tokens": response.usage.prompt_tokens,
|
||||
@@ -124,13 +127,13 @@ Provide a concise analysis (2-3 paragraphs) focusing on what this patent reveals
|
||||
response=placeholder,
|
||||
company_name=company_name,
|
||||
analysis_type="single_patent",
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
metadata={"patent_content_length": len(patent_content), "pending": True}
|
||||
)
|
||||
return placeholder
|
||||
|
||||
def analyze_patent_portfolio(
|
||||
self, patents_data: list[Dict[str, str]], company_name: str
|
||||
self, patents_data: list[Dict[str, str]], company_name: str, model: str | None = None
|
||||
) -> str:
|
||||
"""Analyze multiple patents to estimate overall company performance.
|
||||
|
||||
@@ -165,13 +168,16 @@ Patent Portfolio:
|
||||
|
||||
Provide a comprehensive analysis (4-5 paragraphs) with a final verdict on the company's innovation strength and performance outlook."""
|
||||
|
||||
effective_model = model or self.model
|
||||
|
||||
if self.test_mode:
|
||||
logger.debug("TEST MODE - Portfolio prompt:\n%s", prompt)
|
||||
return "[TEST MODE]"
|
||||
|
||||
metadata = {
|
||||
"patent_count": len(patents_data),
|
||||
"patent_ids": [p['patent_id'] for p in patents_data]
|
||||
"patent_ids": [p['patent_id'] for p in patents_data],
|
||||
"model": effective_model,
|
||||
}
|
||||
|
||||
# Check cache first
|
||||
@@ -188,7 +194,7 @@ Provide a comprehensive analysis (4-5 paragraphs) with a final verdict on the co
|
||||
response=cached["response"],
|
||||
company_name=company_name,
|
||||
analysis_type="portfolio",
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
metadata={
|
||||
**metadata,
|
||||
"cache_hit": True,
|
||||
@@ -202,7 +208,7 @@ Provide a comprehensive analysis (4-5 paragraphs) with a final verdict on the co
|
||||
if self.client:
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
max_tokens=2048,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
)
|
||||
@@ -215,7 +221,7 @@ Provide a comprehensive analysis (4-5 paragraphs) with a final verdict on the co
|
||||
response=response_text,
|
||||
company_name=company_name,
|
||||
analysis_type="portfolio",
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
metadata=metadata,
|
||||
token_usage={
|
||||
"prompt_tokens": response.usage.prompt_tokens,
|
||||
@@ -235,7 +241,7 @@ Provide a comprehensive analysis (4-5 paragraphs) with a final verdict on the co
|
||||
response=placeholder,
|
||||
company_name=company_name,
|
||||
analysis_type="portfolio",
|
||||
model=self.model,
|
||||
model=effective_model,
|
||||
metadata={**metadata, "pending": True}
|
||||
)
|
||||
return placeholder
|
||||
|
||||
@@ -24,6 +24,7 @@ class CompanyAnalysisResult:
|
||||
patent_count: int
|
||||
success: bool
|
||||
error: str | None = None
|
||||
model: str | None = None
|
||||
timestamp: datetime = field(default_factory=datetime.now)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user