创新实训2024.05.26日志:服务端接口实现——用户开启多个会话

1. 概念图

类似于Kimi,文心一言,chatGPT等市面上主流的大模型,我们的大模型也支持同一个用户的多个会话,并且提供支持联系上下文给出解答的能力。

2. 基于会话的对话

在langchain chatchat这个对langchain框架进行二次封装的第三方框架中,提供了一个chat函数接口:

async def chat(query: str = Body(..., description="用户输入", examples=["恼羞成怒"]),
               conversation_id: str = Body("", description="对话框ID"),
               history_len: int = Body(-1, description="从数据库中取历史消息的数量"),
               history: Union[int, List[History]] = Body([],
                                                         description="历史对话,设为一个整数可以从数据库中读取历史消息",
                                                         examples=[[
                                                             {"role": "user",
                                                              "content": "我们来玩成语接龙,我先来,生龙活虎"},
                                                             {"role": "assistant", "content": "虎头虎脑"}]]
                                                         ),
               stream: bool = Body(False, description="流式输出"),
               model_name: str = Body(LLM_MODELS[0], description="LLM 模型名称。"),
               temperature: float = Body(TEMPERATURE, description="LLM 采样温度", ge=0.0, le=2.0),
               max_tokens: Optional[int] = Body(None, description="限制LLM生成Token数量,默认None代表模型最大值"),
               # top_p: float = Body(TOP_P, description="LLM 核采样。勿与temperature同时设置", gt=0.0, lt=1.0),
               prompt_name: str = Body("default", description="使用的prompt模板名称(在configs/prompt_config.py中配置)"),
               ):

2.1. 提取上下文

这里使用conversation_id对每次会话进行标记,并且在当前对话中,查询数据库中对应会话id的消息记录,使得能够进行联系上下文的对话。

memory = ConversationBufferDBMemory(conversation_id=conversation_id,
                                   llm=model,
                                   message_limit=history_len)

这个ConversationBufferDBMemory的功能是管理和维护与特定对话ID相关的消息缓存,以支持基于历史对话的智能助手响应生成。他继承自Langchain提供的一个基类:BaseChatMemory

class BaseChatMemory(BaseMemory, ABC):
    """Abstract base class for chat memory."""

    chat_memory: BaseChatMessageHistory = Field(default_factory=ChatMessageHistory)
    output_key: Optional[str] = None
    input_key: Optional[str] = None
    return_messages: bool = False

    def _get_input_output(
        self, inputs: Dict[str, Any], outputs: Dict[str, str]
    ) -> Tuple[str, str]:
        if self.input_key is None:
            prompt_input_key = get_prompt_input_key(inputs, self.memory_variables)
        else:
            prompt_input_key = self.input_key
        if self.output_key is None:
            if len(outputs) != 1:
                raise ValueError(f"One output key expected, got {outputs.keys()}")
            output_key = list(outputs.keys())[0]
        else:
            output_key = self.output_key
        return inputs[prompt_input_key], outputs[output_key]

    def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
        """Save context from this conversation to buffer."""
        input_str, output_str = self._get_input_output(inputs, outputs)
        self.chat_memory.add_user_message(input_str)
        self.chat_memory.add_ai_message(output_str)

    def clear(self) -> None:
        """Clear memory contents."""
        self.chat_memory.clear()
  • _get_input_output: 一个私有方法,用于获取输入和输出的键。如果没有设置 input_key 或 output_key,则使用 get_prompt_input_key 函数或默认输出键。
  • save_context: 一个公共方法,用于保存对话上下文到缓冲区。它从 inputs 和 outputs 字典中提取输入和输出字符串,并调用 chat_memory 的方法来添加用户消息和AI消息。
  • clear: 一个公共方法,用于清除记忆内容,通过调用 chat_memory 的 clear 方法实现。

我们要做的就是覆写这个基类,由于我们是基于conversation_id(也即会话id)进行上下文关联记录的,因此这个覆写的子类中,最重要的就是这个会话id属性。此外,提取历史记录时,需要区分ai与人类的角色,因此还需要消息前缀,来区分是ai回复的,还是用户提问的:

    conversation_id: str
    human_prefix: str = "Human"
    ai_prefix: str = "Assistant"
    llm: BaseLanguageModel
    memory_key: str = "history"
    max_token_limit: int = 2000
    message_limit: int = 10

提取上下文完毕后,将生成类似于:

[
    HumanMessage(content="你好,助手。"),
    AIMessage(content="你好!有什么可以帮助你的吗?"),
    HumanMessage(content="我想了解天气预报。"),
    AIMessage(content="请告诉我你的城市。"),
    ...
]

的缓冲区历史记录

2.2. 生成prompt模板

我们想方设法的还原上下文,是为了让ai知道之前和他的对话中都发生过什么。从而基于这个语境回答用户之后的问题。因此从ConversationBuffer中拿到之前的会话信息后,我们就需要生成prompt模板,使得模型能够基于上下文环境回答问题。

这个过程是开发者根据具体需求手动去写prompt进行,随后调用langchain中已经封装好的prompt template库进行优化的。

# 使用memory 时必须 prompt 必须含有memory.memory_key 对应的变量
prompt = get_prompt_template("llm_chat", "with_history")
chat_prompt = PromptTemplate.from_template(prompt)
# 根据conversation_id 获取message 列表进而拼凑 memory
memory = ConversationBufferDBMemory(conversation_id=conversation_id,
                                    llm=model,
                                    message_limit=history_len)
                                    
chain = LLMChain(prompt=chat_prompt, llm=model, memory=memory)

其中from_template与LLMChain都是langchain封装好的,前者是用来优化开发者的prompt模板的,后者是使用优化的prompt模板与历史会话信息(也即memory)进行大模型对话。

而开发者编写的模板,其实就和平时我们与大模型进行对话差不多:

"with_history":
    'The following is a friendly conversation between a human and an AI. '
    'The AI is talkative and provides lots of specific details from its context. '
    'If the AI does not know the answer to a question, it truthfully says it does not know.\\n\\n'
    'Current conversation:\\n'
    '{history}\\n'
    'Human: {input}\\n'
    'AI:',

例如,我们可以在基于上下文会话信息的对话中,告诉他,接下来我们给他的对话是一个人类和一个AI之间的对话,如果AI不知道人类的问题的答案,他就诚实的说不知道。并且告诉他这个对话的格式是先Human,再AI的,对应了我们上面的human/ai_prefix

2.3. 与大模型对话

随后的部分,交给大模型与RAG技术,利用预训练与微调后的大模型自身的能力,通过RAG技术与向量知识库建立连接,检索相关知识,给出回答。

3. 接口实现

首先这个新建会话的功能肯定是异步的,因为一个用户新建一个会话和另一个用户新建会话是没有任何依赖关系的。

在为每个会话生成唯一标识时,我采用了python的uuid。随后向Conversation表中添加这个新的会话的记录。

注意这里防止用户伪造请求,我们要捕获user_id不存在引发的外键约束的异常。

async def new_conversation(nc: NewConv) -> BaseResponse:
    """
    用户建立新的对话:
    1. 为新的对话生成一个全局唯一的uuid
    2. 将这个conversation_id插入到数据库中 同时确保外键约束
    """
    conv_id = uuid.uuid4().hex

    try:
        with Session(engine) as session:
            session.add(Conversation(id=conv_id, conv_name=nc.conv_name, create_time=datetime.datetime.utcnow(),
                                     user_id=nc.user_id))
            session.commit()
        logging.info(f"{nc.user_id} 创建了会话 {conv_id} 会话名{nc.conv_name}")
    except Exception as e:
        logging.error(f"{nc.user_id} 创建会话失败 {e}")
        return BaseResponse(code=200, message="会话创建失败", data={"error": f'{e}'})

    return BaseResponse(code=200, message="会话创建成功", data={"conv_id": conv_id})

随后将这个函数作为post接口调用的回调函数即可。

app.post(self.generate_route_path(["new-conversation"]), tags=self.tag, response_model=BaseResponse,
                 summary="用户创建新会话")(new_conversation)

4. 接口测试

我们可以利用.http文件编写接口测试

假设数据库中现在有一个名叫lyh的用户,他的id是:d078b124cf27413bbb99f6484782e98c

基于这个id,我们进行新会话的创建:

POST <http://127.0.0.1:9090/conversation/new-conversation>
Content-Type: application/json
Accept: application/json

{
  "user_id": "d078b124cf27413bbb99f6484782e98c",
  "conv_name": "dummy_conv"
}

回环地址是因为目前测试环境和开发环境是一个,都是在本机。

成功,返回了会话id。

可以看到数据库中新插入了一条会话记录。

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

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

相关文章

GPT-4 与 GPT-4 Turbo有什么区别?

在不断发展的人工智能和自然语言处理领域&#xff0c;OpenAI 的 GPT 系列一直走在最前沿&#xff0c;彻底改变了机器理解和生成类人文本的方式。每一次迭代&#xff0c;进步都会突破可能性的界限。 最新的条目 GPT-4 和 GPT-4 Turbo 引起了人工智能社区内外的极大兴趣和争论。…

推荐13款常用的Vscode插件,提高前端日常开发效率

1. Live Server Live Server 插件是一个用于前端开发的扩展&#xff0c;它的主要作用是提供一个本地开发服务器&#xff0c;以便实时预览和调试网页应用程序。其最大特点在于热重载&#xff0c;即开发者可实时预览代码效果。 因为Live Server 允许开发者在浏览器中实时预览您正…

【C++】详解深浅拷贝的概念及其区别

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C ⚙️操作环境:Visual Studio 2022 目录 什么是拷贝 什么是浅拷贝 什么是深拷贝 深浅拷贝的区别及使用场景 结语 什么是拷贝 在C编程中&#xff0c;拷贝是一个非常重要的概念&#xff0c;对于理解和使用类和对象起…

浅谈JMeter体系结构

JMeter体系结构详解 JMeter是一款功能强大的开源性能测试工具&#xff0c;广泛应用于Web应用、数据库、FTP服务器等多种场景下的负载和压力测试。其灵活的体系结构设计使得测试计划的创建、执行与结果分析变得高效而直观。本文将深入解析JMeter的三维空间体系结构&#xff0c;…

C++实现定长内存池

项目介绍 本项目实现的是一个高并发的内存池&#xff0c;它的原型是Google的一个开源项目tcmalloc&#xff0c;tcmalloc全称Thread-Caching Malloc&#xff0c;即线程缓存的malloc&#xff0c;实现了高效的多线程内存管理&#xff0c;用于替换系统的内存分配相关函数malloc和fr…

基于UDP的TFTP文件传输-实现网盘上传下载功能

数据传输模式&#xff1a;octet(二进制模式) #include<head.h> char* down_up_request(char* buf,char* filename,int rw,int sockfd,struct sockaddr_in in); int download(struct sockaddr_in in,char* filename,char* buf,int sockfd); int upload(struct sockaddr_in…

数据结构(七)查找

2024年5月26日一稿(王道P291) 7.1 查找的基本概念 7.2 顺序查找和折半查找 7.2.1 顺序查找 7.2.2 折半查找 7.2.3 分块查找 7.3 树形查找 7.3.1 二叉排序树(BST)

FFmpeg+QT播放器实战1---UI页面的设计

1、播放器整体布局的设计 该部分使用QT的UI工具&#xff0c;进行整体页面设置&#xff0c;如下图1所示&#xff1a; 2、控制布局的设计 创建ctrBar的UI页面并进行页面布局设置&#xff0c;如下图2所示&#xff1a; 将图1中ctrBarWind对象提升为ctrBar类(该界面替代原先的控…

Modbus TCP转Profinet网关测试配置案例

本案例采用XD-ETHPN20网关做为Modbus TCP通信协议设备与Profinet通信协议设备连接的桥梁。Modbus TCP是一种基于TCP/IP协议的工业通信协议&#xff0c;而Profinet则是用于太网通信的协议。Modbus TCP转Profinet网关可实现这两种不同协议之间的数据交换和传输&#xff0c;极大地…

实在智能TARS:面向垂直领域自主训练的类GPT大模型

一、写在前面 在数字化浪潮的推动下&#xff0c;企业正寻求突破传统生产力的局限&#xff0c;以实现更高效、更智能的运营模式。实在智能科技有限公司的TARS产品&#xff0c;以其前沿的人工智能技术&#xff0c;为企业注入了新质生产力&#xff0c;引领着智能化转型的新潮流。…

数据结构(三)循环链表

文章目录 一、循环链表&#xff08;一&#xff09;概念&#xff08;二&#xff09;示意图&#xff08;三&#xff09;操作1. 创建循环链表&#xff08;1&#xff09;函数声明&#xff08;2&#xff09;注意点&#xff08;3&#xff09;代码实现 2. 插入&#xff08;头插&#x…

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(十二)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 我们&#xff0c;继续讲一…

【Django项目】 音乐网站spotify复刻

代码&#xff1a;https://github.com/tomitokko/spotify-clone 注&#xff1a;该项目不是自己提供mp3文件&#xff0c;而是使用spotify 的api接口获取。

用实践结果告诉你为啥说 CloudFlare 是赛博菩萨?

最近几天明月都没有更新博客了,主要是接了几个 CloudFlare 代维配置的活儿,有需要加速优化的,有需要排除疑难故障的,有需要提高防御攻击能力的甚至还有纯粹为了体验“打不死”装逼需要的。总之,各种各样的需求,五花八门的,好在 CloudFlare 都能一一满足,最主要的是这些…

C++入门:从C语言到C++的过渡(3)

目录 1.内联函数 1.1内联函数的定义 1.2特性 2.auto关键字 2.1auto的简介 2.2注意事项 3.范围for 4.nullptr空指针 1.内联函数 在C语言中&#xff0c;无论使用宏常量还是宏函数都容易出错&#xff0c;而且无法调试。而C为了弥补这一缺陷&#xff0c;引入了内联函数的概…

【NumPy】关于numpy.clip()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

前端手写文件上传;使用input实现文件拖动上传

使用input实现文件拖动上传 vue2代码&#xff1a; <template><div><div class"drop-area" dragenter"highlight" dragover"highlight" dragleave"unhighlight" drop"handleDrop"click"handleClick&quo…

嵌入式C语言中结构体使用详解

各位开发者大家好,今天给大家分享一下,嵌入式C语言中结构体的使用方法。 第一个:内存对齐 内存对齐是指一个数据类型在内存中存放时,对其地址的要求。简单来说内存对齐就是使得其内存地址是该类型大小的整数倍,例如 double 类型的变量,其内存地址需要是8的倍数(double大…

数据结构(四)

数据结构&#xff08;四&#xff09; 算法算法的特征算法和程序的区别怎么样评判一个算法的好坏 常见的查找算法线性树状哈希查找构建哈希函数的方法质数求余法解决冲突 算法 一堆指令的有序集合 算法的特征 唯一性&#xff1a;每一句话只有一种解释 有穷性&#xff1a;算法能…

澳大利亚.德国-门户媒体投放通稿:需要注意什么地方

概述 在现代社会&#xff0c;新闻媒体的投放成为企业和组织宣传推广的重要手段之一。澳大利亚和德国作为全球重要的经济和科技中心&#xff0c;其新闻媒体也备受关注。本文将介绍澳大利亚和德国的一些主要新闻媒体&#xff0c;并讨论发表新闻稿时需要注意的地方。 澳大利亚媒…