当前位置:   article > 正文

开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(四)

开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(四)

一、前言

    使用 FastAPI 可以帮助我们更简单高效地部署 AI 交互业务。FastAPI 提供了快速构建 API 的能力,开发者可以轻松地定义模型需要的输入和输出格式,并编写好相应的业务逻辑。

    FastAPI 的异步高性能架构,可以有效支持大量并发的预测请求,为用户提供流畅的交互体验。此外,FastAPI 还提供了容器化部署能力,开发者可以轻松打包 AI 模型为 Docker 镜像,实现跨环境的部署和扩展。

    总之,使用 FastAPI 可以大大提高 AI 应用程序的开发效率和用户体验,为 AI 模型的部署和交互提供全方位的支持。

    在上一篇开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(三)-CSDN博客学习了FastAPI集成LangChain与openai的api服务进行交互,本篇学习如何如何通过FastAPI与本地部署的qwen2-7b-instruct模型进行交互


二、术语

2.1. vLLM

    vLLM是一个开源的大模型推理加速框架,通过PagedAttention高效地管理attention中缓存的张量,实现了比HuggingFace Transformers高14-24倍的吞吐量。

2.2. OpenAI-Compatible Server

    遵循 OpenAI API 的接口规范,让开发者可以使用OpenAI API相同的方式和方法来调用这些服务,从而利用它们的语言模型功能。

2.3. Qwen2-7B-Instruct

    是通义千问 Qwen2 系列中的一个指令微调模型。它在 Qwen2-7B 的基础上进行了指令微调,以提高模型在特定任务上的性能。

    Qwen2-7B-Instruct 具有以下特点:

  • 强大的性能:在多个基准测试中,Qwen2-7B-Instruct 的性能可与 Llama-3-70B-Instruct 相匹敌。
  • 代码和数学能力提升:得益于高质量的数据和指令微调,Qwen2-7B-Instruct 在数学和代码能力上实现了飞升。
  • 多语言能力:模型训练过程中增加了 27 种语言相关的高质量数据,提升了多语言能力。
  • 上下文长度支持:Qwen2 系列中的所有 Instruct 模型均在 32k 上下文中进行训练,Qwen2-7B-Instruct 和 Qwen2-72B-Instruct 还支持最高可达 128k token 的上下文长度。

2.4. context length

    模型的上下文长度是指在生成文本时,模型所能看到的输入文本的长度范围。在生成文本的过程中,模型会根据前面的上下文来预测下一个词或字符。上下文长度决定了模型能够考虑到的历史信息的数量。

2.5. system prompt(系统提示)

    是指在生成对话或文本的任务中,为了引导模型产生合适的响应或输出,对模型进行输入的开头部分或系统提供的指令。系统提示通常包含一些关键信息,如对话的背景、任务的要求或期望的回答风格等,以帮助模型理解上下文并生成相关的响应。通过精心设计和调整系统提示,可以引导模型产生更准确、连贯且符合预期的输出。

2.6. temperature(温度)

    是用于控制生成模型输出的多样性和随机性的一个参数。当温度较高时,模型会更加随机地选择输出,使得生成结果更加多样化和创造性,但可能会牺牲一些准确性和一致性。相反,当温度较低时,模型会更加确定性地选择输出,使得生成结果更加集中和可控。较低的温度值会使概率分布更尖峰,使得高概率的词或标记更容易被选中。

2.7. top_p

    是一种用于控制生成模型输出的参数。在生成文本或对话的任务中,模型通常会输出一个概率分布,表示每个可能的词或标记的概率。top_p参数用于指定一个概率的阈值,模型将从概率累积最高的词开始逐步选择,直到累积概率超过阈值为止。通过设置top_p参数,我们可以控制生成模型输出的多样性和可控性。较小的top_p值会限制模型选择的候选词的数量,使得模型的输出更加集中和可控。较大的top_p值会增加模型选择的候选词的数量,使得模型的输出更加多样化和创造性。

2.8. repetition_penalty

    是一种用于控制生成模型输出中重复内容的参数。在生成文本或对话的任务中,模型有时候可能会倾向于产生重复的词语、短语或句子,导致生成结果的质量下降或显得不够自然。为了解决这个问题,可以使用重复惩罚机制。重复惩罚参数可以调整模型对已经生成过的内容的偏好程度。较高的重复惩罚值会使模型更加抑制生成已经出现过的内容,以鼓励生成更多新颖的内容。较低的重复惩罚值则会相对宽容,允许模型生成一定程度的重复内容。

2.9. history

    "历史上下文"是指在处理当前文本或对话时,与之前的文本或对话相关的信息和语境。历史上下文包括了之前的句子、段落或对话中的内容,以及前文中提到的实体、事件和语义关系等。它提供了理解当前文本的重要背景信息,帮助我们更准确地解释和推断文本的含义。处理历史上下文时,模型需要能够捕捉并记忆之前的信息,并将其与当前文本进行关联,以产生有意义的输出。

2.10.流式输出

    是模型推理过程中逐步生成输出结果,而非一次性生成整个输出,从而实现更低的延迟和更好的实时性。


三、前置条件

3.1. 创建虚拟环境&安装依赖

  增加openai的依赖包

  1. conda create -n fastapi_test python=3.10
  2. conda activate fastapi_test
  3. pip install fastapi websockets uvicorn
  4. pip install openai

3.2. 创建本地AI服务

开源模型应用落地-Qwen2-7B-Instruct与vllm实现推理加速的正确姿势(十)


四、技术实现

4.1. 集成本地构建的AI服务

服务端:

  1. # -*- coding:utf-8 -*-
  2. import traceback
  3. import uvicorn
  4. from typing import Annotated
  5. from fastapi import (
  6. Depends,
  7. FastAPI,
  8. WebSocket,
  9. WebSocketException,
  10. WebSocketDisconnect,
  11. status,
  12. )
  13. from openai import OpenAI
  14. DEFAULT_IP='127.0.0.1'
  15. DEFAULT_PORT=9000
  16. DEFAULT_MODEL = "/model/qwen2-7b-instruct"
  17. DEFAULT_MAX_TOKENS = 10240
  18. DEFAULT_SYSTEM_PROMPT = '你是一位得力的助手。'
  19. openai_api_key = "EMPTY"
  20. openai_api_base = f"http://{DEFAULT_IP}:{DEFAULT_PORT}/v1"
  21. class ConnectionManager:
  22. def __init__(self):
  23. self.active_connections: list[WebSocket] = []
  24. async def connect(self, websocket: WebSocket):
  25. await websocket.accept()
  26. self.active_connections.append(websocket)
  27. def disconnect(self, websocket: WebSocket):
  28. self.active_connections.remove(websocket)
  29. async def send_personal_message(self, message: str, websocket: WebSocket):
  30. await websocket.send_text(message)
  31. async def broadcast(self, message: str):
  32. for connection in self.active_connections:
  33. await connection.send_text(message)
  34. manager = ConnectionManager()
  35. app = FastAPI()
  36. async def authenticate(
  37. websocket: WebSocket,
  38. userid: str,
  39. secret: str,
  40. ):
  41. if userid is None or secret is None:
  42. raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
  43. print(f'userid: {userid},secret: {secret}')
  44. if '12345' == userid and 'xxxxxxxxxxxxxxxxxxxxxxxxxx' == secret:
  45. return 'pass'
  46. else:
  47. return 'fail'
  48. async def chat(message, history=None, system=None, config=None, stream=True):
  49. global client
  50. if config is None:
  51. config = {'temperature': 0.45, 'top_p': 0.9, 'repetition_penalty': 1.2, 'max_tokens': DEFAULT_MAX_TOKENS,'n':1}
  52. size = 0
  53. messages = []
  54. if system is not None:
  55. messages.append({"role": "system", "content": system})
  56. size = size+len(system)
  57. if history is not None:
  58. if len(history) > 0:
  59. for his in history:
  60. user,assistant = his
  61. user_obj = {"role": "user", "content": user}
  62. assistant_obj = {"role": "assistant", "content": assistant}
  63. messages.append(user_obj)
  64. messages.append(assistant_obj)
  65. size = size + len(user)
  66. size = size + len(assistant)
  67. if message is None:
  68. raise RuntimeError("prompt不能为空!")
  69. else:
  70. messages.append({"role": "user", "content": message})
  71. size = size + len(message)+100
  72. try:
  73. chat_response = client.chat.completions.create(
  74. model=DEFAULT_MODEL,
  75. messages=messages,
  76. stream=stream,
  77. temperature=config['temperature'],
  78. top_p=config['top_p'],
  79. max_tokens=config['max_tokens']-size,
  80. frequency_penalty=config['repetition_penalty'],
  81. # presence_penalty=config['repetition_penalty']
  82. )
  83. for chunk in chat_response:
  84. msg = chunk.choices[0].delta.content
  85. if msg is not None:
  86. yield msg
  87. except Exception:
  88. traceback.print_exc()
  89. @app.websocket("/ws")
  90. async def websocket_endpoint(*,websocket: WebSocket,userid: str,permission: Annotated[str, Depends(authenticate)],):
  91. await manager.connect(websocket)
  92. try:
  93. while True:
  94. text = await websocket.receive_text()
  95. if 'fail' == permission:
  96. await manager.send_personal_message(
  97. f"authentication failed", websocket
  98. )
  99. else:
  100. if text is not None and len(text) > 0:
  101. async for msg in chat(text,None,DEFAULT_SYSTEM_PROMPT,None):
  102. await manager.send_personal_message(msg, websocket)
  103. except WebSocketDisconnect:
  104. manager.disconnect(websocket)
  105. print(f"Client #{userid} left the chat")
  106. await manager.broadcast(f"Client #{userid} left the chat")
  107. if __name__ == '__main__':
  108. client = OpenAI(api_key=openai_api_key, base_url=openai_api_base)
  109. uvicorn.run(app, host='0.0.0.0',port=7777)

客户端:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Chat</title>
  5. </head>
  6. <body>
  7. <h1>WebSocket Chat</h1>
  8. <form action="" onsubmit="sendMessage(event)">
  9. <label>USERID: <input type="text" id="userid" autocomplete="off" value="12345"/></label>
  10. <label>SECRET: <input type="text" id="secret" autocomplete="off" value="xxxxxxxxxxxxxxxxxxxxxxxxxx"/></label>
  11. <br/>
  12. <button onclick="connect(event)">Connect</button>
  13. <hr>
  14. <label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
  15. <button>Send</button>
  16. </form>
  17. <ul id='messages'>
  18. </ul>
  19. <script>
  20. var ws = null;
  21. function connect(event) {
  22. var userid = document.getElementById("userid")
  23. var secret = document.getElementById("secret")
  24. ws = new WebSocket("ws://localhost:7777/ws?userid="+userid.value+"&secret=" + secret.value);
  25. ws.onmessage = function(event) {
  26. var messages = document.getElementById('messages')
  27. var message = document.createElement('li')
  28. var content = document.createTextNode(event.data)
  29. message.appendChild(content)
  30. messages.appendChild(message)
  31. };
  32. event.preventDefault()
  33. }
  34. function sendMessage(event) {
  35. var input = document.getElementById("messageText")
  36. ws.send(input.value)
  37. input.value = ''
  38. event.preventDefault()
  39. }
  40. </script>
  41. </body>
  42. </html>

调用结果:

用户输入:你好

模型输出:你好!很高兴能为你提供帮助。有什么问题或需要我协助的事项吗?

用户输入:你是谁?

模型输出:我是阿里云开发的一款超大规模语言模型,我叫通义千问。作为一个AI助手,我的目标是帮助用户获得准确、有用的信息,解决他们的问题和困惑。无论是提供知识解答、创意启发,还是进行对话交流,我都将全力以赴提供高质量的服务。

PS:

1. 此处服务端采用OpenAI-Compatible Server,非唯一实现方式

2. 页面输出的样式可以根据实际需要进行调整,此处仅用于演示效果。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/773132
推荐阅读
相关标签
  

闽ICP备14008679号