├── .github └── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── config.py ├── cookie ├── bard.json ├── bing.json ├── claude.json └── ernie.json ├── doc └── wiki │ ├── Bard.md │ ├── Bing.md │ ├── ChatGPT.md │ ├── Claude.md │ ├── Home.md │ └── 文心一言.md ├── gunicorn.py ├── main.py ├── module ├── __init__.py ├── auxiliary.py ├── chat.py ├── chatgpt.py └── core.py ├── requirements.txt └── view ├── __init__.py ├── bard.py ├── bing.py ├── chatgpt.py ├── claude.py └── ernie.py /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug提交 2 | description: Bug提交 3 | title: '[Bug]: ' 4 | body: 5 | - type: checkboxes 6 | id: confirm 7 | attributes: 8 | label: 在提交前请确保以下这些 9 | options: 10 | - label: Chat-WebAPI为最新版本 11 | required: true 12 | - label: 在Issue无法找到它 13 | required: true 14 | - type: dropdown 15 | id: os 16 | attributes: 17 | label: 操作系统 18 | multiple: true 19 | options: 20 | - Linux 21 | - Windows 22 | - MacOS 23 | - Android 24 | - iOS 25 | - Other 26 | validations: 27 | required: true 28 | - type: input 29 | id: python-version 30 | attributes: 31 | label: Python版本 32 | description: '`python -V`' 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: problem 37 | attributes: 38 | label: 问题 39 | validations: 40 | required: true 41 | - type: textarea 42 | id: trigger 43 | attributes: 44 | label: 触发 45 | description: 如何触发这个问题(例如提供代码). 46 | validations: 47 | required: true 48 | - type: textarea 49 | id: error 50 | attributes: 51 | label: 报错 52 | description: 提供报错输出. 53 | - type: textarea 54 | id: other 55 | attributes: 56 | label: 其他 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 功能建议 2 | description: 功能建议 3 | title: '[Feature]: ' 4 | body: 5 | - type: checkboxes 6 | id: confirm 7 | attributes: 8 | label: 在提交前请确保以下这些 9 | options: 10 | - label: 在Issue无法找到它 11 | required: true 12 | - type: textarea 13 | id: description 14 | attributes: 15 | label: 描述 16 | validations: 17 | required: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/.DS_Store 3 | .DS_Store? -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.0-slim-bullseye 2 | 3 | WORKDIR /app 4 | 5 | COPY ./requirements.txt . 6 | RUN pip install -r ./requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ --no-cache-dir 7 | 8 | COPY . . 9 | 10 | CMD ["gunicorn", "main:APP", "-c", "gunicorn.py"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 XiaoXinYo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 提示 2 | 1. 升级咯,此仓库前身为Bing-Chat,现已更名为Chat-WebAPI,支持多种Chat,现已支持Bard,Bing,ChatGPT,Claude,文心一言. 3 | 2. 已知Bing,文心一言有封号风险. 4 | --- 5 | ![Release](https://img.shields.io/badge/Release-0.1.8-blue) 6 | --- 7 | ## 介绍 8 | 一款基于Python-FastAPI框架,开发的多种Chat WebAPI程序. 9 | ## 需求 10 | 1. 平台: Windows/Linux/Docker. 11 | 2. 语言: Python3.8+. 12 | 3. 其他: Bard账户,Bing账户,ChatGPT密钥,Claude账户,文心一言账户. 13 | ## 配置 14 | 查看config.py文件. 15 | ## Cookie 16 | 1. 浏览器安装Cookie-Editor扩展. 17 | 2. 访问[Bard](https://bard.google.com/)/[Bing](https://www.bing.com/chat)/[Claude](https://claude.ai/)/[文心一言](https://yiyan.baidu.com/). 18 | 3. 在页面中点击扩展. 19 | 4. 点击扩展右下角的Export-Export as JSON 20 | 5. 将复制的内容粘贴到对应的Cookie文件(cookie/)中. 21 | ## 部署 22 | 1. Windows: 运行main.py文件. 23 | 2. Linux: 执行`gunicorn main:APP -c gunicorn.py`命令. 24 | 3. 支持Docker. 25 | ## 接口文档 26 | 查看[Wiki](https://github.com/XiaoXinYo/Chat-WebAPI/wiki). 27 | 提示: 在使用Chat前,请确保对应的Cookie已配置好,ChatGPT密钥需要在config.py文件中写. -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | HTTP = { 2 | 'host': '0.0.0.0', # HTTP地址 3 | 'port': 222, # HTTP端口 4 | 'ssl': { 5 | 'enable': False, # 启用HTTP SSL 6 | 'keyPath': '', # HTTP SSL Key路径 7 | 'certPath': '' # HTTP SSL Cert路径 8 | } 9 | } 10 | 11 | PROXY = { 12 | 'enable': False, # 启用代理 13 | 'host': '', # 代理地址 14 | 'port': 80, # 代理端口, 15 | 'ssl': False # 启用代理SSL 16 | } 17 | 18 | CHATGPT_KEY = '' # ChatGPT密钥 19 | 20 | TOKEN_USE_MAX_TIME_INTERVAL = 30 # Token使用最大时间间隔(分钟),超过此时间未使用将被删除 -------------------------------------------------------------------------------- /cookie/bard.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ykaiqx/Chat-WebAPI/8c8253bb9ced3e0a9a5e3254f935393e1247bee5/cookie/bard.json -------------------------------------------------------------------------------- /cookie/bing.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ykaiqx/Chat-WebAPI/8c8253bb9ced3e0a9a5e3254f935393e1247bee5/cookie/bing.json -------------------------------------------------------------------------------- /cookie/claude.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ykaiqx/Chat-WebAPI/8c8253bb9ced3e0a9a5e3254f935393e1247bee5/cookie/claude.json -------------------------------------------------------------------------------- /cookie/ernie.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ykaiqx/Chat-WebAPI/8c8253bb9ced3e0a9a5e3254f935393e1247bee5/cookie/ernie.json -------------------------------------------------------------------------------- /doc/wiki/Bard.md: -------------------------------------------------------------------------------- 1 | ## Ask 2 | ### 请求 3 | 1. 网址: /bard/ask. 4 | 2. 方式: GET/POST. 5 | 3. 格式: JSON(当方式为POST时). 6 | 4. 参数: 7 | 8 | 名称|类型|必填|说明 9 | ---|---|---|--- 10 | question|String|是| 11 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 12 | ### 响应 13 | 1. 格式: JSON. 14 | 2. 参数: 15 | 16 | 名称|类型|说明 17 | ---|---|--- 18 | code|Integer| 19 | message|String| 20 | data|Object| 21 | answer|String| 22 | urls|List| 23 | imageUrls|List| 24 | token|String|用于连续对话 25 | 3. 示例: 26 | ```json 27 | { 28 | "code": 200, 29 | "message": "success", 30 | "data": { 31 | "answer": "Hello! How can I help you today?", 32 | "urls": [ 33 | "https://bard.google.com(Bard)" 34 | ], 35 | "imageUrls": [ 36 | "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" 37 | ], 38 | "token": "8953d67b-eac2-457e-a2ee-fedc8ba53599" 39 | } 40 | } 41 | ``` -------------------------------------------------------------------------------- /doc/wiki/Bing.md: -------------------------------------------------------------------------------- 1 | ## Ask 2 | ### 请求 3 | 1. 网址: /bing/ask. 4 | 2. 方式: GET/POST. 5 | 3. 格式: JSON(当方式为POST时). 6 | 4. 参数: 7 | 8 | 名称|类型|必填|说明 9 | ---|---|---|--- 10 | question|String|是| 11 | style|String|否|balanced代表平衡,creative代表创造,precise代表精确,默认为balanced 12 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 13 | ### 响应 14 | 1. 格式: JSON. 15 | 2. 参数: 16 | 17 | 名称|类型|说明 18 | ---|---|--- 19 | code|Integer| 20 | message|String| 21 | data|Object| 22 | answer|String| 23 | urls|List| 24 | reset|Bool|下次对话是否被重置 25 | token|String|用于连续对话 26 | 3. 示例: 27 | ```json 28 | { 29 | "code": 200, 30 | "message": "success", 31 | "data": { 32 | "answer": "您好,这是必应。", 33 | "urls": [ 34 | { 35 | "title": "The New Bing - Learn More", 36 | "url": "https://www.bing.com/new" 37 | } 38 | ], 39 | "reset": false, 40 | "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599" 41 | } 42 | } 43 | ``` 44 | ## Ask Stream 45 | ### 请求 46 | 1. 网址: /bing/ask_stream. 47 | 2. 方式: GET/POST. 48 | 3. 格式: JSON(当方式为POST时). 49 | 4. 参数: 50 | 51 | 名称|类型|必填|说明 52 | ---|---|---|--- 53 | question|String|是| 54 | style|String|否|balanced代表平衡,creative代表创造,precise代表精确,默认为balanced 55 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 56 | ### 响应 57 | 1. 格式: Event-Stream. 58 | 2. 参数: 59 | 60 | 名称|类型|说明 61 | ---|---|--- 62 | code|Integer| 63 | message|String| 64 | data|Object| 65 | answer|String| 66 | urls|List|当done为true时,显示真实值 67 | done|Bool|部分传输是否完成 68 | reset|Bool|下次对话是否被重置,当done为true时,显示真实值 69 | token|String|用于连续对话 70 | 3. 示例: 71 | ``` 72 | data: {"code": 200, "message": "success", "data": {"answer": "您。", "urls": [], "done": false, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 73 | 74 | data: {"code": 200, "message": "success", "data": {"answer": "好", "urls": [], "done": false, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 75 | 76 | data: {"code": 200, "message": "success", "data": {"answer": ",", "urls": [], "done": false, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 77 | 78 | data: {"code": 200, "message": "success", "data": {"answer": "这。", "urls": [], "done": false, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 79 | 80 | data: {"code": 200, "message": "success", "data": {"answer": "是", "urls": [], "done": false, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 81 | 82 | data: {"code": 200, "message": "success", "data": {"answer": "必应", "urls": [], "done": false, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 83 | 84 | data: {"code": 200, "message": "success", "data": {"answer": "。", "urls": [], "done": false, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 85 | 86 | data: {"code": 200, "message": "success", "data": {"answer": "您好,这是必应。", "urls": [{"title": "The New Bing - Learn More", "url": "https://www.bing.com/new"}], "done": true, "reset": false, "token": "7953d67b-eac2-457e-a2ee-fedc8ba53599"}} 87 | ``` -------------------------------------------------------------------------------- /doc/wiki/ChatGPT.md: -------------------------------------------------------------------------------- 1 | ## 模型 2 | 名称|最大长度 3 | ---|--- 4 | gpt-3.5-turbo|4097 5 | gpt-4|8192 6 | ## Ask 7 | ### 请求 8 | 1. 网址: /chatgpt/ask. 9 | 2. 方式: GET/POST. 10 | 3. 格式: JSON(当方式为POST时). 11 | 4. 参数: 12 | 13 | 名称|类型|必填|说明 14 | ---|---|--|--- 15 | question|String|是| 16 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 17 | model|String|否|首次需填写,后续修改则填写 18 | ### 响应 19 | 1. 格式: JSON. 20 | 2. 参数: 21 | 22 | 名称|类型|说明 23 | ---|---|--- 24 | code|Integer| 25 | message|String| 26 | data|Object| 27 | answer|String| 28 | token|String|用于连续对话 29 | 3. 示例: 30 | ```json 31 | { 32 | "code": 200, 33 | "message": "success", 34 | "data": { 35 | "answer": "你好!很高兴能和你聊天。有什么我可以帮你的吗?", 36 | "token": "d65b5b6a-bae3-4cfd-a8b1-07d15bb891c9" 37 | } 38 | } 39 | ``` 40 | ## Ask Stream 41 | ### 请求 42 | 1. 网址: /chatgpt/ask_stream. 43 | 2. 方式: GET/POST. 44 | 3. 格式: JSON(当方式为POST时). 45 | 4. 参数: 46 | 47 | 名称|类型|必填|说明 48 | ---|---|---|--- 49 | question|String|是| 50 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 51 | model|String|否|首次需填写,后续修改则填写 52 | ### 响应 53 | 1. 格式: Event-Stream. 54 | 2. 参数: 55 | 56 | 名称|类型|说明 57 | ---|---|--- 58 | code|Integer| 59 | message|String| 60 | data|Object| 61 | answer|String| 62 | done|Bool|部分传输是否完成 63 | token|String|用于连续对话 64 | 3. 示例: 65 | ``` 66 | data: {"code": 200, "message": "success", "data": {"answer": "你好!我是", "done": false, "token": "84361cde-73fc-488b-b75f-c69d3602411d"}} 67 | 68 | data: {"code": 200, "message": "success", "data": {"answer": "ChatGPT", "done": false, "token": "84361cde-73fc-488b-b75f-c69d3602411d"}} 69 | 70 | data: {"code": 200, "message": "success", "data": {"answer": ",", "done": false, "token": "84361cde-73fc-488b-b75f-c69d3602411d"}} 71 | 72 | data: {"code": 200, "message": "success", "data": {"answer": "你好!我是ChatGPT,很高兴能和你交流。有什么可以帮助你的吗?", "done": false, "token": "84361cde-73fc-488b-b75f-c69d3602411d"}} 73 | 74 | data: {"code": 200, "message": "success", "data": {"answer": "?", "done": false, "token": "84361cde-73fc-488b-b75f-c69d3602411d"}} 75 | 76 | data: {"code": 200, "message": "success", "data": {"answer": "你好!我是ChatGPT,很高兴能和你交流。有什么可以帮助你的吗?", "done": True, "token": "84361cde-73fc-488b-b75f-c69d3602411d"}} 77 | ``` -------------------------------------------------------------------------------- /doc/wiki/Claude.md: -------------------------------------------------------------------------------- 1 | ## Ask 2 | ### 请求 3 | 1. 网址: /claude/ask. 4 | 2. 方式: GET/POST. 5 | 3. 格式: JSON(当方式为POST时). 6 | 4. 参数: 7 | 8 | 名称|类型|必填|说明 9 | ---|---|---|--- 10 | question|String|是| 11 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 12 | ### 响应 13 | 1. 格式: JSON. 14 | 2. 参数: 15 | 16 | 名称|类型|说明 17 | ---|---|--- 18 | code|Integer| 19 | message|String| 20 | data|Object| 21 | answer|String| 22 | token|String|用于连续对话 23 | 3. 示例: 24 | ```json 25 | { 26 | "code": 200, 27 | "message": "success", 28 | "data": { 29 | "answer": "你好,我是Claude。", 30 | "token": "3153d67b-eac2-457e-a2ee-fedc8ba53588" 31 | } 32 | } 33 | ``` -------------------------------------------------------------------------------- /doc/wiki/Home.md: -------------------------------------------------------------------------------- 1 | 欢迎来到 Chat-API Wiki. -------------------------------------------------------------------------------- /doc/wiki/文心一言.md: -------------------------------------------------------------------------------- 1 | ## Ask 2 | ### 请求 3 | 1. 网址: /ernie/ask. 4 | 2. 方式: GET/POST. 5 | 3. 格式: JSON(当方式为POST时). 6 | 4. 参数: 7 | 8 | 名称|类型|必填|说明 9 | ---|---|---|--- 10 | question|String|是| 11 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 12 | ### 响应 13 | 1. 格式: JSON. 14 | 2. 参数: 15 | 16 | 名称|类型|说明 17 | ---|---|--- 18 | code|Integer| 19 | message|String| 20 | data|Any| 21 | answer|String| 22 | urls|List| 23 | token|String|用于连续对话 24 | 3. 示例: 25 | ```json 26 | { 27 | "code": 200, 28 | "message": "success", 29 | "data": { 30 | "answer": "我画好了,欢迎对我提出反馈和建议,帮助我快速进步。\n在结尾添加#创意图#,可能会解锁小彩蛋哦,如:“帮我画鸡蛋灌饼#创意图#”。", 31 | "urls": [ 32 | "http://eb118-file.cdn.bcebos.com/upload/B2E81C0BACF6A802313A1C6E2FB9A192?x-bce-process=style/wm_ai" 33 | ], 34 | "token": "95c1a1da-0892-4fdc-8c63-c830ce956bd2" 35 | } 36 | } 37 | ``` 38 | ## Ask Stream 39 | ### 请求 40 | 1. 网址: /ernie/ask_stream. 41 | 2. 方式: GET/POST. 42 | 3. 格式: JSON(当方式为POST时). 43 | 4. 参数: 44 | 45 | 名称|类型|必填|说明 46 | ---|---|---|--- 47 | question|String|是| 48 | token|String|否|填则为连续对话,不填则为新对话,值可在响应中获取 49 | ### 响应 50 | 1. 格式: Event-Stream. 51 | 2. 参数: 52 | 53 | 名称|类型|说明 54 | ---|---|--- 55 | code|Integer| 56 | message|String| 57 | data|Object| 58 | answer|String| 59 | urls|List|当done为true时,显示真实值 60 | done|Bool|部分传输是否完成 61 | token|String|用于连续对话 62 | 3. 示例: 63 | ``` 64 | data: {"code": 200, "message": "success", "data": {"answer": "我画好了,欢迎对我提出反馈和建议", "urls": [], "done": false, "token": "6d5f3417-f142-4f23-9bfa-21a641da9ad0"}} 65 | 66 | data: {"code": 200, "message": "success", "data": {"answer": ",", "urls": [], "done": false, "token": "6d5f3417-f142-4f23-9bfa-21a641da9ad0"}} 67 | 68 | data: {"code": 200, "message": "success", "data": {"answer": "帮助我快速进步。\n在结尾添加#创意图#,可能会解锁小彩蛋哦,如:“帮我画鸡蛋灌饼#创意图#”。", "urls": [], "done": false, "token": "6d5f3417-f142-4f23-9bfa-21a641da9ad0"}} 69 | 70 | data: {"code": 200, "message": "success", "data": {"answer": "我画好了,欢迎对我提出反馈和建议,帮助我快速进步。\n在结尾添加#创意图#,可能会解锁小彩蛋哦,如:“帮我画鸡蛋灌饼#创意图#”。", "urls": ["http://eb118-file.cdn.bcebos.com/upload/37A9A144161817C71B24A15E0514E792?x-bce-process=style/wm_ai"], "done": true, "token": "6d5f3417-f142-4f23-9bfa-21a641da9ad0"}} 71 | ``` -------------------------------------------------------------------------------- /gunicorn.py: -------------------------------------------------------------------------------- 1 | from config import HTTP 2 | 3 | workers = 1 4 | worker_class = 'uvicorn.workers.UvicornWorker' 5 | 6 | bind = f'{HTTP["host"]}:{HTTP["port"]}' 7 | if HTTP['ssl']['enable']: 8 | keyfile = HTTP['ssl']['keyPath'] 9 | certfile = HTTP['ssl']['certPath'] -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request, Response 2 | from fastapi.middleware.cors import CORSMiddleware 3 | from contextlib import asynccontextmanager 4 | from view import bard, bing, chatgpt, claude, ernie 5 | from module import chat, core 6 | import asyncio 7 | import config 8 | import uvicorn 9 | 10 | @asynccontextmanager 11 | async def lifespan(app: FastAPI): 12 | asyncio.create_task(chat.check()) 13 | yield 14 | await chat.check(loop=False) 15 | 16 | APP = FastAPI(lifespan=lifespan) 17 | APP.add_middleware( 18 | CORSMiddleware, 19 | allow_origins=['*'], 20 | allow_credentials=True, 21 | allow_methods=['*'], 22 | allow_headers=['*'], 23 | ) 24 | APP.include_router(bard.BARD_APP, prefix='/bard') 25 | APP.include_router(bing.BING_APP, prefix='/bing') 26 | APP.include_router(chatgpt.CHATGPT_APP, prefix='/chatgpt') 27 | APP.include_router(claude.CLAUDE_APP, prefix='/claude') 28 | APP.include_router(ernie.ERNIE_APP, prefix='/ernie') 29 | 30 | @APP.middleware('http') 31 | async def middleware(request: Request, call_next) -> None: 32 | urls = request.url.path.split('/') 33 | if len(urls) == 3: 34 | model = urls[1] 35 | mode = urls[2] 36 | if mode == 'ask': 37 | generate = lambda model: core.GenerateResponse().error(100, f'{model}未配置') 38 | else: 39 | generate = lambda model: core.GenerateResponse().error(100, f'{model}未配置', streamResponse=True) 40 | if model == 'bard': 41 | if not chat.BARD_COOKIE: 42 | return generate('Bard') 43 | elif model == 'bing': 44 | if not chat.BING_COOKIE: 45 | return generate('Bing') 46 | elif model == 'chatgpt': 47 | if not config.CHATGPT_KEY: 48 | return generate('ChatGPT') 49 | elif model == 'claude': 50 | if not chat.CLAUDE_COOKIE: 51 | return generate('Claude') 52 | elif model == 'ernie': 53 | if not chat.ERNIE_COOKIE: 54 | return generate('文心一言') 55 | 56 | response = await call_next(request) 57 | return response 58 | 59 | @APP.exception_handler(404) 60 | def error404(request: Request, exc: Exception) -> Response: 61 | return core.GenerateResponse().error(404, '未找到文件', httpCode=404) 62 | 63 | @APP.exception_handler(500) 64 | def error500(request: Request, exc: Exception) -> Response: 65 | return core.GenerateResponse().error(500, '未知错误', httpCode=500) 66 | 67 | if __name__ == '__main__': 68 | appConfig = { 69 | 'host': config.HTTP['host'], 70 | 'port': config.HTTP['port'] 71 | } 72 | if config.HTTP['ssl']['enable']: 73 | uvicorn.run(APP, **appConfig, ssl_keyfile=config.HTTP['ssl']['keyPath'], ssl_certfile=config.HTTP['ssl']['certPath']) 74 | else: 75 | uvicorn.run(APP, **appConfig) -------------------------------------------------------------------------------- /module/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ykaiqx/Chat-WebAPI/8c8253bb9ced3e0a9a5e3254f935393e1247bee5/module/__init__.py -------------------------------------------------------------------------------- /module/auxiliary.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | import time 3 | import json 4 | 5 | def getTimestamp(ms: bool=False) -> int: 6 | return int(time.time() * (1000 if ms else 1)) 7 | 8 | def getCookie(filePath: str, dict_: bool=True) -> Union[dict, str]: 9 | result = {} 10 | with open(filePath) as file: 11 | try: 12 | cookies = json.load(file) 13 | except: 14 | return result 15 | 16 | for cookie in cookies: 17 | result[cookie['name']] = cookie['value'] 18 | 19 | if not dict_: 20 | result = '; '.join([f'{name}={value}' for name, value in result.items()]) 21 | return result -------------------------------------------------------------------------------- /module/chat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from module import auxiliary 3 | from module import chatgpt 4 | import enum 5 | import bardapi 6 | import re_edge_gpt 7 | import claude2_api 8 | import easy_ernie 9 | import config 10 | import asyncio 11 | import uuid 12 | import json 13 | import dataclasses 14 | 15 | CHATS = [] 16 | 17 | BARD_COOKIE = auxiliary.getCookie('./cookie/bard.json') 18 | with open('./cookie/bing.json', 'r') as file: 19 | BING_COOKIE = json.load(file) 20 | CLAUDE_COOKIE = auxiliary.getCookie('./cookie/claude.json', dict_=False) 21 | ERNIE_COOKIE = auxiliary.getCookie('./cookie/ernie.json') 22 | 23 | class Type(enum.Enum): 24 | BARD = 'Bard' 25 | BING = 'Bing' 26 | CHATGPT = 'ChatGPT' 27 | CLAUDE = 'Claude' 28 | ERNIE = 'Ernie' 29 | 30 | @dataclasses.dataclass 31 | class Chat: 32 | type_: Type 33 | token: str 34 | bot: object 35 | parameter: dict 36 | timestamp: int 37 | 38 | async def generate(type_: Type, parameter: dict={}) -> Optional[tuple]: 39 | global CHATS 40 | addressPortProxy = f'{config.PROXY["host"]}:{config.PROXY["port"]}' if config.PROXY['enable'] else None 41 | proxy = { 42 | 'http': f'http://{addressPortProxy}/', 43 | 'https': f'https://{addressPortProxy}/' 44 | } if config.PROXY['enable'] else None 45 | if type_ == Type.BARD: 46 | bot = bardapi.BardCookies(cookie_dict=BARD_COOKIE, proxies=proxy) 47 | elif type_ == Type.BING: 48 | bot = await re_edge_gpt.Chatbot.create(proxy=addressPortProxy, cookies=BING_COOKIE) 49 | elif type_ == Type.CHATGPT: 50 | bot = chatgpt.ChatGPT(config.CHATGPT_KEY, proxy=proxy) 51 | elif type_ == Type.CLAUDE: 52 | claudeSession = claude2_api.SessionData(CLAUDE_COOKIE, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0') 53 | claudeProxy = claude2_api.client.HTTPProxy( 54 | config.PROXY['host'], 55 | config.PROXY['port'], 56 | use_ssl=config.PROXY['ssl'] 57 | ) if config.PROXY['enable'] else None 58 | bot = claude2_api.ClaudeAPIClient(claudeSession, proxy=claudeProxy) 59 | parameter['chatId'] = bot.create_chat() 60 | elif type_ == Type.ERNIE: 61 | bot = easy_ernie.FastErnie(ERNIE_COOKIE['BAIDUID'], ERNIE_COOKIE['BDUSS_BFESS']) 62 | else: 63 | return None 64 | token = str(uuid.uuid4()) 65 | CHATS.append(Chat(type_, token, bot, parameter, auxiliary.getTimestamp())) 66 | return token, bot 67 | 68 | def get(type_: Type, token: str) -> Optional[Chat]: 69 | global CHATS 70 | for chat in CHATS: 71 | if chat.type_ == type_ and chat.token == token: 72 | return chat 73 | return None 74 | 75 | def update(token, parameter: dict) -> None: 76 | global CHATS 77 | for chat in CHATS: 78 | if chat.token == token: 79 | chat.parameter = parameter 80 | break 81 | 82 | async def check(loop=True) -> None: 83 | global CHATS 84 | while True: 85 | for chat in CHATS.copy(): 86 | if loop and auxiliary.getTimestamp() - chat.timestamp <= config.TOKEN_USE_MAX_TIME_INTERVAL * 60: 87 | continue 88 | 89 | if chat.type_ == Type.BARD: 90 | await chat.bot.close() 91 | elif chat.type_ == Type.BING: 92 | chat.bot.close() 93 | elif chat.type_ == Type.CLAUDE: 94 | chat.bot.delete_chat(chat.parameter['chatId']) 95 | elif chat.type_ == Type.ERNIE: 96 | chat.bot.close() 97 | CHATS.remove(chat) 98 | 99 | if loop: 100 | await asyncio.sleep(60) 101 | else: 102 | break -------------------------------------------------------------------------------- /module/chatgpt.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Generator 2 | import requests 3 | import json 4 | 5 | MODELS = [ 6 | 'gpt-3.5-turbo', 7 | 'gpt-4' 8 | ] 9 | 10 | class ChatGPT: 11 | def __init__(self, apiKey: str, apiUrl: str='https://api.openai.com', proxy: Optional[dict]=None) -> None: 12 | self.apiUrl = apiUrl 13 | self.apiKey = apiKey 14 | self.messages = [] 15 | self.session = requests.Session() 16 | self.session.headers = { 17 | 'Content-Type': 'application/json', 18 | 'Authorization': f'Bearer {apiKey}' 19 | } 20 | if proxy: 21 | self.session.proxies = proxy 22 | 23 | def checkJson(self, data: str) -> None: 24 | try: 25 | data = json.loads(data) 26 | except: 27 | raise Exception('请求失败,响应格式错误') 28 | 29 | if 'error' in data: 30 | raise Exception(f'请求失败,{data["error"]["message"]}') 31 | 32 | def request(self, method: str, url: str, data: Optional[dict] = None, stream=False, check=True) -> requests.Response: 33 | if method == 'get': 34 | self.response = self.session.get(url, params=data, stream=stream) 35 | else: 36 | self.session.headers['Content-Length'] = str(len(data)) 37 | self.response = self.session.request(method, url, data=json.dumps(data), stream=stream) 38 | 39 | if not stream and check: 40 | self.checkJson(self.response.text) 41 | return self.response 42 | 43 | def get(self, url: str, data: Optional[dict] = None, stream=False, check=True) -> requests.Response: 44 | return self.request('get', url, data, stream=stream, check=check) 45 | 46 | def post(self, url: str, data: dict, stream=False, check=True) -> requests.Response: 47 | return self.request('post', url, data, stream=stream, check=check) 48 | 49 | @staticmethod 50 | def getModel() -> list: 51 | return MODELS 52 | 53 | def askStream(self, model: str, question: str) -> Generator: 54 | if model not in MODELS: 55 | raise Exception(f'{model}模型不存在') 56 | 57 | self.messages.append({ 58 | 'role': 'user', 59 | 'content': question 60 | }) 61 | response = self.post( 62 | f'{self.apiUrl}/v1/chat/completions', 63 | { 64 | 'model': model, 65 | 'messages': self.messages, 66 | 'stream': True, 67 | }, 68 | stream=True, 69 | check=False 70 | ) 71 | if response.headers.get('Content-Type') != 'text/event-stream': 72 | self.checkJson(response.text) 73 | 74 | fullAnswer = '' 75 | for line in response.iter_lines(): 76 | if not line: 77 | continue 78 | 79 | line = line.decode('utf-8') 80 | data = line[6:] 81 | data = json.loads(data) 82 | choices = data['choices'][0] 83 | answer = choices['delta'].get('content') 84 | if answer: 85 | fullAnswer += answer 86 | yield { 87 | 'answer': answer, 88 | 'done': False 89 | } 90 | elif choices['finish_reason'] == 'stop': 91 | yield { 92 | 'answer': fullAnswer, 93 | 'done': True 94 | } 95 | break 96 | 97 | self.messages.append({ 98 | 'role': 'system', 99 | 'content': fullAnswer 100 | }) 101 | 102 | def ask(self, model: str, question: str) -> str: 103 | for data in self.askStream(model, question): 104 | if data['done']: 105 | return data['answer'] -------------------------------------------------------------------------------- /module/core.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Any 2 | from fastapi import Request, Response 3 | import json 4 | 5 | async def getRequestParameter(request: Request) -> dict: 6 | result = {} 7 | if request.method == 'GET': 8 | result = request.query_params 9 | elif request.method == 'POST': 10 | result = await request.json() 11 | return dict(result) 12 | 13 | class GenerateResponse: 14 | TYPE = Union[str, Response] 15 | 16 | def __init__(self): 17 | self.code = 0 18 | self.message = '' 19 | self.data = None 20 | self.httpCode = 200 21 | self.streamFormat = False 22 | self.streamResponse = False 23 | 24 | def generate(self) -> TYPE: 25 | responseJSON = json.dumps({ 26 | 'code': self.code, 27 | 'message': self.message, 28 | 'data': self.data 29 | }, ensure_ascii=False) 30 | if self.streamFormat: 31 | return f'data: {responseJSON}\n\n' 32 | if self.streamResponse: 33 | return Response(f'data: {responseJSON}\n\n', media_type='text/event-stream') 34 | return Response(responseJSON, status_code=self.httpCode, media_type='application/json') 35 | 36 | def error(self, code: int, message: str, streamFormat=False, streamResponse=False, httpCode=200) -> TYPE: 37 | self.code = code 38 | self.message = message 39 | self.streamFormat = streamFormat 40 | self.streamResponse = streamResponse 41 | self.httpCode = httpCode 42 | return self.generate() 43 | 44 | def success(self, data: Any, streamFormat=False) -> TYPE: 45 | self.code = 200 46 | self.message = 'success' 47 | self.data = data 48 | self.streamFormat = streamFormat 49 | return self.generate() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | gunicorn 4 | asyncio 5 | bardapi 6 | re_edge_gpt 7 | requests 8 | unofficial-claude2-api 9 | easy-ernie -------------------------------------------------------------------------------- /view/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ykaiqx/Chat-WebAPI/8c8253bb9ced3e0a9a5e3254f935393e1247bee5/view/__init__.py -------------------------------------------------------------------------------- /view/bard.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | from module import core, chat 3 | 4 | BARD_APP = APIRouter() 5 | 6 | @BARD_APP.route('/ask', methods=['GET', 'POST']) 7 | async def ask(request: Request) -> Response: 8 | parameter = await core.getRequestParameter(request) 9 | question = parameter.get('question') 10 | token = parameter.get('token') 11 | if not question: 12 | return core.GenerateResponse().error(110, '参数不能为空') 13 | 14 | if token: 15 | chatG = chat.get(chat.Type.BARD, token) 16 | if not chatG: 17 | return core.GenerateResponse().error(110, 'token不存在') 18 | 19 | bot = chatG.bot 20 | else: 21 | token, bot = await chat.generate(chat.Type.BARD) 22 | 23 | data = bot.get_answer(question) 24 | urls = data['links'] 25 | imageUrls = data['images'] 26 | urls = [url for url in urls if url not in imageUrls] 27 | return core.GenerateResponse().success({ 28 | 'answer': data['content'], 29 | 'urls': urls, 30 | 'imageUrls': imageUrls, 31 | 'token': token 32 | }) -------------------------------------------------------------------------------- /view/bing.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator 2 | from fastapi import APIRouter, Request, Response 3 | from fastapi.responses import StreamingResponse 4 | from module import core, chat 5 | import re_edge_gpt 6 | import re 7 | 8 | BING_APP = APIRouter() 9 | STYLES = ['balanced', 'creative', 'precise'] 10 | 11 | def getStyleEnum(style: str) -> re_edge_gpt.ConversationStyle: 12 | enum = re_edge_gpt.ConversationStyle 13 | if style == 'balanced': 14 | enum = enum.balanced 15 | elif style == 'creative': 16 | enum = enum.creative 17 | elif style == 'precise': 18 | enum = enum.precise 19 | return enum 20 | 21 | def todayUpperLimit(data: dict) -> bool: 22 | return data['item']['result']['value'] == 'Throttled' 23 | 24 | def filterAnswer(answer: str) -> str: 25 | answer = answer.replace('- **', '').replace('**', '') 26 | answer = re.sub('\[\^.*?\^]', '', answer) 27 | return answer 28 | 29 | def getAnswer(data: dict) -> str: 30 | messages = data['item']['messages'] 31 | for message in messages: 32 | if 'suggestedResponses' not in message: 33 | continue 34 | return filterAnswer(message['text']) 35 | return '' 36 | 37 | def getUrl(data: dict) -> list: 38 | messages = data['item']['messages'] 39 | urls = [] 40 | for message in messages: 41 | sourceAttributions = message.get('sourceAttributions') 42 | if not sourceAttributions: 43 | continue 44 | for sourceAttribution in sourceAttributions: 45 | urls.append({ 46 | 'title': sourceAttribution['providerDisplayName'], 47 | 'url': sourceAttribution['seeMoreUrl'] 48 | }) 49 | return urls 50 | 51 | def needReset(data: dict, answer: str) -> bool: 52 | errorAnswers = ['I’m still learning', '我还在学习'] 53 | maxTimes = data['item']['throttling']['maxNumUserMessagesInConversation'] 54 | nowTimes = data['item']['throttling']['numUserMessagesInConversation'] 55 | if [errorAnswer for errorAnswer in errorAnswers if errorAnswer in answer]: 56 | return True 57 | elif nowTimes == maxTimes: 58 | return True 59 | return False 60 | 61 | @BING_APP.route('/ask', methods=['GET', 'POST']) 62 | async def ask(request: Request) -> Response: 63 | parameter = await core.getRequestParameter(request) 64 | question = parameter.get('question') 65 | style = parameter.get('style', 'balanced') 66 | token = parameter.get('token') 67 | if not question: 68 | return core.GenerateResponse().error(110, '参数不能为空') 69 | elif style not in STYLES: 70 | return core.GenerateResponse().error(110, 'style不存在') 71 | 72 | if token: 73 | chatG = chat.get(chat.Type.BING, token) 74 | if not chatG: 75 | return core.GenerateResponse().error(110, 'token不存在') 76 | 77 | bot = chatG.bot 78 | else: 79 | token, bot = await chat.generate(chat.Type.BING) 80 | 81 | data = await bot.ask(question, conversation_style=getStyleEnum(style)) 82 | if todayUpperLimit(data): 83 | return core.GenerateResponse().error(130, '已上限,24小时后尝试') 84 | 85 | info = { 86 | 'answer': '', 87 | 'urls': [], 88 | 'reset': False, 89 | 'token': token 90 | } 91 | answer = getAnswer(data) 92 | answer = filterAnswer(answer) 93 | info['answer'] = answer 94 | info['urls'] = getUrl(data) 95 | 96 | if needReset(data, answer): 97 | await bot.reset() 98 | info['reset'] = True 99 | 100 | return core.GenerateResponse().success(info) 101 | 102 | 103 | @BING_APP.route('/ask_stream', methods=['GET', 'POST']) 104 | async def askStream(request: Request) -> Response: 105 | parameter = await core.getRequestParameter(request) 106 | question = parameter.get('question') 107 | style = parameter.get('style', 'balanced') 108 | token = parameter.get('token') 109 | if not question: 110 | return core.GenerateResponse().error(110, '参数不能为空', streamResponse=True) 111 | elif style not in STYLES: 112 | return core.GenerateResponse().error(110, 'style不存在', streamResponse=True) 113 | 114 | if token: 115 | chatG = chat.get(chat.Type.BING, token) 116 | if not chatG: 117 | return core.GenerateResponse().error(110, 'token不存在') 118 | 119 | bot = chatG.bot 120 | else: 121 | token, bot = await chat.generate(chat.Type.BING) 122 | 123 | async def generator() -> AsyncGenerator: 124 | index = 0 125 | info = { 126 | 'answer': '', 127 | 'urls': [], 128 | 'done': False, 129 | 'reset': False, 130 | 'token': token 131 | } 132 | async for final, data in bot.ask_stream(question, conversation_style=getStyleEnum(style), raw=True): 133 | if not final: 134 | if 'sourceAttributions' not in data['arguments'][0].get('messages', [{}])[0]: 135 | continue 136 | 137 | text = data['arguments'][0]['messages'][0]['text'] 138 | answer = text[index:] 139 | index = len(text) 140 | answer = filterAnswer(answer) 141 | if answer: 142 | info['answer'] = answer 143 | yield core.GenerateResponse().success(info, streamFormat=True) 144 | else: 145 | if todayUpperLimit(data): 146 | yield core.GenerateResponse().error(120, '已上限,24小时后尝试', streamFormat=True) 147 | break 148 | 149 | answer = getAnswer(data) 150 | info['answer'] = answer 151 | info['urls'] = getUrl(data) 152 | info['done'] = True 153 | 154 | if needReset(data, answer): 155 | await bot.reset() 156 | info['reset'] = True 157 | 158 | yield core.GenerateResponse().success(info, streamFormat=True) 159 | return StreamingResponse(generator(), media_type='text/event-stream') -------------------------------------------------------------------------------- /view/chatgpt.py: -------------------------------------------------------------------------------- 1 | from typing import Generator 2 | from fastapi import APIRouter, Request, Response 3 | from fastapi.responses import StreamingResponse 4 | from module import core, chat, chatgpt 5 | 6 | CHATGPT_APP = APIRouter() 7 | 8 | @CHATGPT_APP.route('/ask', methods=['GET', 'POST']) 9 | async def ask(request: Request) -> Response: 10 | parameter = await core.getRequestParameter(request) 11 | question = parameter.get('question') 12 | token = parameter.get('token') 13 | model = parameter.get('model') 14 | if not question: 15 | return core.GenerateResponse().error(110, '参数不能为空') 16 | 17 | if token: 18 | chatG = chat.get(chat.Type.CHATGPT, token) 19 | if not chatG: 20 | return core.GenerateResponse().error(110, 'token不存在') 21 | 22 | bot = chatG.bot 23 | modelG = chatG.parameter.get('model') 24 | if model: 25 | if model not in chatgpt.ChatGPT.getModel(): 26 | return core.GenerateResponse().error(110, 'model不存在') 27 | elif model != modelG: 28 | chat.update(token, {'model': model}) 29 | else: 30 | model = modelG 31 | else: 32 | if not model: 33 | return core.GenerateResponse().error(110, '参数不能为空') 34 | elif model not in chatgpt.ChatGPT.getModel(): 35 | return core.GenerateResponse().error(110, 'model不存在') 36 | token, bot = await chat.generate(chat.Type.CHATGPT, {'model': model}) 37 | 38 | return core.GenerateResponse().success({ 39 | 'answer': bot.ask(model, question), 40 | 'token': token 41 | }) 42 | 43 | @CHATGPT_APP.route('/ask_stream', methods=['GET', 'POST']) 44 | async def askStream(request: Request) -> Response: 45 | parameter = await core.getRequestParameter(request) 46 | question = parameter.get('question') 47 | token = parameter.get('token') 48 | model = parameter.get('model') 49 | if not question: 50 | return core.GenerateResponse().error(110, '参数不能为空', streamResponse=True) 51 | 52 | if token: 53 | chatG = chat.get(chat.Type.CHATGPT, token) 54 | if not chatG: 55 | return core.GenerateResponse().error(110, 'token不存在') 56 | 57 | bot = chatG.bot 58 | modelG = chatG.parameter.get('model') 59 | if model: 60 | if model not in chatgpt.ChatGPT.getModel(): 61 | return core.GenerateResponse().error(110, 'model不存在') 62 | elif model != modelG: 63 | chat.update(token, {'model': model}) 64 | else: 65 | model = modelG 66 | else: 67 | if not model: 68 | return core.GenerateResponse().error(110, '参数不能为空') 69 | elif model not in chatgpt.ChatGPT.getModel(): 70 | return core.GenerateResponse().error(110, 'model不存在') 71 | token, bot = await chat.generate(chat.Type.CHATGPT, {'model': model}) 72 | 73 | def generate() -> Generator: 74 | for data in bot.askStream(model, question): 75 | if data['done']: 76 | yield core.GenerateResponse().success({ 77 | 'answer': data['answer'], 78 | 'done': True, 79 | 'token': token 80 | }, streamFormat=True) 81 | break 82 | else: 83 | yield core.GenerateResponse().success({ 84 | 'answer': data['answer'], 85 | 'done': False, 86 | 'token': token 87 | }, streamFormat=True) 88 | return StreamingResponse(generate(), media_type='text/event-stream') -------------------------------------------------------------------------------- /view/claude.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | from module import core, chat 3 | 4 | CLAUDE_APP = APIRouter() 5 | 6 | @CLAUDE_APP.route('/ask', methods=['GET', 'POST']) 7 | async def ask(request: Request) -> Response: 8 | parameter = await core.getRequestParameter(request) 9 | question = parameter.get('question') 10 | token = parameter.get('token') 11 | if not question: 12 | return core.GenerateResponse().error(110, '参数不能为空') 13 | 14 | if token: 15 | chatG = chat.get(chat.Type.CLAUDE, token) 16 | if not chatG: 17 | return core.GenerateResponse().error(110, 'token不存在') 18 | 19 | bot = chatG.bot 20 | chatId = chatG.parameter['chatId'] 21 | else: 22 | token, bot = await chat.generate(chat.Type.CLAUDE) 23 | chatId = chat.get(chat.Type.CLAUDE, token).parameter['chatId'] 24 | 25 | data = bot.send_message(chatId, question) 26 | return core.GenerateResponse().success({ 27 | 'answer': data.answer, 28 | 'token': token 29 | }) -------------------------------------------------------------------------------- /view/ernie.py: -------------------------------------------------------------------------------- 1 | from typing import Generator 2 | from fastapi import APIRouter, Request, Response 3 | from fastapi.responses import StreamingResponse 4 | from module import core, chat 5 | 6 | ERNIE_APP = APIRouter() 7 | 8 | @ERNIE_APP.route('/ask', methods=['GET', 'POST']) 9 | async def ask(request: Request) -> Response: 10 | parameter = await core.getRequestParameter(request) 11 | question = parameter.get('question') 12 | token = parameter.get('token') 13 | if not question: 14 | return core.GenerateResponse().error(110, '参数不能为空') 15 | 16 | if token: 17 | chatG = chat.get(chat.Type.ERNIE, token) 18 | if not chatG: 19 | return core.GenerateResponse().error(110, 'token不存在') 20 | 21 | bot = chatG.bot 22 | else: 23 | token, bot = await chat.generate(chat.Type.ERNIE) 24 | 25 | data = bot.ask(question) 26 | return core.GenerateResponse().success({ 27 | 'answer': data['answer'], 28 | 'urls': data['urls'], 29 | 'token': token 30 | }) 31 | 32 | @ERNIE_APP.route('/ask_stream', methods=['GET', 'POST']) 33 | async def askStream(request: Request) -> Response: 34 | parameter = await core.getRequestParameter(request) 35 | question = parameter.get('question') 36 | token = parameter.get('token') 37 | if not question: 38 | return core.GenerateResponse().error(110, '参数不能为空', streamResponse=True) 39 | 40 | if token: 41 | chatG = chat.get(chat.Type.ERNIE, token) 42 | if not chatG: 43 | return core.GenerateResponse().error(110, 'token不存在', streamResponse=True) 44 | 45 | bot = chatG.bot 46 | else: 47 | token, bot = await chat.generate(chat.Type.ERNIE) 48 | 49 | async def generate() -> Generator: 50 | for data in bot.askStream(question): 51 | yield core.GenerateResponse().success({ 52 | 'answer': data['answer'], 53 | 'urls': data['urls'], 54 | 'done': data['done'], 55 | 'token': token 56 | }, streamFormat=True) 57 | return StreamingResponse(generate(), media_type='text/event-stream') --------------------------------------------------------------------------------