pounce/backend/app/services/llm_naming.py
Yves Gugger e135c3258b
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Remove chat companion, add LLM naming for Trends & Forge tabs (AI keyword expansion, concept generator)
2025-12-17 15:45:16 +01:00

180 lines
5.9 KiB
Python

"""
LLM-powered naming suggestions for Trends and Forge tabs.
Uses simple prompts for focused tasks - no complex agent loop.
"""
from __future__ import annotations
import json
import re
from typing import Optional
from app.config import get_settings
from app.services.llm_gateway import chat_completions
settings = get_settings()
async def expand_trend_keywords(trend: str, geo: str = "US") -> list[str]:
"""
Given a trending topic, generate related domain-friendly keywords.
Returns a list of 5-10 short, brandable keywords.
"""
prompt = f"""You are a domain naming expert. Given the trending topic "{trend}" (trending in {geo}),
suggest 8-10 short, memorable keywords that would make good domain names.
Rules:
- Each keyword should be 4-10 characters
- No spaces, hyphens, or special characters
- Mix of: related words, abbreviations, creative variations
- Think like a domain investor looking for valuable names
Return ONLY a JSON array of lowercase strings, nothing else.
Example: ["swiftie", "erastour", "taylormerch", "tswift"]"""
try:
res = await chat_completions({
"model": settings.llm_default_model,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.8,
"stream": False,
})
content = res.get("choices", [{}])[0].get("message", {}).get("content", "")
# Extract JSON array from response
match = re.search(r'\[.*?\]', content, re.DOTALL)
if match:
keywords = json.loads(match.group(0))
# Filter and clean
return [
kw.lower().strip()[:15]
for kw in keywords
if isinstance(kw, str) and 3 <= len(kw.strip()) <= 15 and kw.isalnum()
][:10]
except Exception as e:
print(f"LLM keyword expansion failed: {e}")
return []
async def analyze_trend(trend: str, geo: str = "US") -> str:
"""
Provide a brief analysis of why a trend is relevant for domain investors.
Returns 2-3 sentences max.
"""
prompt = f"""You are a domain investing analyst. The topic "{trend}" is currently trending in {geo}.
In 2-3 short sentences, explain:
1. Why this is trending (if obvious)
2. What domain opportunity this presents
Be concise and actionable. No fluff."""
try:
res = await chat_completions({
"model": settings.llm_default_model,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.5,
"stream": False,
})
content = res.get("choices", [{}])[0].get("message", {}).get("content", "")
# Clean up and limit length
content = content.strip()[:500]
return content
except Exception as e:
print(f"LLM trend analysis failed: {e}")
return ""
async def generate_brandable_names(
concept: str,
style: Optional[str] = None,
count: int = 15
) -> list[str]:
"""
Generate brandable domain names based on a concept description.
Args:
concept: Description like "AI startup for legal documents"
style: Optional style hint like "professional", "playful", "tech"
count: Number of names to generate
Returns list of brandable name suggestions (without TLD).
"""
style_hint = f" The style should be {style}." if style else ""
prompt = f"""You are an expert brand naming consultant. Generate {count} unique, brandable domain names for: "{concept}"{style_hint}
Rules:
- Names must be 4-8 characters (shorter is better)
- Easy to spell and pronounce
- Memorable and unique
- No dictionary words (invented names only)
- Mix of patterns: CVCVC (Zalor), CVCCV (Bento), short words (Lyft)
Return ONLY a JSON array of lowercase strings, nothing else.
Example: ["zenix", "klaro", "voxly", "nimbl", "brivv"]"""
try:
res = await chat_completions({
"model": settings.llm_default_model,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.9, # Higher creativity
"stream": False,
})
content = res.get("choices", [{}])[0].get("message", {}).get("content", "")
# Extract JSON array
match = re.search(r'\[.*?\]', content, re.DOTALL)
if match:
names = json.loads(match.group(0))
# Filter and clean
return [
name.lower().strip()
for name in names
if isinstance(name, str) and 3 <= len(name.strip()) <= 12 and name.isalnum()
][:count]
except Exception as e:
print(f"LLM brandable generation failed: {e}")
return []
async def generate_similar_names(brand: str, count: int = 12) -> list[str]:
"""
Generate names similar to an existing brand.
Useful for finding alternatives or inspired names.
"""
prompt = f"""You are a brand naming expert. Generate {count} new brandable names INSPIRED BY (but not copying) "{brand}".
The names should:
- Have similar length and rhythm to "{brand}"
- Feel like they belong in the same industry
- Be completely original (not existing brands)
- Be 4-8 characters, easy to spell
Return ONLY a JSON array of lowercase strings, nothing else."""
try:
res = await chat_completions({
"model": settings.llm_default_model,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.85,
"stream": False,
})
content = res.get("choices", [{}])[0].get("message", {}).get("content", "")
match = re.search(r'\[.*?\]', content, re.DOTALL)
if match:
names = json.loads(match.group(0))
return [
name.lower().strip()
for name in names
if isinstance(name, str) and 3 <= len(name.strip()) <= 12 and name.isalnum()
][:count]
except Exception as e:
print(f"LLM similar names failed: {e}")
return []