2025.10.12故事生成系统(核心API客户端开发)

第2天:核心API客户端开发

📋 第2天概述

在第1天完成环境搭建的基础上,第2天将重点开发核心API客户端,建立与阿里云三大AI服务(文字生成、图片生成、语音合成)的稳定连接和基础功能调用。

🎯 第2天目标

主要任务:实现完整的阿里云API客户端,包含错误处理、重试机制和响应解析
核心需求:构建可复用的API调用模块,支持三大AI服务的稳定调用

🏗️ 架构设计

客户端类设计

class AliyunClient:
    """阿里云AI服务统一客户端"""
    
    def __init__(self, config):
        self.config = config
        self.session = requests.Session()
        
    # 文字生成相关方法
    def generate_text(self, prompt, **kwargs)
    def _call_text_api(self, payload)
    
    # 图片生成相关方法
    def generate_image(self, prompt, **kwargs)
    def _call_image_api(self, payload)
    
    # 语音合成相关方法
    def synthesize_speech(self, text, **kwargs)
    def _generate_nls_token(self)
    def _call_speech_api(self, payload)
    
    # 通用工具方法
    def _get_headers(self, service_type)
    def _handle_response(self, response)
    def _retry_request(self, func, max_retries=3)

🔧 具体实施步骤

步骤1:创建核心客户端类

创建 src/core/aliyun_client.py

import requests
import json
import time
import base64
import hmac
import hashlib
import urllib.parse
from datetime import datetime
from typing import Dict, Any, Optional

class AliyunClient:
    """
    阿里云百炼大模型API客户端
    用于封装与阿里云API的交互
    """
    
    def __init__(self, config):
        self.config = config
        self.session = requests.Session()
        
        # 设置请求超时和重试策略
        self.session.mount('http://', requests.adapters.HTTPAdapter(max_retries=3))
        self.session.mount('https://', requests.adapters.HTTPAdapter(max_retries=3))
    
    def _get_headers(self, service_type: str) -> Dict[str, str]:
        """获取不同服务的请求头"""
        base_headers = {
            "Content-Type": "application/json"
        }
        
        if service_type == "text":
            base_headers["Authorization"] = f"Bearer {self.config.ALIYUN_API_KEY}"
        elif service_type == "image":
            base_headers["Authorization"] = f"Bearer {self.config.ALIYUN_API_KEY}"
        elif service_type == "speech":
            # 语音服务需要动态Token
            token = self._generate_nls_token()
            base_headers["X-NLS-Token"] = token
            
        return base_headers
    
    def _generate_nls_token(self) -> str:
        """生成智能语音交互API的访问令牌"""
        if not self.config.ALIYUN_ACCESS_KEY_ID or not self.config.ALIYUN_ACCESS_KEY_SECRET:
            raise ValueError("缺少AccessKey配置,无法生成Token")
        
        # 阿里云智能语音交互Token生成API
        token_url = f"{self.config.TOKEN_SERVICE_URL}/pop/2018-05-18/tokens"
        
        # 构建请求参数
        params = {
            "Action": "CreateToken"
        }
        
        # 添加阿里云POP协议要求的公共参数
        timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
        params.update({
            "Format": "JSON",
            "Version": "2019-02-28",
            "AccessKeyId": self.config.ALIYUN_ACCESS_KEY_ID,
            "SignatureMethod": "HMAC-SHA1",
            "SignatureVersion": "1.0",
            "SignatureNonce": str(int(datetime.utcnow().timestamp() * 1000)),
            "Timestamp": timestamp
        })
        
        # 对参数进行排序
        sorted_params = sorted(params.items())
        
        # 构建规范化请求字符串
        canonicalized_query_string = '&'.join([
            f"{urllib.parse.quote(k)}={urllib.parse.quote(v)}" for k, v in sorted_params
        ])
        
        # 构建签名字符串
        string_to_sign = "POST&%2F&" + urllib.parse.quote(canonicalized_query_string)
        
        # 计算签名
        key = self.config.ALIYUN_ACCESS_KEY_SECRET + "&"
        signature = base64.b64encode(
            hmac.new(key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1).digest()
        ).decode('utf-8')
        
        # 添加签名到参数
        params["Signature"] = signature
        
        # 构建完整URL
        query_string = '&'.join([f"{k}={urllib.parse.quote(v)}" for k, v in params.items()])
        full_url = f"{token_url}?{query_string}"
        
        try:
            # 发送请求获取Token
            response = self.session.post(full_url, timeout=10)
            
            if response.status_code == 200:
                result = response.json()
                if "Token" in result and "Id" in result["Token"]:
                    return result["Token"]["Id"]
                else:
                    raise ValueError(f"Token响应格式错误: {result}")
            else:
                raise ValueError(f"Token API请求失败: {response.status_code} - {response.text}")
                
        except Exception as e:
            raise Exception(f"Token生成失败: {e}")
    
    def _retry_request(self, func, max_retries: int = 3, delay: float = 1.0) -> Any:
        """重试机制实现"""
        for attempt in range(max_retries):
            try:
                return func()
            except Exception as e:
                if attempt == max_retries - 1:
                    raise e
                time.sleep(delay * (2 ** attempt))  # 指数退避
    
    def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
        """统一处理API响应"""
        if response.status_code != 200:
            raise Exception(f"API请求失败: {response.status_code} - {response.text}")
        
        try:
            return response.json()
        except json.JSONDecodeError:
            raise Exception(f"响应解析失败: {response.text}")

步骤2:实现文字生成功能

def generate_text(self, prompt: str, model: str = "qwen-max", temperature: float = 0.7, 
                 max_tokens: int = 1000, **kwargs) -> Dict[str, Any]:
    """
    调用文本生成API
    
    Args:
        prompt: 提示词
        model: 模型名称
        temperature: 生成温度
        max_tokens: 最大生成长度
        **kwargs: 其他参数
    
    Returns:
        dict: API响应结果
    """
    
    def _call_api():
        headers = self._get_headers("text")
        
        # 兼容模式对话API使用OpenAI兼容格式
        payload = {
            "model": model,
            "messages": [{
                "role": "user",
                "content": prompt
            }],
            "temperature": temperature,
            "max_tokens": max_tokens
        }
        
        # 添加额外参数
        payload.update(kwargs)
        
        response = self.session.post(
            self.config.TEXT_GENERATION_URL,
            headers=headers,
            json=payload,
            timeout=30
        )
        
        return self._handle_response(response)
    
    return self._retry_request(_call_api)

步骤3:实现图片生成功能

def generate_image(self, prompt: str, model: str = "wan2.5-t2i-preview", 
                  size: str = "1024x1024", style: str = "cartoon", n: int = 1, **kwargs) -> Dict[str, Any]:
    """
    调用图像生成API
    
    Args:
        prompt: 提示词
        model: 模型名称
        size: 图片尺寸
        style: 图片风格
        n: 生成数量
        **kwargs: 其他参数
    
    Returns:
        dict: API响应结果
    """
    
    def _call_api():
        headers = self._get_headers("image")
        
        payload = {
            "model": model,
            "input": {
                "prompt": prompt
            },
            "parameters": {
                "size": size,
                "style": style,
                "n": n
            }
        }
        
        # 添加额外参数
        payload["parameters"].update(kwargs)
        
        response = self.session.post(
            self.config.IMAGE_GENERATION_URL,
            headers=headers,
            json=payload,
            timeout=60  # 图片生成需要更长时间
        )
        
        return self._handle_response(response)
    
    return self._retry_request(_call_api)

步骤4:实现语音合成功能

def synthesize_speech(self, text: str, voice: str = "xiaoyun", format: str = "wav", 
                     sample_rate: int = 16000, volume: int = 50, **kwargs) -> Dict[str, Any]:
    """
    调用语音合成API
    
    Args:
        text: 要合成的文本
        voice: 语音类型
        format: 音频格式
        sample_rate: 采样率
        volume: 音量
        **kwargs: 其他参数
    
    Returns:
        dict: API响应结果
    """
    
    def _call_api():
        headers = self._get_headers("speech")
        
        payload = {
            "appkey": self.config.SPEECH_APP_KEY,
            "text": text,
            "voice": voice,
            "format": format,
            "sample_rate": sample_rate,
            "volume": volume
        }
        
        # 添加额外参数
        payload.update(kwargs)
        
        response = self.session.post(
            self.config.SPEECH_SYNTHESIS_URL,
            headers=headers,
            json=payload,
            timeout=30
        )
        
        return self._handle_response(response)
    
    return self._retry_request(_call_api)

步骤5:创建响应解析工具

def parse_text_response(self, response: Dict[str, Any]) -> str:
    """解析文本生成响应"""
    # 尝试从不同的可能路径提取文本
    generated_text = ""
    
    # 路径1: result.choices[0].message.content (OpenAI格式)
    if 'choices' in response and len(response['choices']) > 0:
        choice = response['choices'][0]
        if 'message' in choice and 'content' in choice['message']:
            generated_text = choice['message']['content']
    
    # 路径2: result.output.text (阿里云格式)
    elif 'output' in response and 'text' in response['output']:
        generated_text = response['output']['text']
    
    # 路径3: result.content (简化格式)
    elif 'content' in response:
        generated_text = response['content']
    
    # 路径4: result.text (直接格式)
    elif 'text' in response:
        generated_text = response['text']
    
    if not generated_text:
        raise ValueError("无法从响应中提取文本内容")
    
    return generated_text

def parse_image_response(self, response: Dict[str, Any]) -> str:
    """解析图片生成响应"""
    if 'output' in response and 'task_id' in response['output']:
        return response['output']['task_id']
    elif 'data' in response and len(response['data']) > 0:
        return response['data'][0].get('url', '')
    else:
        raise ValueError("无法从响应中提取图片信息")

def parse_speech_response(self, response: Dict[str, Any]) -> bytes:
    """解析语音合成响应"""
    if 'audio' in response:
        # 返回base64编码的音频数据
        return base64.b64decode(response['audio'])
    else:
        raise ValueError("无法从响应中提取音频数据")

🧪 测试用例

创建 tests/test_aliyun_client.py

import unittest
from src.core.aliyun_client import AliyunClient
from src.core.config import Config

class TestAliyunClient(unittest.TestCase):
    
    def setUp(self):
        self.config = Config()
        self.client = AliyunClient(self.config)
    
    def test_text_generation(self):
        """测试文字生成功能"""
        try:
            response = self.client.generate_text("写一个简短的测试故事")
            self.assertIsInstance(response, dict)
            text = self.client.parse_text_response(response)
            self.assertIsInstance(text, str)
            self.assertTrue(len(text) > 0)
            print(f"✅ 文字生成测试通过,生成文本长度: {len(text)}")
        except Exception as e:
            print(f"⚠️ 文字生成测试跳过(API可能不可用): {e}")
    
    def test_image_generation(self):
        """测试图片生成功能"""
        try:
            response = self.client.generate_image("一只可爱的小猫")
            self.assertIsInstance(response, dict)
            task_id = self.client.parse_image_response(response)
            self.assertIsInstance(task_id, str)
            print(f"✅ 图片生成测试通过,任务ID: {task_id}")
        except Exception as e:
            print(f"⚠️ 图片生成测试跳过(API可能不可用): {e}")
    
    def test_speech_synthesis(self):
        """测试语音合成功能"""
        try:
            response = self.client.synthesize_speech("这是一个测试语音")
            self.assertIsInstance(response, dict)
            audio_data = self.client.parse_speech_response(response)
            self.assertIsInstance(audio_data, bytes)
            print(f"✅ 语音合成测试通过,音频数据长度: {len(audio_data)}")
        except Exception as e:
            print(f"⚠️ 语音合成测试跳过(API可能不可用): {e}")

if __name__ == "__main__":
    unittest.main()

📊 第2天成果

完成内容

  1. ✅ 核心API客户端类实现
  2. ✅ 三大AI服务功能封装
  3. ✅ 错误处理和重试机制
  4. ✅ 响应解析工具
  5. ✅ 完整的测试用例

技术要点

  • 统一的请求头管理
  • 智能Token生成机制
  • 指数退避重试策略
  • 多格式响应解析
  • 完整的异常处理

🔄 明日计划

第3天将基于今天的API客户端,实现文字生成服务的具体业务逻辑和用户界面。


文档创建时间:2024年开发计划第2天
预计耗时:6-8小时
关键文件:src/core/aliyun_client.py, tests/test_aliyun_client.py

posted @ 2025-12-28 23:04  ysd666  阅读(13)  评论(0)    收藏  举报