使用 FastAPI 实现聊天完成 API 详解

使用 FastAPI 实现聊天完成 API 详解

    • 简介
    • 基础概念
      • FastAPI
      • Pydantic
      • PyTorch
    • 代码详解
      • 1. 定义 API 端点
      • 2. 请求验证
      • 3. 生成参数字典
      • 4. 处理流式响应
      • 5. 工具调用处理
      • 6. 非流式响应处理
      • 7. 处理使用信息和工具调用
      • 8. 构建聊天消息
      • 9. 构建响应选择
      • 10. 更新使用信息
      • 11. 返回最终响应
    • 总结
      • 示例代码

简介

在这篇博客中,我们将详细解释一段使用 FastAPI 构建的聊天完成 API 代码。这段代码实现了一个 POST 请求的 API 端点,用于处理聊天消息并生成响应。我们将逐行解析代码,并提供必要的背景知识和示例代码。

基础概念

FastAPI

FastAPI 是一个用于构建 API 的现代、快速(高性能)的 Web 框架,基于 Python 3.6+。它使用类型提示来自动生成文档和验证请求数据。

Pydantic

Pydantic 是一个用于数据验证和设置管理的库,常与 FastAPI 一起使用。它通过 Python 的类型提示来定义数据模型,并自动验证输入数据的类型和格式。

PyTorch

PyTorch 是一个开源的深度学习框架,广泛用于研究和生产环境。它提供了灵活的张量计算和自动求导功能。

代码详解

下面是完整的代码,我们将逐段进行解释。

@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
async def create_chat_completion(request: ChatCompletionRequest):
    if len(request.messages) < 1 or request.messages[-1].role == "assistant":
        raise HTTPException(status_code=400, detail="Invalid request")

    gen_params = dict(
        messages=request.messages,
        temperature=request.temperature,
        top_p=request.top_p,
        max_tokens=request.max_tokens or 1024,
        echo=False,
        stream=request.stream,
        repetition_penalty=request.repetition_penalty,
        tools=request.tools,
        tool_choice=request.tool_choice,
    )
    logger.debug(f"==== request ====\n{gen_params}")

    if request.stream:
        predict_stream_generator = predict_stream(request.model, gen_params)
        output = await anext(predict_stream_generator)
        if output:
            return EventSourceResponse(predict_stream_generator, media_type="text/event-stream")
        logger.debug(f"First result output:\n{output}")

        function_call = None
        if output and request.tools:
            try:
                function_call = process_response(output, request.tools, use_tool=True)
            except:
                logger.warning("Failed to parse tool call")

        if isinstance(function_call, dict):
            function_call = ChoiceDeltaToolCallFunction(**function_call)
            generate = parse_output_text(request.model, output, function_call=function_call)
            return EventSourceResponse(generate, media_type="text/event-stream")
        else:
            return EventSourceResponse(predict_stream_generator, media_type="text/event-stream")
    response = ""
    async for response in generate_stream_glm4(gen_params):
        pass

    if response["text"].startswith("\n"):
        response["text"] = response["text"][1:]
    response["text"] = response["text"].strip()

    usage = UsageInfo()

    function_call, finish_reason = None, "stop"
    tool_calls = None
    if request.tools:
        try:
            function_call = process_response(response["text"], request.tools, use_tool=True)
        except Exception as e:
            logger.warning(f"Failed to parse tool call: {e}")
    if isinstance(function_call, dict):
        finish_reason = "tool_calls"
        function_call_response = ChoiceDeltaToolCallFunction(**function_call)
        function_call_instance = FunctionCall(
            name=function_call_response.name,
            arguments=function_call_response.arguments
        )
        tool_calls = [
            ChatCompletionMessageToolCall(
                id=generate_id('call_', 24),
                function=function_call_instance,
                type="function")]

    message = ChatMessage(
        role="assistant",
        content=None if tool_calls else response["text"],
        function_call=None,
        tool_calls=tool_calls,
    )

    logger.debug(f"==== message ====\n{message}")

    choice_data = ChatCompletionResponseChoice(
        index=0,
        message=message,
        finish_reason=finish_reason,
    )
    task_usage = UsageInfo.model_validate(response["usage"])
    for usage_key, usage_value in task_usage.model_dump().items():
        setattr(usage, usage_key, getattr(usage, usage_key) + usage_value)

    return ChatCompletionResponse(
        model=request.model,
        choices=[choice_data],
        object="chat.completion",
        usage=usage
    )

1. 定义 API 端点

@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
async def create_chat_completion(request: ChatCompletionRequest):

这行代码定义了一个 POST 请求的 API 端点 /v1/chat/completions,并指定了请求的响应模型为 ChatCompletionResponsecreate_chat_completion 函数将处理传入的 ChatCompletionRequest 请求。

2. 请求验证

    if len(request.messages) < 1 or request.messages[-1].role == "assistant":
        raise HTTPException(status_code=400, detail="Invalid request")

这里我们进行请求验证:

  • 检查 messages 列表的长度是否小于 1。
  • 检查最后一条消息的角色是否为 “assistant”。

如果以上任一条件为真,则抛出 HTTP 400 错误。

3. 生成参数字典

    gen_params = dict(
        messages=request.messages,
        temperature=request.temperature,
        top_p=request.top_p,
        max_tokens=request.max_tokens or 1024,
        echo=False,
        stream=request.stream,
        repetition_penalty=request.repetition_penalty,
        tools=request.tools,
        tool_choice=request.tool_choice,
    )
    logger.debug(f"==== request ====\n{gen_params}")

将请求中的参数转化为一个字典 gen_params,用于后续的生成操作。同时,记录调试信息。

4. 处理流式响应

    if request.stream:
        predict_stream_generator = predict_stream(request.model, gen_params)
        output = await anext(predict_stream_generator)
        if output:
            return EventSourceResponse(predict_stream_generator, media_type="text/event-stream")
        logger.debug(f"First result output:\n{output}")

如果请求中指定了流式响应,则调用 predict_stream 函数生成流式响应生成器,并返回 EventSourceResponse。如果第一个输出存在,则直接返回生成器作为事件流响应。

5. 工具调用处理

        function_call = None
        if output and request.tools:
            try:
                function_call = process_response(output, request.tools, use_tool=True)
            except:
                logger.warning("Failed to parse tool call")

        if isinstance(function_call, dict):
            function_call = ChoiceDeltaToolCallFunction(**function_call)
            generate = parse_output_text(request.model, output, function_call=function_call)
            return EventSourceResponse(generate, media_type="text/event-stream")
        else:
            return EventSourceResponse(predict_stream_generator, media_type="text/event-stream")

如果输出存在且请求包含工具调用,则尝试解析工具调用。如果解析成功,则处理工具调用并生成新的事件流响应;否则,继续返回原始的事件流生成器。

6. 非流式响应处理

    response = ""
    async for response in generate_stream_glm4(gen_params):
        pass

    if response["text"].startswith("\n"):
        response["text"] = response["text"][1:]
    response["text"] = response["text"].strip()

如果请求未指定流式响应,则调用 generate_stream_glm4 生成响应。在生成响应后,去掉开头的换行符并修剪两端空白。

7. 处理使用信息和工具调用

    usage = UsageInfo()

    function_call, finish_reason = None, "stop"
    tool_calls = None
    if request.tools:
        try:
            function_call = process_response(response["text"], request.tools, use_tool=True)
        except Exception as e:
            logger.warning(f"Failed to parse tool call: {e}")
    if isinstance(function_call, dict):
        finish_reason = "tool_calls"
        function_call_response = ChoiceDeltaToolCallFunction(**function_call)
        function_call_instance = FunctionCall(
            name=function_call_response.name,
            arguments=function_call_response.arguments
        )
        tool_calls = [
            ChatCompletionMessageToolCall(
                id=generate_id('call_', 24),
                function=function_call_instance,
                type="function")]

在处理响应后,创建 UsageInfo 实例并检查是否有工具调用。如果有工具调用,则解析并生成工具调用响应。

8. 构建聊天消息

    message = ChatMessage(
        role="assistant",
        content=None if tool_calls else response["text"],
        function_call=None,
        tool_calls=tool_calls,
    )

根据生成的响应和工具调用信息,创建一个 ChatMessage 实例。

9. 构建响应选择

    logger.debug(f"==== message ====\n{message}")

    choice_data = ChatCompletionResponseChoice(
        index=0,
        message=message,
        finish_reason=finish_reason,
    )

这段代码将生成的 ChatMessage 实例记录到日志中,并且创建一个 ChatCompletionResponseChoice 实例,其中包含了消息的索引、消息内容和完成原因。

10. 更新使用信息

    task_usage = UsageInfo.model_validate(response["usage"])
    for usage_key, usage_value in task_usage.model_dump().items():
        setattr(usage, usage_key, getattr(usage, usage_key) + usage_value)

从响应中提取使用信息,并将其添加到 usage 实例中。UsageInfo.model_validate 方法用于验证并创建一个包含使用信息的实例。

11. 返回最终响应

    return ChatCompletionResponse(
        model=request.model,
        choices=[choice_data],
        object="chat.completion",
        usage=usage
    )

最后,创建并返回一个 ChatCompletionResponse 实例,其中包含了模型名称、选项列表和使用信息。

总结

通过这篇博客,我们详细解析了一个基于 FastAPI 实现的聊天完成 API 的代码。我们逐行解释了代码的功能,并介绍了相关的基础概念和库。

示例代码

为了帮助理解,我们提供一个简化版的示例代码,用于实现类似的聊天完成 API:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List

app = FastAPI()

class ChatMessage(BaseModel):
    role: str
    content: str

class ChatCompletionRequest(BaseModel):
    messages: List[ChatMessage]
    temperature: float
    max_tokens: int

class ChatCompletionResponse(BaseModel):
    message: ChatMessage

@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
async def create_chat_completion(request: ChatCompletionRequest):
    if len(request.messages) < 1 or request.messages[-1].role == "assistant":
        raise HTTPException(status_code=400, detail="Invalid request")

    # Simplified response generation logic
    response_text = "This is a response."
    response_message = ChatMessage(role="assistant", content=response_text)
    
    return ChatCompletionResponse(message=response_message)

这段简化代码定义了一个基本的聊天完成 API 端点,处理请求并返回简单的响应。通过这个示例,可以更好地理解完整代码的工作原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/758366.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【proteus经典实战】16X192点阵程序

一、简介 6X192点阵程序通常用于表示高分辨率图像或文字&#xff0c;其中16X表示像素阵列的宽度&#xff0c;192表示每个像素阵列中的点阵数&#xff0c;16X192点阵程序需要一定的编程知识和技能才能编写和调试&#xff0c;同时还需要考虑硬件设备的兼容性和性能等因素。 初始…

玩游戏就能学习亚马逊云科技AWS技术并通过热门技术认证考试??

亚马逊AWS限时活动&#xff0c;玩免费游戏Cloud Quest Practitioner送AWS云从业证书考试25%折扣券(价值171元)&#xff0c;玩游戏的同时还能学知识一举两得。Cloud Quest是AWS出的一款3D角色扮演游戏/虚拟城市建造形式的实验课程(游戏画面有点像天际线)&#xff0c;大家通过完成…

FPV穿越机集群控制技术详解

随着无人机技术的不断发展&#xff0c;FPV&#xff08;First Person View&#xff0c;第一人称视角&#xff09;穿越机在娱乐、航拍、搜索与救援等领域的应用日益广泛。FPV穿越机集群控制技术则是这一领域的热点研究方向&#xff0c;旨在通过协同控制多个穿越机实现更高效、更复…

Efficient Unified Demosaicing for Bayer and Non-Bayer Patterned Image Sensors

这篇文章是 2023 ICCV 的一篇文章&#xff0c;主要介绍一套统一的去马赛克的算法框架的 由于手机 Camera 上 CMOS 的单个 pixel size 比较小&#xff0c;所以现在很多手机的 Camera CMOS 会采用一些独特的非 Bayer 模式的 CFA (Quad, Nona 以及 Q X Q) 等&#xff0c;这类非 B…

【Linux】已解决:Ubuntu虚拟机安装Java/JDK

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项结论 已解决&#xff1a;Ubuntu虚拟机安装Java/JDK 一、分析问题背景 在Ubuntu虚拟机上安装Java开发工具包&#xff08;JDK&#xff09;是许多开发者的常见任务。然而&#xff0c;在…

STM32音频应用开发:DMA与定时器的高效协作

摘要: 本文章将深入浅出地介绍如何使用STM32单片机实现音频播放功能。文章将从音频基础知识入手&#xff0c;逐步讲解音频解码、DAC转换、音频放大等关键环节&#xff0c;并结合STM32 HAL库给出具体的代码实现和电路设计方案。最后&#xff0c;我们将通过一个实例演示如何播放W…

! Warning: `flutter` on your path resolves to

目录 项目场景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; 1. 检查并更新.bash_profile或.zshrc文件 2.添加Flutter路径到环境变量 3. 加载配置文件 4.验证Flutter路径 5.重新启动终端 项目场景&#xff1a; 今天重新安装了AndroidStudio,并配置…

zdppy_api+vue3实现前后端分离的登录功能

实现思路 1、准备zdppy的开发环境 2、使用amauth提供的低代码接口&#xff0c;直接生成login登录接口 3、使用之前开发的登录模板渲染登录界面 4、给登录按钮绑定点击事件 5、给用户名和密码的输入框双向绑定数据 6、使用axios在登录按钮点击的时候&#xff0c;携带用户数据发…

Linux部署wordpress站点

先安装宝塔面板 yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec 因为wordpress需要php&#xff0c;mysql&#xff0c;apache &#xff0c;httpd环境 参考&#xff1a;Linux 安装宝塔…

【学习】使用PyTorch训练与评估自己的ResNet网络教程

参考&#xff1a;保姆级使用PyTorch训练与评估自己的ResNet网络教程_训练自己的图像分类网络resnet101 pytorch-CSDN博客 项目地址&#xff1a;GitHub - Fafa-DL/Awesome-Backbones: Integrate deep learning models for image classification | Backbone learning/comparison…

HBase Shell命令详解

HBase Shell命令 一、 命名空间 命名空间是 HBase 中用于组织表的一种逻辑容器&#xff0c;类似于文件系统中的文件夹。 Namespace允许用户在 HBase 中更好地管理和组织表&#xff0c;以及提供了隔离和命名约定。 1. 创建命名空间 命令&#xff1a; create_namespace name…

【scrapy】1.scrapy爬虫入门

一、scrapy爬虫框架 Scrapy 框架是一个基于Twisted的一个异步处理爬虫框架&#xff0c;应用范围非常的广泛&#xff0c;常用于数据采集、网络监测&#xff0c;以及自动化测试等。 scrapy框架包括5个主要的组件&#xff1a; Scheduler&#xff1a;事件调度器&#xff0c;它负…

机器学习引领教育革命:智能教育的新时代

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f4d2;1. 引言&#x1f4d9;2. 机器学习在教育中的应用&#x1f31e;个性化学习&#x1f319;评估与反馈的智能化⭐教学资源的优…

Lua: 轻量级多用途脚本语言

Lua 是一种高效而轻量级的脚本语言&#xff0c;具备强大的扩展性和灵活性&#xff0c;广泛应用于游戏开发、嵌入式系统、Web 应用等多个领域。本文将深入探讨 Lua 的特性、应用场景以及如何使用 Lua 进行开发。 1. Lua 的起源与发展 Lua 的发展始于上世纪90年代初&#xff0c;…

Java单体架构项目_云霄外卖-特殊点

项目介绍&#xff1a; 定位&#xff1a; 专门为餐饮企业&#xff08;餐厅、饭店&#xff09;定制的一款软件商品 分为&#xff1a; 管理端&#xff1a;外卖商家使用 用户端&#xff08;微信小程序&#xff09;&#xff1a;点餐用户使用。 功能架构&#xff1a; &#xff08…

哎呀呀 又迟到了,还被抓住了,面面相觑 害怕

网络编程 我应该迟点来&#xff0c;唠嗑到35嘿嘿 心疼自己早起呜呜呜&#xff0c;幸运的是35开讲 计算机网络 分4层 应用层&#xff08;Application Layer&#xff09;&#xff1a; 应用层是用户接口和网络应用程序的接口。它允许用户访问网络服务&#xff0c;并支持各种应用程…

Windows系统下文件夹权限详解

文章目录 问题描述文件夹属性 问题描述 今天在Win10系统下&#xff0c;实现文件夹设置权限&#xff0c;具体的方案的涉及到我们公司内部的一款加密软件&#xff0c;不太方便透漏&#xff0c;借此机会&#xff0c;我也重新的回顾下windows系统下的文件夹权限 文件夹属性 打开…

[C++][设计模式][中介者模式]详细讲解

目录 1.动机2.模式定义3.要点总结 1.动机 在软件构建过程中&#xff0c;经常会出现多个对象相互关联的情况&#xff0c;对象之间常常会维持一种复杂的引用关系&#xff0c;如果遇到一些需求的更改&#xff0c;这种直接的引用关系将面临不断的变化在这种情况下&#xff0c;可以…

【小沐学AI】Python实现语音识别(whisper+HuggingFace)

文章目录 1、简介1.1 whisper 2、HuggingFace2.1 安装transformers2.2 Pipeline 简介2.3 Tasks 简介2.3.1 sentiment-analysis2.3.2 zero-shot-classification2.3.3 text-generation2.3.4 fill-mask2.3.5 ner2.3.6 question-answering2.3.7 summarization2.3.8 translation 3、…

PyTorch Tensor进阶操作指南(二):深度学习中的关键技巧

本文主要讲tensor的裁剪、索引、降维和增维 Tensor与numpy互转、Tensor运算等&#xff0c;请看这篇文章 目录 9.1、首先看torch.squeeze()函数&#xff1a; 示例9.1&#xff1a;&#xff08;基本的使用&#xff09; 小技巧1&#xff1a;如何看维数 示例9.2&#xff1a;&a…