Chainlit接入FastGpt接口完美对接,实现全新的用户聊天界面

news2024/11/16 7:33:02

前言

由于fastgpt只提供了一个分享用的网页应用,网页访问地址没法自定义,虽然可以接入NextWeb/ChatGPT web等开源应用。但是如果我们想直接给客户应用,还需要客户去设置配置,里面还有很多我们不想展示给客户的东西怎么办?于是,我使用Chainlit实现了一个无缝快速接入fastgpt实现自定义用户使用界面的应用,代码清晰简单。还可以自定义logo、欢迎语、网站图标等。
之前写一个一篇文章 《Chainlit接入FastGpt接口快速实现自定义用户聊天界面》 文章中实现了Fastgpt的对话接口,可以实现聊天,但是有些人想。接入Fastgpt的欢迎语,获取聊天历史记录等。本此的代码更新可以实现这些,下面我主要讲一些这次代码更新实现的亮点吧!

1. 接入Fastgpt后,可以自动更新成你的网站图标和网站名称

在这里插入图片描述

2. 接入欢迎语

在这里插入图片描述

3. 接入聊天记录

在这里插入图片描述

  • 不仅可以实现聊天记录的自动添加还可实现删除,搜索聊天记录的功能,原fastgpt的分享聊天网页可没有聊天搜索功能呦!

4 可以开启黑白主题的切换

在这里插入图片描述

5.可以自定义修改访问路径

当然这个功能需要你自己修改启动地址

快速开始

获取Fastgpt的信息

获取fastgpt的base_url和share_id

登录fastgpt后台,在工作台里,点击自己创建的AI应用,点击发布渠道,点击免登录窗口,创建新连接,创建后点击开始使用按钮。

  • 复制base_urlshare_id,后面需要配置到Chainlit的环境变量中

在这里插入图片描述

获取fastgpt的API_KEY

登录fastgpt后台,在工作台里,点击自己创建的AI应用,点击发布渠道,点击API访问创建,访问APIKEY.

  • 只需要复制API_KEY即可,后面需要配置到Chainlit的环境变量中
    在这里插入图片描述

Chainlit网页搭建

创建一个文件夹,例如“chainlit_fastgpt”

mkdir chainlit_fastgpt

进入 chainlit_chat文件夹下,执行命令创建python 虚拟环境空间(需要提前安装好python sdkChainlit 需要python>=3.8。,具体操作,由于文章长度问题就不在叙述,自行百度),命令如下:

python -m venv .venv
  • 这一步是避免python第三方库冲突,省事版可以跳过
  • .venv是创建的虚拟空间文件夹可以自定义

接下来激活你创建虚拟空间,命令如下:

#linux or mac
source .venv/bin/activate
#windows
.venv\Scripts\activate

在项目根目录下创建requirements.txt,内容如下:

chainlit~=1.1.402
aiohttp~=3.10.5
requests~=2.32.3
literalai~=0.0.607

在项目根目录下创建app.py文件,代码如下:

import hashlib
import json
import os
from typing import Optional, Dict

import aiohttp
import chainlit as cl
import chainlit.data as cl_data
import requests

from fastgpt_data import FastgptDataLayer, now, share_id, app_name, welcome_text

fastgpt_base_url = os.getenv("FASTGPT_BASE_URL")
fastgpt_api_key = os.getenv("FASTGPT_API_KEY")
fastgpt_api_detail = os.getenv("FASTGPT_API_DETAIL", False)

cl_data._data_layer = FastgptDataLayer()
cl.config.ui.name = app_name


def download_logo():
    local_filename = "./public/favicon.svg"
    directory = os.path.dirname(local_filename)
    os.makedirs(directory, exist_ok=True)
    # Streaming, so we can iterate over the response.
    with requests.get(f"{fastgpt_base_url}/icon/logo.svg", stream=True) as r:
        r.raise_for_status()  # Check if the request was successful
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192):
                # If you have chunk encoded response uncomment if
                # and set chunk_size parameter to None.
                f.write(chunk)


download_logo()


@cl.on_chat_start
async def chat_start():
    if welcome_text:
        # elements = [cl.Text(content=welcomeText, display="inline")]
        await cl.Message(content=welcome_text).send()


@cl.on_message
async def handle_message(message: cl.Message):
    msg = cl.Message(content="")
    url = f"{fastgpt_base_url}/api/v1/chat/completions"
    print('message.thread_id',message.thread_id)
    headers = {
        "Authorization": f"Bearer {fastgpt_api_key}",
        "Content-Type": "application/json"
    }
    data = {
        "messages": [
            {
                "role": "user",
                "content": message.content
            }
        ],
        "variables": {
            "cTime": now
        },
        "responseChatItemId": message.id,
        "shareId": share_id,
        "chatId": message.thread_id,
        "appType": "advanced",
        "outLinkUid": cl.context.session.user.identifier,
        "detail": fastgpt_api_detail,
        "stream": True
    }
    async for data in fetch_sse(url, headers=headers, data=json.dumps(data), detail=fastgpt_api_detail):
        delta = data['choices'][0]['delta']
        if delta:
            await msg.stream_token(delta['content'])
    await msg.send()


@cl.header_auth_callback
def header_auth_callback(headers: Dict) -> Optional[cl.User]:
    print(headers)
    # 创建一个md5 hash对象
    md5_hash = hashlib.md5()
    user_agent_bytes = headers.get('user-agent').encode('utf-8')

    # 更新这个hash对象的内容
    md5_hash.update(user_agent_bytes)

    # 获取md5哈希值的十六进制表示形式
    md5_hex_digest = md5_hash.hexdigest()
    out_link_uid = md5_hex_digest
    print("MD5加密后的结果:", md5_hex_digest)
    return cl.User(identifier=out_link_uid, display_name="visitor")


@cl.on_chat_resume
async def on_chat_resume():
    pass


async def fetch_sse(url, headers, data, detail):
    async with aiohttp.ClientSession() as session:
        async with session.post(url, headers=headers, data=data) as response:
            async for line in response.content:
                if line:  # 过滤掉空行
                    data = line.decode('utf-8').rstrip('\n\r')
                    # print(f"Received: {data}")
                    # 检查是否为数据行,并且是我们感兴趣的事件类型
                    if detail:
                        if data.startswith('event:'):
                            event_type = data.split(':', 1)[1].strip()  # 提取事件类型
                        elif data.startswith('data:') and event_type == 'flowNodeStatus':
                            data = data.split(':', 1)[1].strip()
                            flowNodeStatus = json.loads(data)
                            current_step = cl.context.current_step
                            current_step.name = flowNodeStatus['name']
                        elif data.startswith('data:') and event_type == 'answer':
                            data = data.split(':', 1)[1].strip()  # 提取数据内容
                            # 如果数据包含换行符,可能需要进一步处理(这取决于你的具体需求)
                            # 这里我们简单地打印出来
                            if data != '[DONE]':
                                yield json.loads(data)
                    else:
                        if data.startswith('data:'):
                            data = data.split(':', 1)[1].strip()  # 提取数据内容
                            # 如果数据包含换行符,可能需要进一步处理(这取决于你的具体需求)
                            # 这里我们简单地打印出来
                            if data != '[DONE]':
                                yield json.loads(data)

  • 传入的model,temperature等参数字段均无效,这些字段由编排决定,不会根据 API 参数改变。

  • 不会返回实际消耗Token值,如果需要,可以设置detail=true,并手动计算 responseData 里的tokens值。

在项目根目录下创建fastgpt_data.py文件,代码如下:

import json
import os
import uuid
from typing import Optional, List, Dict

import requests
from chainlit import PersistedUser
from chainlit.data import BaseDataLayer
from chainlit.types import PageInfo, ThreadFilter, ThreadDict, Pagination, PaginatedResponse
from literalai.helper import utc_now

fastgpt_base_url = os.getenv("FASTGPT_BASE_URL")
share_id = os.getenv("FASTGPT_SHARE_ID")
now = utc_now()
user_cur_threads = []
thread_user_dict = {}


def change_type(user_type: str):
    if user_type == 'AI':
        return 'assistant_message'
    if user_type == 'Human':
        return 'user_message'


def get_app_info():
    with requests.get(
            f"{fastgpt_base_url}/api/core/chat/outLink/init?chatId=&shareId={share_id}&outLinkUid=123456"
    ) as resp:
        app = {}
        if resp.status_code == 200:
            res = json.loads(resp.content)
            app = res.get('data').get('app')
            appId = res.get('data').get('appId')
            app['id'] = appId
        return app


app_info = get_app_info()

app_id = app_info.get('id')
app_name = app_info.get('name')
welcome_text = app_info.get('chatConfig').get('welcomeText')


def getHistories(user_id):
    histories = []
    if user_id:
        with requests.post(
                f"{fastgpt_base_url}/api/core/chat/getHistories",
                data={"shareId": share_id, "outLinkUid": user_id}
        ) as resp:
            if resp.status_code == 200:
                res = json.loads(resp.content)
                data = res["data"]
                print(data)
                histories = [
                    {
                        "id": item["chatId"],
                        "name": item["title"],
                        "createdAt": item["updateTime"],
                        "userId": user_id,
                        "userIdentifier": user_id
                    }
                    for item in data
                ]
        if user_cur_threads:
            thread = next((t for t in user_cur_threads if t["userId"] == user_id), None)
            if thread:  # 确保 thread 不为 None
                thread_id = thread.get("id")
                if histories:
                    # 检查 thread 的 ID 是否已存在于 threads 中
                    if not any(t.get("id") == thread_id for t in histories):
                        histories.insert(0, thread)
                else:
                    # 如果 threads 是空列表,则直接插入 thread
                    histories.insert(0, thread)
        for item in histories:
            thread_user_dict[item.get('id')] = item.get('userId')
    return histories


class FastgptDataLayer(BaseDataLayer):

    async def get_user(self, identifier: str):
        print('get_user', identifier)
        return PersistedUser(id=identifier, createdAt=now, identifier=identifier)

    async def update_thread(
            self,
            thread_id: str,
            name: Optional[str] = None,
            user_id: Optional[str] = None,
            metadata: Optional[Dict] = None,
            tags: Optional[List[str]] = None,
    ):
        print('---------update_thread----------',thread_id)
        thread = next((t for t in user_cur_threads if t["userId"] == user_id), None)
        if thread:
            if thread_id:
                thread["id"] = thread_id
            if name:
                thread["name"] = name
            if user_id:
                thread["userId"] = user_id
                thread["userIdentifier"] = user_id
            if metadata:
                thread["metadata"] = metadata
            if tags:
                thread["tags"] = tags
            thread["createdAt"] = utc_now()
        else:
            print('---------update_thread----------thread_id ', thread_id, name)
            user_cur_threads.append(
                {
                    "id": thread_id,
                    "name": name,
                    "metadata": metadata,
                    "tags": tags,
                    "createdAt": utc_now(),
                    "userId": user_id,
                    "userIdentifier": user_id,
                }
            )

    async def get_thread_author(self, thread_id: str):
        print('get_thread_author')
        return thread_user_dict.get(thread_id, None)

    async def list_threads(
            self, pagination: Pagination, filters: ThreadFilter
    ) -> PaginatedResponse[ThreadDict]:
        threads = []
        if filters:
            threads = getHistories(filters.userId)
        search = filters.search if filters.search else ""
        filtered_threads = [thread for thread in threads if search in thread.get('name', '')]
        start = 0
        if pagination.cursor:
            for i, thread in enumerate(filtered_threads):
                if thread["id"] == pagination.cursor:  # Find the start index using pagination.cursor
                    start = i + 1
                    break
        end = start + pagination.first
        paginated_threads = filtered_threads[start:end] or []
        has_next_page = len(paginated_threads) > end
        start_cursor = paginated_threads[0]["id"] if paginated_threads else None
        end_cursor = paginated_threads[-1]["id"] if paginated_threads else None
        return PaginatedResponse(
            pageInfo=PageInfo(
                hasNextPage=has_next_page,
                startCursor=start_cursor,
                endCursor=end_cursor,
            ),
            data=paginated_threads,
        )

    async def get_thread(self, thread_id: str):
        print('get_thread', thread_id)
        user_id = thread_user_dict.get(thread_id, None)
        thread = None
        if user_id:
            params = {
                'chatId': thread_id,
                'shareId': share_id,
                'outLinkUid': user_id,
            }
            with requests.get(
                    f"{fastgpt_base_url}/api/core/chat/outLink/init",
                    params=params,
            ) as resp:
                if resp.status_code == 200:
                    res = json.loads(resp.content)
                    data = res["data"]
                    if data:
                        history = data['history']
                        files = []
                        texts = []
                        for item in history:
                            for entry in item['value']:
                                if entry.get('type') == 'text':
                                    text = {
                                        "id": item["_id"],
                                        "threadId": thread_id,
                                        "name": item["obj"],
                                        "type": change_type(item["obj"]),
                                        "input": None,
                                        "createdAt": utc_now(),
                                        "output": entry.get('text').get('content'),
                                    }
                                    texts.append(text)
                                if entry.get('type') == 'file':
                                    file = {
                                        "id": str(uuid.UUID),
                                        "threadId": thread_id,
                                        "forId": item["_id"],
                                        "name": entry.get('file').get('name'),
                                        "type": entry.get('file').get('type'),
                                        "url": entry.get('file').get('url'),
                                        "display": "inline",
                                        "size": "medium"
                                    }
                                    files.append(file)
                        thread = {
                            "id": thread_id,
                            "name": data.get("title", ''),
                            "createdAt": utc_now(),
                            "userId": "admin",
                            "userIdentifier": "admin",
                            "metadata": {"appId": data["appId"]},
                            "steps": texts,
                            "elements": files,
                        }
                        return thread
        return thread

    async def delete_thread(self, thread_id: str):
        print('delete_thread')
        thread = next((t for t in user_cur_threads if t["id"] == thread_id), None)
        user_id = thread_user_dict.get(thread_id, None)
        if thread:
            user_cur_threads.remove(thread)
        if user_id:
            params = {
                'appId': app_id,
                'chatId': thread_id,
                'shareId': share_id,
                'outLinkUid': user_id,
            }
            requests.get(
                f"{fastgpt_base_url}/api/core/chat/delHistory",
                params=params
            )

在项目根目录下创建.env环境变量,配置如下:

CHAINLIT_AUTH_SECRET="xOIPIMBGfI7N*VK6O~KOVIRC/cGRNSmk%bmO4Q@el647hR?^mdW6=8KlQBuWWTbk"
FASTGPT_BASE_URL="https://share.fastgpt.in"
FASTGPT_API_KEY="fastgpt-key"
FASTGPT_SHARE_ID=""
FASTGPT_API_DETAIL=False
  • 项目根目录下,执行 chainlit create-secret命令,可以获得CHAINLIT_AUTH_SECRET
  • FASTGPT_BASE_URL 为你Fastgpt服务器,网页端分享地址的的base_url
  • FASTGPT_API_KEY 为你Fastgpt服务器f发布渠道中->API访问的密匙。
  • FASTGPT_SHARE_ID 为你Fastgpt服务器f发布渠道中->免登录窗口中的url的中参数shareId=后面的值。

执行以下命令安装依赖:

pip install -r .\requirements.txt
  • 安装后,项目根目录下会多出.chainlit.files文件夹和chainlit.md文件

运行应用程序

要启动 Chainlit 应用程序,请打开终端并导航到包含的目录app.py。然后运行以下命令:

 chainlit run app.py -w   
  • -w标志告知 Chainlit 启用自动重新加载,因此您无需在每次更改应用程序时重新启动服务器。您的聊天机器人 UI 现在应该可以通过http://localhost:8000访问。
  • 自定义端口可以追加--port 80

启动后界面如下:
在这里插入图片描述

  • 由于时间关系,这个应用和fastgpt的文件上传接口、语音对话还未实现,后续会在更新一次,实现完美对接!

相关文章推荐

《使用 Xinference 部署本地模型》
《Fastgpt接入Whisper本地模型实现语音输入》
《Fastgpt部署和接入使用重排模型bge-reranker》
《Fastgpt部署接入 M3E和chatglm2-m3e文本向量模型》
《Fastgpt 无法启动或启动后无法正常使用的讨论(启动失败、用户未注册等问题这里)》
《vllm推理服务兼容openai服务API》
《vLLM模型推理引擎参数大全》
《解决vllm推理框架内在开启多显卡时报错问题》
《Ollama 在本地快速部署大型语言模型,可进行定制并创建属于您自己的模型》

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

# 利刃出鞘_Tomcat 核心原理解析(九)-- Tomcat 安全

利刃出鞘_Tomcat 核心原理解析(九)-- Tomcat 安全 一、Tomcat专题 - Tomcat安全 - 配置安全 1、 删除 tomcat 的 webapps 目录下的所有文件,禁用 tomcat 管理界面. 如下目录均可删除: D:\java-test\apache-tomcat-8.5.42-wind…

轻松实现PDF转图片!2024四大实用工具推荐!

有时候我们需要将PDF文件中的内容转换为图片格式,以便在不同的平台和设备上更好地展示和分享。"PDF转图片"这一需求催生了众多转换工具的出现,它们以高效、便捷的服务帮助用户轻松实现格式转换。 福昕PDF转换大师(365客户端&#…

JAVA Future类详解

在编程中,Java中的"Future"是一个接口,代表是作为主线程开辟的一个分支任务,处理耗时的业务,并且可以可以为主线程最终返回异步计算的结果。此外,它提供了检查计算是否完成,等待其完成&#xff0…

ISP 3A 算法:自动曝光(AE)中的平均亮度法详解

在自动曝光(AE)算法中,平均亮度法是一种经典且广泛应用的技术。它通过计算场景中所有像素的平均亮度来确定最佳曝光设置,从而保证图像的整体亮度处于适当的水平。尽管该方法相对简单,但它在AE算法中扮演着重要的角色&a…

仕考网:专科考公好考吗?有岗位吗?

2024年,国家公务员以及大多数省市的公务员考试接受至少拥有大专学历的考生。某些特定职位,例如上海市和北京市的岗位,可能要求考生必须持有本科或以上学历才能参与考试。 属于国家公务员考试、省直属单位、市直属单位以及中央直属单位的职位…

奥斯卡影后斯特里普和马丁肖特在最近的这次约会后再次引发热议

奥斯卡影后斯特里普和马丁肖特在最近的这次约会后再次引发热议 2024-08-24 00:00 发布于:河北省 自从梅丽尔斯特里普于 2023 年与相恋多年的丈夫分手以来,媒体对她的关注度只增不减。毕竟,这是好莱坞最著名的女演员 40 多年来第一次单身&…

手机投屏到电脑显示(Android -> win11)

文章目录 1.电脑安装“无线显示”功能2.系统->投影到此电脑3.手机进入无线投屏功能更 1.电脑安装“无线显示”功能 选择 “开始 ”> “设置 ”> “系统 > 投影到此 PC ”。 在 Add the ‘Wireless Display’ optional feature to project to this PC (…

ELK进阶-安全认证设置流程介绍

目录 一、前言 二、ElasticSearch的安全认证配置 设置ElasticSearch的安全认证功能(最低) 启用TLS/SSL加密通信 (1)生成CA证书 (2)生成transport证书 (3)生成http证书 &#…

小米汽车每台亏6万,背后的认知格局

最近,小米汽车传出一则新闻:一台车亏损了6万块钱。这则消息一出,遭到了同行车企负责人的谩骂,并对雷军进行了直接攻击。对方指责雷军:“你亏损6万块钱还卖车干什么?你就是因为不亏卖不掉,所以才…

宠物毛发不除对身体有什么危害?除毛好的小型空气净化器分享

身为呼吸科医生,我亲眼见证过不少因环境因素导致的呼吸问题。在享受养猫乐趣的同时,我也时刻关注着它们可能带来的健康隐患,尤其是毛发中的细菌问题。我发现,不少年轻人对宠物毛发可能携带的细菌威胁缺乏足够认识。今天&#xff0…

golang多版本管理工具g

一、golang多版本管理工具g 一)g简介 g是一个Linux、macOS、Windows下的命令行工具,可以提供一个便捷的多版本go环境的管理和切换 二)g特性 支持列出可供安装的go版本号支持列出已安装的go版本号支持在本地安装多个go版本支持卸载已安装的go…

数据缓存软件Redis推出Redis 8 这更改许可证后首个重大版本更新

2024 年 3 月流行的数据库缓存应用 Redis 宣布修改开源许可证,此次修改许可证的目的本质上就是避免大型云计算公司白嫖,例如亚马逊的 AWS 等。AWS 等云计算服务商基于 Redis 推出托管服务并向客户收费,作为开发商 Redis 并没有获得收益&#…

还没做过电子版招生简章的老师看过来!

随着科技的不断发展,传统的招生简章逐渐走向数字化,电子版招生简章以其便捷、环保、可更新等优势,越来越受到各学校的青睐。如果你还不会制作电子版招生简章,那就赶紧来看看这篇文章吧! 一、电子版招生简章的优势 1. …

【PHP入门教程】PHPStudy环境搭建+composer创建项目

文章目录 PHP 的历史PHP 的用途PHP 的特点和优势PHP 环境搭建环境准备安装window 安装CentOS / Ubuntu / Debian 安装 第一个Hello World使用Apache服务运行命令行运行代码 Composer安装 Composer:安装途中报错解决:初始化项目创建文件最终文件目录Compo…

记录练习reserve(强网先锋ADmingyue)

1.强网 64位无壳,ida拖入。 复制qiangwangxianfengjiushini到v6缓冲区,拷贝ZmxhZ3ttYWZha3VhaWxhaXFpYW5kYW9ifQ前36个字符(正好是36)到v5缓冲区,因为存在“”符号,应该是base64,下文for循环遍历v4与v5的每个字符(即解…

k8s跨节点后pod无法访问

场景 k8s在node1节点部署nginx后, 除node1外,主节点以及node2节点都无法正常访问nginx 并且主节点以及node2节点都无法ping通node1节点上的pod 网络插件为calico 并且也没有相关路由信息 解决方案 启动tunl0接口,因为calico需要使用tunl0网…

Wireshark_UDP_v7.0

Wireshark_UDP_v7.0 就随便在浏览器上访问点什么,然后抓包,过滤输入udp,然后回答问题。 本来试的哔哩哔哩视频和在线扫雷,emmm没有udp,最后还是得原神启动 source port、destination port、length、checksum source…

【力扣】地下城游戏

🔥博客主页: 我要成为C领域大神🎥系列专栏:【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 恶魔们抓住了公…

SQLSugar入门篇:快速上手与基础概念

文章目录 前言一、框架简介二、环境搭建与安装1.net core和.net5/.net6/.net7/.net8/.net9环境2.net framework4.6 环境 三、环境搭建与安装1.对象说明2.原生模式:SqlSugarClient3.单例模式:SqlSugarScope4.多库用法5.连接参数 四、实体类映射1.实体使用…

大数据技术之Flume事务及内部原理(3)

目录 Flume Agent 架构概述 Flume Agent 内部工作流程 Flume Agent 的配置 Flume Agent内部重要组件 ChannelSelector SinkProcessor Apache Flume 是一个分布式的、可靠的、可用的服务,用于有效地收集、聚合和移动大量日志数据。它具有简单灵活的架构&#x…