322 lines
9.7 KiB
Python
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
|