AI摘要API接口接入实战教程,避坑指南攻略(附代码)

AI摘要API接口接入实战教程,避坑指南攻略(附代码)

上周产品说文章列表的摘要别再截前30个字了,我花了一个下午接AI摘要API,中间踩了几个坑。今天把接入方案和踩过的坑一起摊开讲,你照着抄就能用。


一、接入之前,先说几个你该知道的事实

这篇文章是我以云策API的AI摘要接口为例接入实践攻略。进入技术细节之前,有几件事得先交代。

平台背景:官网首页显示,平台成立于2022年,自称累计服务超过10000+开发者,日均调用量突破100万次。但运营主体是谁、有没有企业资质、后端对接的是哪个大模型——是自研还是套壳调用其他API——这些信息在官网和公开渠道都没找到,不确定。

SSL证书已过期:截至本文写作时,api.auth.top 的SSL证书已经过期,浏览器进行访问的时候会直接弹出安全警告,不手动跳过的话根本打不开官网。对于一个API服务平台来说,SSL证书过期不是小事——它直接影响数据传输的安全性。你决定接入之前,先确认证书是否已经续上。

隐私政策:隐私政策页面是存在的,不过内容却十分模板化,存在“我们可能收集以下类型的个人信息”以及“我们使用收集的信息用于”这类章节标题,而在标题下面几乎没有给出具体的说明。像你的文本数据传送过去之后会不会被存储、会不会被用于模型训练这些关键问题,都没有给出明确的回答。

计费模式:这个网站是完全免费的API网站,我已经注册使用,你可以自行去注册,然后自己在控制台查看就知道我说的是真是假。

以下方案的核心逻辑——校验、重试、超时、异步并发以及异步并发这些环节,并不依赖特定的平台特性,换成其他的AI摘要API同样可以正常使用。

83b5009e0420260518114205

 

 


二、截前30个字,真的不行

我们网站的文章列表当中,摘要一直是粗暴截取正文的前30个字。直到有一天我自己去刷列表的时候,看到一篇深度测评的摘要显示的是“近年来随着智能手机市场的快速发展”。

这到底是什么呢?用户看了之后就好像没看一样。

我后来专门留意了一下,列表页用户平均停留还不到3秒,也就是在3秒之内就会决定点不点进去。要是给他一段毫无信息量的开头废话,那他凭什么会点进去呢?

也有不少人运用TextRank来开展抽取式摘要的工作,也就是从原文当中挑选出权重较高的句子再拼接起来。这样的做法听起来比较靠谱,但拼接出来的内容读起来就像是缝合怪,上下文会断成两截。这是因为它本质上就是一个“搬运工”,没办法重新组织语言,更没法把散落在不同段落当中的关键信息串联起来。

生成式摘要就不一样了。大模型会先读懂全文的语义,再用自己的话来概括一遍。整体逻辑通顺、信息密度高,同时噪音也比较少。云策API的AI摘要接口走的也就是这条路线。


三、说清楚:这个接口能做什么,不能做什么

核心信息:

  • 接口地址https://api.auth.top/api/aizy
  • 请求方式:POST
  • 认证方式:Header里传 Authorization: Bearer YOUR_API_KEY
  • 请求参数:就一个——text
  • QPS上限:30次/秒
  • 平均响应时间:约801ms

返回极简:

{
  "code": 200,
  "msg": "总结成功",
  "data": "AI生成的摘要内容"
}

参数表就一个text——没有temperature、没有top_p、没有max_tokens。

这得两面看。好处是你不用研究参数怎么调,丢文本进去就能拿到摘要,接入速度很快。坏处是你没法控制摘要长度,也没法指定风格,比如”用三句话概括”或者”面向专业读者”。

要是你的业务只需要一段通顺的摘要,那这个极简设计刚好可以满足使用。要是你需要对输出进行精确控制,那么有两个思路可以选用:一是在应用层开展二次加工工作,比如借助规则把过长的摘要进行截断,或者再调用一次接口来对摘要做压缩处理;二是换用一个支持更多参数的接口。

直白来说,它适宜用于快速验证以及简单的场景当中,要是正式上线并且对摘要有着精细的要求,那就得自己补充一层逻辑。

ea571676ce20260518114354

 


四、拿Key和存Key,别偷懒

去云策API官网注册,在控制台生成API Key。

拿到Key之后——别硬编码在代码里。万一代码上传到GitHub或者被误推到公开仓库,Key就裸奔了。

放到环境变量里,而且变量名别用太泛的 API_KEY,容易跟其他服务的Key冲突:

# Linux/macOS
export YUNCE_API_KEY="your_api_key_here"

# Windows PowerShell
$env:YUNCE_API_KEY="your_api_key_here"

我们可以借助 os.environ.get(“YUNCE_API_KEY”) 来完成代码里的读取操作。要是需要修改Key的话,只需要更改一个环境变量就可以,不用去改动代码然后重新部署。需要留意的是,修改环境变量之后,通常都要重启应用进程才能让它生效;如果是容器化部署的情况,那就需要重建容器,这样新的Key才会发挥作用。不过不管怎样,至少不用去改动代码,也不用走CI/CD流程。


五、生产级封装:同步版

我最开始写了一个只有5行的极简版本,顺利跑通了接口,但在上线之前发现缺少了校验、重试以及超时控制,裸写的代码根本就扛不住实际的运行压力。比如说有一次传输了一批文章,其中有一篇的内容是空的,接口返回了错误但代码没有去处理,直接抛出了异常,而调用方也没有进行try-catch,结果整个脚本直接停了。下面就是我迭代之后的版本,把该补上的内容都给补上了。

import os
import time
import random
import requests

class SyncAISummaryClient:
    """云策API AI摘要客户端 - 同步版"""
    
    def __init__(self, api_key=None):
        self.api_key = api_key or os.environ.get("YUNCE_API_KEY")
        if not self.api_key:
            raise ValueError("API Key没配置!设置环境变量YUNCE_API_KEY或传入api_key参数")
        self.url = "https://api.auth.top/api/aizy"
        self.max_retries = 3
        self.timeout = 15  # 秒
    
    def summarize(self, text, retry_count=0):
        """生成AI摘要,返回结构化结果"""
        
        if not text or not isinstance(text, str):
            return {"success": False, "error_type": "invalid_input", "message": "text不能为空,且必须是字符串"}
        if len(text.strip()) < 10:
            return {"success": False, "error_type": "invalid_input", "message": "文本太短了,少于10个字没法生成有意义的摘要"}
        
        headers = {"Authorization": f"Bearer {self.api_key}"}
        payload = {"text": text}
        # 注意:日志中不要打印text内容,避免敏感信息泄露
        
        try:
            resp = requests.post(
                self.url, headers=headers,
                data=payload, timeout=self.timeout
            )
            resp.raise_for_status()
            result = resp.json()
            
            if result.get("code") == 200:
                return {"success": True, "data": result["data"]}
            else:
                return {
                    "success": False,
                    "error_type": "api_error",
                    "message": f"API返回错误: {result.get('msg', '未知错误')}"
                }
                
        except requests.exceptions.Timeout:
            if retry_count < self.max_retries:
                # 指数退避 + 随机抖动,避免多客户端同时重试形成惊群效应
                wait = (2 ** retry_count) + random.uniform(0, 1)
                print(f"请求超时,{wait:.1f}秒后第{retry_count+1}次重试...")
                time.sleep(wait)
                return self.summarize(text, retry_count + 1)
            return {
                "success": False,
                "error_type": "timeout",
                "message": "请求多次超时,请检查网络或稍后重试"
            }
            
        except requests.exceptions.RequestException as e:
            return {
                "success": False,
                "error_type": "network_error",
                "message": f"网络请求失败: {str(e)}"
            }

使用:

client = SyncAISummaryClient()
result = client.summarize("你的长文本内容...")

if result["success"]:
    print(f"摘要: {result['data']}")
else:
    print(f"出错了[{result['error_type']}]: {result['message']}")

几个设计决策解释一下。

指数退避加随机抖动——超时后不立刻重试,1秒、2秒、4秒逐步拉长间隔。加上 random.uniform(0, 1) 的抖动,是因为如果你部署了多个实例同时超时、同时重试,不加抖动的话它们会在同一时刻涌入,形成惊群效应。这个小细节在生产环境里差别很大。

超时15秒——接口平均响应800ms,但长文本会慢。我最初设了5秒,结果超500字几乎必超时,调到15秒才稳。

返回结构化结果——统一用 {"success": True/False, "error_type": ..., "message": ...} 的格式,不管参数校验失败、API报错、超时、网络异常,调用方都用同一套逻辑处理:检查 success,根据 error_type 做针对性处理。超时可以重试,API错误可以告警,网络错误可以走降级逻辑。比抛异常让调用方到处try-catch靠谱得多。

182845aceb20260518114355


六、两种认证方式,强烈推荐Header传

官方文档提到了两种认证的方式。上面所使用的是通过Header来传递Bearer Token的方式,另一种则是把密钥放到请求体当中的方式:

payload = {"text": "你的文本", "key": "YOUR_API_KEY"}
response = requests.post("https://api.auth.top/api/aizy", data=payload)

这两种方式都可以使用,但我强烈推荐采用Header的方式。把Key跟业务数据分开之后,代码会变得更加清晰,日志脱敏的操作也会更为方便。要是把Key混在Body当中,哪天在打印日志的时候不小心把整个请求体都打出来了……那就会变得十分尴尬。我之前有个同事把Key写在了配置文件里,而配置文件又被提交到了私有Git仓库,后来仓库被误设为了public,那场面实在是让人不忍回忆。


七、异步版:批量处理的正确姿势

实际业务当中你往往不是去处理一篇文章,而是几百上千篇。同步版本一个一个去跑速度太慢,那就得上异步的方式。

但需要注意,QPS上限是30次/秒。你不能毫无顾忌地开启100个并发往里灌。10个并发是个保守的安全值——以800ms平均响应时间来计算,10并发的理论吞吐约12.5 QPS,但实际网络存在波动、长文本的响应可能从800ms拉长到几秒,10并发留出了足够的余量。我以10并发跑了约500篇文章,没有触发过限流。

异步版同样需要重试机制,不能比同步版矮一截:

import os
import asyncio
import random
import json
import aiohttp

class AsyncAISummaryClient:
    """云策API AI摘要客户端 - 异步版"""
    
    def __init__(self, api_key=None, max_concurrent=10, max_retries=3):
        self.api_key = api_key or os.environ.get("YUNCE_API_KEY")
        if not self.api_key:
            raise ValueError("API Key没配置!设置环境变量YUNCE_API_KEY")
        self.url = "https://api.auth.top/api/aizy"
        self.max_concurrent = max_concurrent
        self.max_retries = max_retries
        self.timeout_seconds = 15
    
    async def summarize_one(self, session, text, sem, retry_count=0):
        """单次异步调用,带重试,返回结构化结果"""
        
        if not text or len(text.strip()) < 10:
            return {"success": False, "error_type": "invalid_input", "message": "文本过短"}
        
        async with sem:
            try:
                async with session.post(
                    self.url,
                    headers={"Authorization": f"Bearer {self.api_key}"},
                    data={"text": text}
                ) as resp:
                    resp.raise_for_status()
                    # HTTP状态异常走 ClientError 分支,业务错误码走 api_error 分支
                    result = await resp.json()
                    if result.get("code") == 200:
                        return {"success": True, "data": result["data"]}
                    return {
                        "success": False,
                        "error_type": "api_error",
                        "message": result.get("msg", "未知错误")
                    }
            except asyncio.TimeoutError:
                # ClientTimeout(total=15) 触发的整体超时,走这里
                # 如果你后续加了 sock_read 或 sock_connect 超时,
                # 需要额外捕获 aiohttp.ServerTimeoutError(它是 ClientError 的子类)
                if retry_count < self.max_retries:
                    wait = (2 ** retry_count) + random.uniform(0, 1)
                    await asyncio.sleep(wait)
                    # 当前假设超时是服务端波动导致,复用同一session重试
                    # 如果遇到 aiohttp.ClientConnectionError 或连接池相关错误,
                    # 建议在调用层重建session之后再重试
                    return await self.summarize_one(session, text, sem, retry_count + 1)
                return {
                    "success": False,
                    "error_type": "timeout",
                    "message": "多次超时"
                }
            except (aiohttp.ContentTypeError, json.JSONDecodeError):
                # 上游返回了非JSON响应(比如nginx的502 HTML页面),不是网络错误
                return {
                    "success": False,
                    "error_type": "parse_error",
                    "message": "上游返回了非JSON响应"
                }
            except aiohttp.ClientError as e:
                # 网络层错误:连接断开、DNS解析失败、HTTP状态异常等
                return {
                    "success": False,
                    "error_type": "network_error",
                    "message": str(e)
                }
            except Exception as e:
                return {
                    "success": False,
                    "error_type": "unknown",
                    "message": str(e)
                }
    
    async def batch(self, texts):
        """批量生成摘要"""
        sem = asyncio.Semaphore(self.max_concurrent)
        timeout = aiohttp.ClientTimeout(total=self.timeout_seconds)
        
        async with aiohttp.ClientSession(timeout=timeout) as session:
            tasks = [self.summarize_one(session, t, sem) for t in texts]
            return await asyncio.gather(*tasks)

# 使用
async def main():
    client = AsyncAISummaryClient(max_concurrent=10)
    articles = ["文章1内容...", "文章2内容...", "文章3内容..."]
    results = await client.batch(articles)
    
    for i, r in enumerate(results):
        if r["success"]:
            print(f"文章{i+1}摘要: {r['data']}")
        else:
            print(f"文章{i+1}失败[{r['error_type']}]: {r['message']}")

asyncio.run(main())

跟同步版对齐了:指数退避加随机抖动、结构化返回、参数校验、raise_for_status。异常处理分了四层——asyncio.TimeoutError 是 ClientTimeout(total=15) 触发的整体超时,走重试逻辑;aiohttp.ContentTypeError 和 json.JSONDecodeError 是上游返回了非JSON响应(比如nginx的HTML错误页),归类为 parse_error 而非网络错误,调用方不会误判成自己网络有问题;aiohttp.ClientError 覆盖网络层错误和HTTP状态异常,直接返回;其他未预期的错误单独兜底。

关于重试复用session的问题:当前假设超时是由服务端波动所导致的,复用同一session来进行重试一般不会有问题。

但如果遇到的是aiohttp.ClientConnectionError或是连接池耗尽这类session层面的问题,重试用同一个session可能会继续失败。这种情况建议在调用层重建session之后再进行重试,代码里为了简洁起见没有展开相关内容,生产环境可以根据实际需求进行补充。

9679ccb5a920260518114355

 


八、接到CMS后台:Python FastAPI中间层

实际场景当中,你的后端会去充当中间层,前端不会直接去调用云策API。这样一来Key就不会暴露出来,同时还能够在后端开展缓存以及限流的相关工作。

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import os
import json
import logging

logger = logging.getLogger(__name__)

app = FastAPI()

class SummaryRequest(BaseModel):
    text: str

@app.post("/api/summary")
async def generate_summary(req: SummaryRequest):
    if len(req.text.strip()) < 10:
        raise HTTPException(status_code=400, detail="文本内容过短,至少需要10个字")
    
    api_key = os.environ.get("YUNCE_API_KEY")
    if not api_key:
        raise HTTPException(status_code=500, detail="服务配置错误")
    
    try:
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.post(
                "https://api.auth.top/api/aizy",
                headers={"Authorization": f"Bearer {api_key}"},
                data={"text": req.text}
            )
            resp.raise_for_status()
            
            try:
                result = resp.json()
            except (json.JSONDecodeError, httpx.DecodingError):
                logger.error(f"上游返回非JSON响应, status={resp.status_code}, body={resp.text[:200]}")
                raise HTTPException(status_code=502, detail="上游AI服务返回了非预期响应")
            
            if result.get("code") == 200:
                return {"success": True, "summary": result["data"]}
            else:
                raise HTTPException(
                    status_code=400,
                    detail=f"AI处理失败: {result.get('msg')}"
                )
    except httpx.TimeoutException:
        raise HTTPException(status_code=504, detail="AI服务响应超时,请稍后重试")
    except httpx.HTTPStatusError as e:
        logger.warning(f"上游AI服务返回异常状态: {e.response.status_code}")
        raise HTTPException(status_code=502, detail="上游AI服务异常")
    except httpx.RequestError as e:
        logger.error(f"无法连接AI服务: {str(e)}")
        raise HTTPException(status_code=502, detail="无法连接AI服务")
    except HTTPException:
        raise
    except Exception:
        logger.exception("生成摘要时发生未预期错误")
        raise HTTPException(status_code=500, detail="服务内部错误")

前端调你自己的 /api/summary 就行,Key永远在后端。

异常处理分了多层:JSON解析失败(上游可能返回了HTML错误页)、超时(504)、上游HTTP状态异常(502)、网络连接错误(502)、未预期错误(500+完整日志)。这样出问题你扫一眼日志就知道是云策API挂了、返回了乱七八糟的东西、还是你代码有bug。

这里有一个容易踩的坑:except HTTPException: raise 这行不能省。FastAPI的HTTPException是Exception的子类,如果不单独re-raise,它会被最后的兜底捕获,所有业务异常都变成500。

选FastAPI而不是Flask,是因为它原生支持async,跟httpx搭配顺手。你团队如果已经用Flask了,也没必要为这一个接口换框架。


九、我踩过的四个坑

坑1:文本太长会超时

我试过传一篇8000字的文章,等了14秒才返回。超过1万字直接超时了。单次调用控制在5000字以内比较稳妥。超过的话,先分段再合并——每段生成摘要,最后把各段摘要再喂给接口做一次总摘要。

坑2:摘要质量跟文本类型强相关

新闻、论文这类逻辑清晰的文本,摘要效果很好。但意识流小说、大量对话的剧本,AI也会犯懵。这不是接口的问题,是生成式摘要本身的局限。对于文学性文本,我建议还是人工写摘要。

坑3:别拿它当实时接口用

800ms的响应时间,做后台批处理绰绰有余。但如果你要在用户请求的同步链路里实时生成摘要——用户点进去白屏等一秒,体验很差。正确做法是预生成:文章发布时就把摘要存好,用户看的时候直接读数据库。

坑4:Key泄露了怎么办?

三步走:

  1. 立刻重生成Key——去控制台重新生成,旧Key会立即失效,堵住漏洞。
  2. 检查泄露期间的调用日志——看控制台里有没有异常消耗,确认有没有人用你的Key恶意调用。
  3. 排查泄露原因——是代码仓库公开了?还是日志把请求体打出去了?找到根因才能防止再次发生。

这也是为什么我一直强调Key要放在环境变量当中、不要进行硬编码——真要是出了状况,只需要修改一个环境变量就可以了(修改完成之后记得重启进程,要是采用容器化部署的话则需要重建容器,新的Key才会生效),不需要去修改代码然后重新进行部署。


十、值不值得接?说几句实在话

要是你的产品存在内容展示的场景,比如文章列表、资讯流、知识库当中,那么接入AI摘要大概率是具备价值的。精准的摘要和截取前30个字相比,在用户体验上是质的区别。至于点击率提升多少,我没做过A/B测试,不敢给出具体数字,但逻辑上更清晰的摘要一定比废话截断更容易吸引点击。

SEO方面也会带来正面的影响。生成式摘要天然就可以做到语义通顺,并且包含相关的关键词,把它塞进meta description当中的时候,搜索引擎在理解页面主题时会比截取式摘要更加准确——尤其是现在Google和百度越来越重视语义相关性,而不仅仅是依靠关键词匹配,一段语义完整的摘要比“近年来随着……”这种截断文本对搜索排名更加友好。

技术成本呢?一个POST请求,半天能上线。QPS 30次/秒对中小项目够用。

但有一点需要提醒:这个平台的SSL证书曾经出现过过期的情况,同时隐私政策也不够透明,在接入之前务必先确认好证书的状态以及数据处理政策。要是你的业务确实需要摘要功能,这里面的代码直接拿去进行修改就可以使用,核心逻辑更换成别的AI摘要API也同样适用。在适配你自己的场景的时候,注意调整一下并发数以及超时阈值就可以了。

ddf9c9a45520260518114357


好了,代码拿走,那些可能出现的坑我已经替你提前踩过了。

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容