rmi-backend/app/alibaba_dashscope_connector.py

322 lines
9.7 KiB
Python

"""
Alibaba Cloud DashScope Connector - Qwen Models for Content Generation.
Supports: qwen-max, qwen-plus, qwen-turbo, qwen-coder, qwen-vl-max
"""
import logging
import os
import httpx
logger = logging.getLogger(__name__)
# ── Alibaba DashScope Config ─────────────────────────────────
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "")
DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/api/v1"
# Available Qwen models
QWEN_MODELS = {
"qwen-max": {
"context": 32000,
"best_for": "Long-form content, detailed copy, highest quality",
"cost": "$$",
},
"qwen-plus": {
"context": 32000,
"best_for": "Balanced quality/speed, marketing copy",
"cost": "$",
},
"qwen-turbo": {
"context": 8000,
"best_for": "Quick drafts, social posts, fastest",
"cost": "¢",
},
"qwen-coder": {
"context": 32000,
"best_for": "Technical docs, API guides, code",
"cost": "$$",
},
"qwen-vl-max": {
"context": 8000,
"best_for": "Image + text, vision tasks",
"cost": "$$$",
},
}
class AlibabaDashScopeConnector:
"""Alibaba DashScope AI services connector."""
def __init__(self):
self.api_key = DASHSCOPE_API_KEY
self._session = None
def _get_session(self):
if self._session is None:
self._session = httpx.AsyncClient(
timeout=120.0,
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
)
return self._session
async def generate_text(
self,
prompt: str,
model: str = "qwen-plus",
max_tokens: int = 1000,
temperature: float = 0.7,
system_prompt: str | None = None,
) -> dict:
"""
Generate text using Qwen models.
Args:
prompt: User prompt
model: Model name (qwen-max, qwen-plus, qwen-turbo, qwen-coder)
max_tokens: Max tokens in response
temperature: Creativity (0.0-1.0)
system_prompt: System instructions
Returns:
Dict with generated text and metadata
"""
if not self.api_key:
logger.error("DASHSCOPE_API_KEY not configured")
return {"error": "Alibaba API key not configured"}
if model not in QWEN_MODELS:
return {"error": f"Unknown model: {model}. Available: {list(QWEN_MODELS.keys())}"}
# Build request
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
payload = {
"model": model,
"input": {"messages": messages},
"parameters": {
"max_tokens": max_tokens,
"temperature": temperature,
"result_format": "text",
},
}
try:
session = self._get_session()
response = await session.post(
f"{DASHSCOPE_BASE_URL}/services/aigc/text-generation/generation", json=payload
)
if response.status_code == 200:
result = response.json()
output = result.get("output", {})
return {
"status": "success",
"text": output.get("text", ""),
"model": model,
"usage": output.get("usage", {}),
"prompt": prompt[:100] + "...",
}
else:
logger.error(f"DashScope API error: {response.status_code} - {response.text[:200]}")
return {
"error": f"API error: {response.status_code}",
"details": response.text[:500],
}
except Exception as e:
logger.error(f"DashScope text generation failed: {e}")
return {"error": str(e)}
async def generate_marketing_content(self, content_type: str, topic: str, details: dict | None = None) -> dict:
"""Generate marketing content for specific use cases."""
# Content type templates
templates = {
"blog_post": {
"system": "You are a professional crypto marketing copywriter. Write engaging, informative blog posts.",
"prompt": f"""Write a {details.get("word_count", 600)}-word blog post about: {topic}
Key points to cover:
{chr(10).join(f"- {point}" for point in details.get("key_points", []))}
Tone: Professional but accessible
Include: Call to action at the end
Platform: RMI Intelligence Platform blog
""",
},
"twitter_thread": {
"system": "You are a crypto Twitter expert. Write engaging threads that get shares.",
"prompt": f"""Create a Twitter thread (8-12 tweets) about: {topic}
Key points:
{chr(10).join(f"- {point}" for point in details.get("key_points", []))}
Format:
- Tweet 1: Hook
- Tweets 2-10: Content
- Final tweet: CTA
Include emojis, hashtags, and @cryptorugmunch tag
Max 280 chars per tweet
""",
},
"telegram_post": {
"system": "You write engaging Telegram posts for crypto communities.",
"prompt": f"""Write a Telegram announcement about: {topic}
Key points:
{chr(10).join(f"- {point}" for point in details.get("key_points", []))}
Format:
- Start with emoji headline
- Use **bold** for emphasis
- Include links
- Add relevant hashtags
Tone: Exciting but professional
""",
},
"email_newsletter": {
"system": "You write engaging email newsletters for crypto platforms.",
"prompt": f"""Write an email newsletter about: {topic}
Key points:
{chr(10).join(f"- {point}" for point in details.get("key_points", []))}
Structure:
- Subject line (5 options)
- Opening hook
- Main content
- CTA
- Sign-off
Tone: Friendly, professional, valuable
Length: {details.get("word_count", 400)} words
""",
},
"press_release": {
"system": "You write professional press releases for crypto companies.",
"prompt": f"""Write a press release about: {topic}
Key points:
{chr(10).join(f"- {point}" for point in details.get("key_points", []))}
Format:
- FOR IMMEDIATE RELEASE
- Headline
- Dateline
- Body paragraphs
- About RMI
- Media contact
Tone: Professional, newsworthy
Length: {details.get("word_count", 500)} words
""",
},
"feature_announcement": {
"system": "You write exciting feature announcements for crypto products.",
"prompt": f"""Write a feature announcement for: {topic}
Feature details:
{chr(10).join(f"- {point}" for point in details.get("features", []))}
Benefits:
{chr(10).join(f"- {point}" for point in details.get("benefits", []))}
Include:
- Exciting headline
- What it does
- Why it matters
- How to use it
- CTA
Tone: Exciting, clear, benefit-focused
""",
},
}
template = templates.get(content_type)
if not template:
return {"error": f"Unknown content type: {content_type}"}
# Generate using qwen-plus by default
model = details.get("model", "qwen-plus")
return await self.generate_text(
prompt=template["prompt"],
system_prompt=template["system"],
model=model,
max_tokens=details.get("max_tokens", 1500),
temperature=details.get("temperature", 0.7),
)
async def generate_variations(self, base_content: str, num_variations: int = 5, platform: str = "twitter") -> dict:
"""Generate multiple variations of content."""
prompt = f"""Generate {num_variations} variations of this content for {platform}:
Original:
{base_content}
Requirements:
- Each variation should be unique
- Keep the core message
- Vary the tone slightly (some more excited, some more professional)
- All should be high quality
- Include relevant emojis for {platform}
Output format:
Variation 1: [content]
Variation 2: [content]
...
"""
return await self.generate_text(prompt=prompt, model="qwen-plus", max_tokens=2000, temperature=0.8)
async def summarize_content(self, content: str, summary_type: str = "bullet_points") -> dict:
"""Summarize long content into different formats."""
summary_prompts = {
"bullet_points": "Summarize this into 5-7 key bullet points:",
"twitter_thread": "Convert this into a 5-tweet Twitter thread:",
"one_liner": "Summarize this in one compelling sentence:",
"email_blurb": "Summarize this into a 100-word email blurb:",
}
prompt = f"""{summary_prompts.get(summary_type, "Summarize:")}
{content[:3000]} # Limit input length
"""
return await self.generate_text(prompt=prompt, model="qwen-turbo", max_tokens=500, temperature=0.5)
def list_models(self) -> list[dict]:
"""List available Qwen models."""
return [{"id": model_id, **info} for model_id, info in QWEN_MODELS.items()]
def status(self) -> dict:
"""Check connector status."""
return {
"api_key_configured": bool(self.api_key),
"api_key_prefix": self.api_key[:20] + "..." if self.api_key else "NOT SET",
"base_url": DASHSCOPE_BASE_URL,
"models_available": list(QWEN_MODELS.keys()),
}
# Singleton
_alibaba_dashscope: AlibabaDashScopeConnector | None = None
def get_alibaba_dashscope_connector() -> AlibabaDashScopeConnector:
global _alibaba_dashscope
if _alibaba_dashscope is None:
_alibaba_dashscope = AlibabaDashScopeConnector()
return _alibaba_dashscope