您现在的位置是:网站首页> AI人工智能
开源的的AI模型及调用平台
- AI人工智能
- 2025-03-07
- 2295人已阅读
开源的的AI模型及调用平台
AI模型的格式有哪些文件后缀分别是啥及各种格式模型的使用方法
开源模型
简单几步微调Llama3变身中文大模型!PDF清洗外加Ollama和LM Studio本地加载微调好的大模型
调用框架
Dify 一个开源 LLM 应用开发平台【与coze对标】,点击查看扣子教程
Windows下中文微调Llama3,单卡8G显存只需5分钟,模型可接入GPT4All、Ollama,实现CPU推理聊天,附Colab一键训练脚本
一键部署本地私人专属知识库,开源免费!可接入GPT-4、Llama 3、Gemma、Kimi等几十种大模型,零代码集成
Llama 3.1 一键本地部署!Meta 最强开源大模型,100%保证成功,无需GPU也能运行,可离线使用
5分钟做一个企业级AI问答系统,纯开源解决方案。老板乐开花 feat.FastGPT 上手体验
喂饭教程!100%实现Ollama+RAG本地化知识库!Ollama+RAG安装部署-带你全面认识RAG框架
微软AI Dev Gallery开源项目帮助Windows开发人员学习如何将具有本地模型和API的AI添加到windows应用程序中
大模型RAG企业项目实战:手把手带你搭建一套完整的RAG系统
5分钟学会Ollama + Open WebUI搭建本地大模型Web交互界面
ollama专题
llama与ollama的区别
LLaMA 和 Ollama 有以下区别:
概念性质:
LLaMA:是由 Meta(Facebook 母公司)开发的开源语言模型系列,属于人工智能模型范畴,如 LLaMA-1、LLaMA-2 和 LLaMA-3 等版本,用于自然语言处理任务。
Ollama:是一个独立开发的开源框架,用于简化大型语言模型的部署和使用,为开发者提供了将语言模型集成到应用程序中的平台。
功能用途:
LLaMA:作为基础语言模型,可用于文本生成、问答系统、文本分类、翻译等多种自然语言处理任务,为研究和开发提供基础模型支持,用户可对其进行微调以适应特定任务和领域需求。
Ollama:主要用于管理和运行各种大型语言模型,包括 LLaMA 及其它兼容模型。它提供了命令行界面和类似 API 的接口,方便用户在本地轻松下载、切换、管理模型,还支持模型的自定义和调优。
应用场景:
LLaMA:适合研究人员和开发者进行人工智能相关的研究和开发工作,如探索新的自然语言处理算法、开发智能聊天机器人、进行文本生成和内容创作等。在对模型有深入理解和调优能力的情况下,能充分发挥其性能优势。
Ollama:适用于注重数据隐私和希望在本地快速部署、测试和使用大型语言模型的用户和开发者。例如,企业在内部进行数据处理和分析时,可通过 Ollama 在本地运行模型,确保数据不泄露;开发者在进行原型开发和实验时,也能利用 Ollama 快速尝试不同模型。
技术特点:
LLaMA:具有不同的参数规模,以适应不同的应用需求和硬件资源条件。在大量文本数据上进行预训练,具备强大的语言理解和生成能力,但可能需要较高的硬件资源来运行和进行调优操作。
Ollama:强调易用性和可扩展性,通过优化和封装,降低了运行大型语言模型的门槛,使非专业用户也能轻松上手。支持多种模型,可与其他服务集成,且注重隐私保护,模型在本地运行,数据由用户自己掌控。
GPT4All语言大模型调用平台
对话模板:
{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}
聊天模板Jinja format:
1.Jinja 概述
Jinja 是一个现代的、功能强大的模板引擎,主要用于 Python 环境,但也可以在其他语言环境中使用。它用于将模板(通常是包含占位符和逻辑的文本文件)与数据相结合,生成最终的文本输出,如 HTML 页面、配置文件、电子邮件内容等。
Jinja 最初是为 Web 开发设计的,特别是用于生成动态网页。它可以在服务器端(如 Flask、Django 等 Python Web 框架)与应用程序逻辑一起工作,根据不同的用户请求和数据动态地生成 HTML。
2.Jinja 格式的基本语法元素
变量替换:
使用{{ variable_name }}语法来在模板中插入变量的值。例如,如果在 Python 中有一个变量user_name = "John",在 Jinja 模板中可以通过{{ user_name }}来显示 “John”。
变量可以是任何 Python 数据类型,如字符串、数字、列表、字典等。对于字典,可以使用{{ my_dict['key'] }}来访问特定键的值。
控制结构:
条件判断:使用{% if condition %}、{% elif condition %}和{% else %}来实现条件逻辑。例如:
jinja
{% if user_age >= 18 %}
You are an adult.
{% else %}
You are a minor.
{% endif %}
循环结构:使用{% for item in iterable %}来进行循环。例如,对于一个列表fruits = ["apple", "banana", "cherry"],可以在模板中这样使用:
jinja
<ul>
{% for fruit in fruits %}
<li>{{ fruit }}</li>
{% endfor %}
</ul>
模板继承和块(Blocks):
Jinja 允许模板继承,这对于构建具有一致布局的网页非常有用。基本模板可以定义页面的整体结构,包括 HTML 的<head>、<body>等部分,而子模板可以继承这个基本模板并填充或修改特定的部分。
例如,一个基本模板(base.html)可能有这样的结构:
html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
子模板(index.html)可以继承这个基本模板并填充块:
html
{% extends "base.html" %}
{% block title %}Home Page{% endblock %}
{% block content %}
<h1>Welcome to the Home Page</h1>
{% endblock %}
过滤器(Filters):
过滤器用于修改变量的值,在变量后面使用管道符(|)和过滤器名称来应用。例如,{{ variable_name | upper }}将变量的值转换为大写形式。
一些常见的过滤器包括lower(转换为小写)、title(每个单词首字母大写)、date(格式化日期)等。例如,{{ post_date | date('%Y-%m-%d') }}可以将日期对象格式化为指定的字符串格式。
3.Jinja 在不同场景中的应用
Web 开发:
在 Flask 等 Web 框架中,Jinja 用于生成 HTML 页面。它可以根据用户的请求、数据库中的数据等来动态地构建页面内容。例如,根据用户的登录状态显示不同的导航菜单,或者根据数据库中的文章列表生成文章列表页面。
配置文件生成:
可以使用 Jinja 来生成配置文件。例如,在部署软件到不同环境(开发、测试、生产)时,通过传递不同的环境变量到 Jinja 模板,生成具有不同参数设置的配置文件。比如,在一个数据库配置模板中,根据环境变量来设置数据库的主机名、用户名和密码等参数。
代码生成:
Jinja 也可以用于生成代码。例如,自动生成数据库迁移脚本或者 API 客户端代码等。通过定义模板,根据特定的数据结构和规则来生成代码片段,提高代码生成的效率和一致性。
***模型gguf可以在huggingface搜索下载或魔塔社区搜索下载
Python语言使用
pip install gpt4all
from gpt4all import GPT4All
model = GPT4All("orca-mini-3b-gguf2-q4_0.gguf")
model = GPT4All(model_name='orca-mini-3b-gguf2-q4_0.gguf')
with model.chat_session():
reponse1 = model.generate(prompt='hello', temp=0)
response2 = model.generate(prompt='write me a short poem', temp=0)
response3 = model.generate(prompt='thank you', temp=0)
print(model.current_chat_session) === "Output"
json [ { 'role': 'user', 'content': 'hello' }, { 'role': 'assistant', 'content': 'What is your name?' }, { 'role': 'user', 'content': 'write me a short poem' }, { 'role': 'assistant', 'content': "I would love to help you with that! Here's a short poem I came up with:\nBeneath the autumn leaves,\nThe wind whispers through the trees.\nA gentle breeze, so at ease,\nAs if it were born to play.\nAnd as the sun sets in the sky,\nThe world around us grows still." }, { 'role': 'user', 'content': 'thank you' }, { 'role': 'assistant', 'content': "You're welcome! I hope this poem was helpful or inspiring for you. Let me know if there is anything else I can assist you with." } ]
在上下文中使用 GPT4All 模型时chat_session():
连续的聊天交流会被考虑在内,并且在会话结束之前不会被丢弃;只要模型有能力。
系统提示符被插入到模型上下文的开头。
传递到的每个提示都generate()包含在适当的提示模板中。如果您传递allow_download=False 给 GPT4All 或使用的模型不是来自官方模型列表,则必须使用 prompt_template参数传递提示模板chat_session()。
注意:如果您不使用chat_session(),则对 的调用generate()将不会包含在提示模板中。这将导致模型继续提示而不是回答它。如有疑问,请使用聊天会话,因为许多较新的模型被设计为专门与提示模板一起使用。
指定模型文件夹
模型文件夹可以model_path在创建GPT4All实例时通过参数设置。下面的示例与未提供时相同;也就是说,~/.cache/gpt4all/是默认文件夹。
from pathlib import Path
from gpt4all import GPT4All
model = GPT4All(model_name='orca-mini-3b-gguf2-q4_0.gguf', model_path=Path.home() / '.cache' / 'gpt4all')
不允许在线下载模型
from gpt4all import GPT4All
model = GPT4All('orca-mini-3b-gguf2-q4_0.gguf', allow_download=False)
system_prompt = '### System:\nYou are an AI assistant that follows instruction extremely well. Help as much as you can.\n\n'
prompt_template = '### User:\n{0}\n\n### Response:\n'
with model.chat_session(system_prompt=system_prompt, prompt_template=prompt_template):
import json
import gpt4all
gptj = gpt4all.GPT4All("ggml-gpt4all-j-v1.3-groovy")
messages = [{"role": "user", "content": "Can you explain what is a large language model?"}]
ret = gptj.chat_completion(messages)
messages.append(ret["choices"][0]["message"])
messages.append({"role": "user", "content": "Can you give some examples applications?"})
ret = gptj.chat_completion(messages)
messages.append(ret["choices"][0]["message"])
messages.append({"role": "user", "content": "Are there any limitations?"})
ret = gptj.chat_completion(messages)
消息。append(ret[“choices”][0][“message”])
消息。append({“role”: “user”, “content”: “用两句话总结上述内容。})
RET = GPTJ。chat_completion(消息)
打印(JSON.转储(消息,缩进 = 4))
打印(JSON.转储(ret, 缩进=4))
您多次调用了该模型。每次它响应时,您都会获取输出并将其附加到消息列表中,以便累积上下文。然后,添加新对话框并再次调用该模型。您需要收集消息,因为每次调用模型时,它都会在不知道上一个对话的情况下重新开始。因此,您的工作是保留历史记录以提醒模型其先前的反应。
LM Studio模型调用平台
Ollama 模型调用平台
软件主页点击Models进入可以看到支持的model
点击一个如llama3,可以查看命令函启动方式
启动:ollama run llama3:8b
API调用
Example using curl:
curl -X POST http://localhost:11434/api/generate -d '{
"model": "llama3",
"prompt":"Why is the sky blue?"
}'
model也下面有查看API文档链接
web或桌面客户端调用ollama服务
- Open WebUI
- Enchanted (macOS native)
- Lollms-Webui
- LibreChat
- Bionic GPT
- HTML UI
- Saddle
- Chatbot UI
- Chatbot UI v2
- Typescript UI
- Minimalistic React UI for Ollama Models
- Ollamac
- big-AGI
- Cheshire Cat assistant framework
- Amica
- chatd
- Ollama-SwiftUI
- Dify.AI
- MindMac
- NextJS Web Interface for Ollama
- Msty
- Chatbox
- WinForm Ollama Copilot
- NextChat with Get Started Doc
- Alpaca WebUI
- OllamaGUI
- OpenAOE
- Odin Runes
- LLM-X: Progressive Web App
- AnythingLLM (Docker + MacOs/Windows/Linux native app)
- Ollama Basic Chat: Uses HyperDiv Reactive UI
- Ollama-chats RPG
- QA-Pilot: Chat with Code Repository
- ChatOllama: Open Source Chatbot based on Ollama with Knowledge Bases
- CRAG Ollama Chat: Simple Web Search with Corrective RAG
- RAGFlow: Open-source Retrieval-Augmented Generation engine based on deep document understanding
- chat: chat web app for teams
- Lobe Chat with Integrating Doc
- Ollama RAG Chatbot: Local Chat with multiples PDFs using Ollama and RAG.
AnythingLLM搭建本地知识库
Windows下中文微调Llama3,单卡8G显存只需5分钟,模型可接入GPT4All、Ollama,实现CPU推理聊天,附Colab一键训练脚本
一键部署本地私人专属知识库,开源免费!可接入GPT-4、Llama 3、Gemma、Kimi等几十种大模型,零代码集成
MaxKB项目地址
https://github.com/1Panel-dev/MaxKB
Docker桌面程序下载
https://www.docker.com/products/docker-desktop/
MaxKB docker 部署指令
docker run -d --name=maxkb -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data 1panel/maxkb
MaxKB本地登录
用户名: admin
密码: MaxKB@123..
Ollama下载
Llama文本开源模型
Meta 官方 Llama 3.1 介绍文章:
https://ai.meta.com/blog/meta-llama-3-1/
Hugging Face Llama 3.1 介绍文章:
https://huggingface.co/blog/llama31
Llama 3.1 Model Card:
https://github.com/meta-llama/llama-models/blob/main/models/llama3_1/MODEL_CARD.md
HuggingChat 聊天页面:
Groq:
LM Studio 官网:
Llama 3.1 一键本地部署!Meta 最强开源大模型,100%保证成功,无需GPU也能运行,可离线使用
1.本地电脑安装的硬件要求:
Windows:3060以上显卡+8G以上显存+16G内存,硬盘空间至少20G
Mac:M1或M2芯片 16G内存,20G以上硬盘空间
在开始之前,首先我们需要安装Ollama客户端,来进行本地部署Llama3.1大模型
官方下载:【点击前往】
安装命令:
安装llama3.1-8b,至少需要8G的显存,安装命令就是
ollama run llama3.1:8b
安装llama3.1-70b,至少需要大约 70-75 GB 显存,适合企业用户,安装命令就是
ollama run llama3.1:78b
安装llama3.1-405b,这是一个极其庞大的模型,安装和运行它在本地需要非常高的显存和硬件资源,至少需要大约 400-450 GB 显存,适合顶级大企业用户,安装命令就是
ollama run llama3.1:405b
简单几步微调Llama3变身中文大模型!PDF清洗外加Ollama和LM Studio本地加载微调好的大模型
5分钟做一个企业级AI问答系统,纯开源解决方案。老板乐开花 feat.FastGPT 上手体验
喂饭教程!100%实现Ollama+RAG本地化知识库!Ollama+RAG安装部署-带你全面认识RAG框架
微软AI Dev Gallery开源项目帮助Windows开发人员学习如何将具有本地模型和API的AI添加到windows应用程序中
搭建私有大模型原来这么简单
https://github.com/open-webui/open-webui
大模型RAG企业项目实战:手把手带你搭建一套完整的RAG系统
rag 外挂知识库(检索的方式来增强生成模型能力)
***重点:通过自选嵌入式模型对文本向量化后存入向量数据库,query查询文本向量化和库中向量匹配得到想要的结果
采用嵌入式模型对文本向量化
嵌入式模型
输出是固定维度的向量。例如,在词嵌入模型中,无论输入的单词是什么,输出的嵌入向量维度是预先定义好的。这个向量空间的维度通常较低,方便存储和计算。
这些向量的主要作用是表示输入数据的特征,使得相似的输入在向量空间中有相近的表示。例如,在情感分析任务中,将句子中的单词转换为词嵌入向量后,可以通过对这些向量进行组合和运算,来获取句子的情感倾向特征表示。
模型类型:自然语言处理->文本向量(nlp->sentence-embedding)
嵌入模型的选择
-标准:找需求相关的语料库来进行文本向量转换测试,进行评估
相近语句向量化后得分接近就OK
text-embedding-ada-0o2 :(OpenAI的跨语言表现很好,非开源收费的)I
大多数场景下,开源的嵌入模型使用都很一般
整理PDF文件,文本切分
# !pip install --upgrade openai
#安装pdf解析库
#!pip installpdfminer.six
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer
def extract_text_from_pdf(filename, page_numbers=None, min_line_length=1):
'''从PDF文件中(按指定页码)提取文字!
paragraphs = []
buffer =''
full_text =''
#提取全部文本
for i, page_layout in enumerate(extract_pages(filename)):
#如果指定了页码范围,跳过范围外的页
if page_numbers is not None and i not in page_numbers:
continue
for element in page_layout:
if isinstance(element,LTTextcContainer):
full_text += element.get_text() +'\n'
#按空行分隔,将文本重新组织成段落
lines = full_text.split('\n')
for text in lines:
if len(text) >= min line length:
buffer += (' '+text) if not text.endswith('-') else text.strip('_')
elif buffer:
paragraphs.append(buffer)
buffer=''
if buffer:
paragraphs.append(buffer)
return paragraphs
paragraphs = extract_text_from_pdf("llama2.pdf",min_line_length=10)
for para in paragraphs[:4]:
print(para+"\n")
创建模版
# !pip install -U python-dotenv
from openai import OpenAI
import os
#加载环境变量
from dotenv import load_dotenv,find_dotenv
- = load_dotenv(find_dotenv(), verbose=True) #读取本地.enV文件,里面定义了OPENAI_API_KEY
client=OpenAI()
def get_completion(prompt, model="gpt-4o"):
'''封装openai接
messages = [{"role": "user", "content": prompt}]
response = client.chat.completions.create(
model=model
messages=messages
temperature=0,#模型输出的随机性,0表示随机性最小
)
return response.choices[o].message.content
def build_prompt(prompt_template,**kwargs):
'''将Prompt模板赋值''''
inputs = {}
for k,v in kwargs.items:
if isinstance(v,list) and all(isinstance(elem,str) for elem in v):
val = '\n\n'.join(v)
else:
val =v
inputs[k] = val
return prompt_template.format(**inputs)
prompt_template="""
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题
已知信息:
{context}#检索出来的原始文档
用户问:
{query}#用户的提问
如果已知信息不包含用户问题的答案,或者已知信息不足以回答用户的问题,请直接回复“我无法回答您的问题”。
请不要输出已知信息中不包含的信息或答案请用中文回答用户问题。
"""
(离线处理:索引库开发,给出的答案:具备可解释性)
文本向量化(OpenAI SDK 可以文本向量化维度可以达几千)
文本转向量用到的模型文章:https://sbert.net/
Sentence Transformers(又名 SBERT)是用于访问、使用和训练最先进的文本和图像嵌入模型的首选 Python 模块。
例子:
from sentence_transformers import SentenceTransformer
# 1. Load a pretrained Sentence Transformer model
model = SentenceTransformer("all-MiniLM-L6-v2")
# The sentences to encode
sentences = [
"The weather is lovely today.",
"It's so sunny outside!",
"He drove to the stadium.",
]
# 2. Calculate embeddings by calling model.encode()
embeddings = model.encode(sentences)
print(embeddings.shape)
# [3, 384]
# 3. Calculate the embedding similarities
similarities = model.similarity(embeddings, embeddings)
print(similarities)
# tensor([[1.0000, 0.6660, 0.1046],
# [0.6660, 1.0000, 0.1411],
# [0.1046, 0.1411, 1.0000]])
向量的距离:
余弦距离:越大越近
欧式距离:也就是向量夹角,越小越接近
import numpy as np
from numpy import dot
from numpy.linalg import norm
def cos_sim(a,b):
'''余弦距离 -越大越相似
return dot(a, b)/(norm(a)*norm(b))
def l2(a, b):
'''欧氏距离 --越小越相似··
x = np.asarray(a)-np.asarray(b)
return norm(x)
def get_embeddings(texts,model="text-embedding-ada-oo2",dimensions=None):
'''封装OpenAI的Embedding模型接口
if model == "text-embedding-ada-002":
dimensions = None
if dimensions:
data = client.embeddings.create(
input=texts, model=model,dimensions=dimensions).data
else:
data = client.embeddings.create(input=texts, model=model).data
return [x.embedding for x in data]
#简单测试获得向量值
test_query=["测试文本"]
vec = get_embeddings(test_query)[0]
#获得维度
print(f"Total dimension:{len(vec)}")
#获得前10个向量
print(f"First 10 elements: {vec[:10]}")
#实际测试向量距离
query="国际争端"
#且能支持跨语言
#query ="globalconflicts"
documents=[
"联合国就苏丹达尔富尔地区大规模暴力事件发出警告”,
"土耳其、芬兰、瑞典与北约代表将继续就瑞典“入约”问题进行谈判”,
"日本岐阜市陆上自卫队射击场内发生枪击事件3人受伤”,
"国家游泳中心(水立方):恢复游泳、嬉水乐园等水上项目运营”,
"我国首次在空间站开展舱外辐射生物学暴露实验!]
query_vec = get_embeddings([query])[0]
doc_vecs=get_embeddings(documents)
print("Query与自己的余弦距离:{:.2f}".format(cos_sim(query_vec,query_vec))))
print("Query与Documents的余弦距离:")
for vec in doc_vecs:
print(cos_sim(query_vec,vec))
print()
print("Query与自己的欧氏距离:{:.2f}".format(12(query_vec,query_vec)))
print("Query与Documents的欧氏距离:")
for vec in docvecs:
print)l2(query_vec,vec))
若前三句话出来的向量值差不多,那么这个向量化的模型就可以否则不可用
嵌入模型选择:
-标准:找需求相关的语料库进行向量的转换测试,进行评估
常用的模型:text-embedding-ada-002【OpenAI公司非开源的商用付费的,在社区找到的不是OpenAI的】跨语言能力可以(实际项目开发基本用框架:langchain llamaindex默认用它)
点击查看2025吃透AI大模型全套教程(LangChain+LLM+RAG+OpenAI+Agent)通俗易懂
大多数场景下,开源的嵌入模型使用都很一般
模型可在huggingface获得魔塔社区找(自然语言处理->文本向量)
Chroma向量数据库
官方文档:https://docs.trychroma.com/docs/overview/introduction
#!pip install chromadb
#为了演示方便,我们只取两页(第一章)
paragraphs = extract_text_from_pdf(
"ilama2.pdf"
page_numbers=[2, 3], min_line_length=10
)
import chromadb
from chromadb.config import Settings
class MyVectorDBConnector:
def init(self,collectionname,embedding fn):
#内存模式
chroma_client=chromadb.client(Settings(allow_reset=True))
#数据持久化
#chroma_client=chromadb.Persistentclient(path="./chroma")
#注意:为了演示,实际不需要每次reset(),并且是不可逆的,它是复位清空数据!
chroma_client.reset()
#创建一个collection
self.collection=chroma_client.get_or_create_collection(name=collection_name)
self.embedding_fn = embedding_fn #模型文本向量化函数,如果不设置数据库默认的模型all-MiniLM-L6-v2
def add_documents(self,documents):
'''向collection 中添加文档与向量
self.collection.add(
embeddings=self.embedding_fn(documents),#每个文档的向量
documents=documents,#文档的原文
ids=[f"id{i}" for i in range(len(documents))]#每个文档的id
)
def search(self,query,top_n): #top_n表示检索几个文档出来
'''检索向量数据库!
results = self.collection.query(
query_embeddings=self.embedding_fn([queryl),
n_results=top_n
)
return results
#创建一个向量数据库对象
vector_db=MyVectorDBConnector("demo",get_embeddings) #get_embeddings为前面已经写过的函数
#向向量数据库中添加文档
vector_db.add_documents(paragraphs)
user_query ="Llama 2有多少参数"
# user_query = "Does Llama 2 have a conversational variant"
results = vector_db.search(user_query,2)
for para in results['documents'][0]:
print(para+"\n")
FA/SS:Meta开源的向量检索引|擎https://github.com/facebookresearch/faiss
Pinecone:商用向量数据库,只有云服务https://www.pinecone.io/
Milvus:开源向量数据库,同时有云服务https://milvus.io/
Weaviate:开源向量数据库,同时有云服务https://weaviate.io/
Qdrant:开源向量数据库,同时有云服务https://qdrant.tech/
PGVectorPostgres的开源向量检索引擎https://github.com/pgvector/pgvector
RediSearch:Redis的开源向量检索引擎https://github.com/RediSearch/RediSearch
ElasticSearch也支持向量检索https://www.elastic.co/enterprise-search/vector-search
基于向量检索的TAG(底层实现代码)
class RAG_Bot:
def __init__(self,vector_db,llm_api,n_results=2):
self.vectordb=vectordb
self.llm_api=ilm_api
self.n_results=n_results
def chat(self,user_query):
#1.检索
search_results= self.vector_db.search(user_query,self.n_results)
#2.构建Prompt
prompt =build_prompt(
prompt_template,context=search_results['documents'][o],query=user_query
#3.调用LLM
response=self.llm_api(prompt)
return response
#创建一个RAG机器人
bot=RAG_Bot(
vector_db,
llm_api=get_completion #前面的函数
)
user_query="llama2有多少参数?"
response= bot.chat(user_query)
print(response)
***目前好用的向量模型是OpenAI的(之前的:text-embedding-ada-002)
OpenAl新发布的两个Embedding模型
2024年1月25日,OpenAl新发布了两个Embedding模型
text-embedding-3-large
text-embedding-3-small
其最大特点是,支持自定义的缩短向量维度,从而在几乎不影响最终效果的情况下降低向量检索与相似度计算的复杂度通俗的说:越大越准、越小越快。官方公布的评测结果
model = "text-embedding-3-large"
dimensions=256 #指定维度
query=“国际争端"#且能支持跨语言
# query = "global conflicts"
documents=[
“联合国就苏丹达尔富尔地区大规模暴力事件发出警告",
"土耳其、芬兰、瑞典与北约代表将继续就瑞典“入约”问题进行谈判",
"日本岐阜市陆上自卫队射击场内发生枪击事件3人受伤”,
"国家游泳中心(水立方):恢复游泳、嬉水乐园等水上项目运营",
"我国首次在空间站开展舱外辐射生物学暴露实验”
]
query_vec =get_embeddings([query],model=model,dimensions=dimensions)[0]
doc_vecs = get_embeddings(documents,model=model,dimensions=dimensions)
print("向量维度:+".format(Len(query_vec)))
print()
print"Query与Documents的余弦距离:"
for vec in doc vecs:
print(cos_sim(query_vec, vec))
print()
print("ouery与Documents的欧氏距离:"
for vec in doc_vecs:
print(l2(query_vec, vec))
rag系统基本需要用到两个模型
embedded模型
-可采用OpenAI线上模型(向量化精确度高)
向量模型的精度真接影响query相似度检索的 文档召回率
文本生成模型(对话模型)
-采用本地私有化部署模型
实战 RAG 系统的进阶知识
文本分割的粒度缺陷
1.粒度太大可能导致检索不精准,粒度太小可能导致信息不全面
2.问题的答案可能跨越两个片段
#创建一个向量数据库对象
vector_db=MyVectorDBConnector("demo_text_split",get_embeddings)
#向向量数据库中添加文档
vector_db.add_documents(paragraphs)
#创建一个RAG机器人
bot = RAG_Bot(
vector_db,
llm_api=get_completion
)
#user_query="llama2有商用许可协议吗"
user_query="llama2chat有多少参数"
search_results = vector_db.search(user_query,2)
for doc in search results['documents']0]
print(doc+")n")
print("====回复====")
bot.chat(user_query)
改进:按一定粒度,部分重叠式的切割文本,使上下文更完整
#!pip install nltk
from nltk.tokenize import sent_tokenize
import json
#·chunk_size.一般根据文档内容或大小来设置
#overlap_size·一般设置·chunk_size·大小的10%-20%之间
def split_text(paragraphs, chunk_size=300, overlap_size=10o):
'''按指定chunk_size和overlap_size交叠割文本!
sentences = [s.strip() for p in paragraphs for s in sent_tokenize(p)]
chunks = []
i=0
while i<len(sentences):
chunk = sentences[i]
overlap =''
prev_len = 0
prev = i - 1
#向前计算重叠部分
while prev >= 0 and len(sentences[prevl)+len(overlap) <= overlap_size:
overlap =sentences[prev]+ ''+overlap
prev -= 1
chunk=overlap+chunk
next = i + 1
#向后计算当前chunk
while next < len(sentences) and len(sentences[next])+len(chunk) <= chunk_size:
chunk = chunk + ''+sentences[next]
next += 1
chunks.append(chunk)
i = next
return chunks
重新应用
chunks = split_text(paragraphs,300, 100)
#创建一个向量数据库对象
vector_db = MyVectorDBconnector("demo_text_split",get_embeddings)
#向向量数据库中添加文档
vectordb.adddocuments(chunks)
#创建一个RAG机器人
bot=RAG_Bot(
vector_db,
llm_api=get_completion
)
#user_query="llama2有商用许可协议吗"
user_query="llama 2chat有多少参数"
search_results = vector_db.search(user_query, 2)
for doc in search_results['documents'][0]:
print(doc+"\n")
response=bot.chat(user_query)
print("==回复=")
print(response)
检索后排序
问题:有时,最合适的答案不一定排在检索的最前面
方案:
1.检索时过招回一部分文本
2.通过一个排序模型对query和document重新打分排序
以下代码运行前要确保能访问HuggingFace!
#!pip install sentence_transformers
from sentence_transformers
import CrossEncoder
model=CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2',max_length=512)#英文,模型较小
#model=CrossEncoder(BAAI/bge-reranker-large,max_length=512)#多语言,国产,模型较大 0.8s
user_query = "how safe is llama 2"
#user_query ="llama 2安全性如何"
scores = model.predict([(user_query, doc) for doc in search_results['documents'][0]])
#按得分排序
sorted_list = sorted(zip(scores, search_results['documents'][0]), key=lambda x: x[0], reverse=True) for score,doc in sorted_list:
print(f"{score}\t{doc}\n")
一些Rerank的APl服务
CohereRerank:支持多语言
JinaRerank:自前只支持英文
开源的嵌入式模型推荐不过需要微调才行
BAAI/bge-reranker-large
混合检索(HybridSearch)
在实际生产中,传统的关键字检索(稀疏表示)与向量检索(稠密表示)各有优劣
举个具体例子,比如文档中包含很长的专有名词,关键字检索往往更精准而向量检索容易引入概念混淆
#背景说明:在医学中“小细胞肺癌”和“非小细胞肺癌”是两种不同的癌症
query="非小细胞肺癌的患者"
documents=[
“玛丽患有肺癌,癌细胞己转移”,
"刘某肺癌I期”,
"张某经诊断为非小细胞肺癌III期",
"小细胞肺癌是肺癌的一种"
]
query_vec=get_embeddings([query])[0]
doc_vecs = get_embeddings(documents)
print(Cosinedistance:" )
for vec in doc_vecs:
print(cos_sim(query_vec,vec))
结果:
Cosine distance:
0.8915956814209096
0.8902380296876382
0.9043403228477503
0.9136486327152477
所以,有时候我们需要结合不同的检索算法,来达到比单一检索算法更优的效果。这就是混合检索
混合检索的核心是,综合文档d在不同检索算法下的排序名次(rank),为其生成最终排序
一个最常用的算法叫ReciprocalRankFusion
其中A表示所有使用的检索算法的集合,ranka(d)表示使用算法a检索时,文档d的排序,k是个常数很多向量数据库都支持混合检索,比如Weaviate、Pinecone等。也可以根据上述原理自己实现
手写简单的例子
需要安装好Elastic Search Server服务端
#lpip install elasticsearch7
1.基于关键字检索排序
importn time
class MyEsconnector:
def __init__(self,es_client,index_name,keyword_fn):
self.esclient = es client
self.index_name = index_name
self.keyword_fn = keyword_fn
def add_documents(self, documents):
'''文档灌库'''
if self.es_client.indices.exists(index=self.index_name):
self.es_client.indices.delete(index=self.index_name)
self.es_client.indices.create(index=self.index_name)
actions = [
{
"_index":self.index_name,
"_source":{
"keywords": self.keyword_fn(doc),
"text":doc,
"id":f"doc_{i}"
}
}
for i,doc in enumerate(documents)
]
helpers.bulk(self.es_client,actions)
time.sleep(1)
def search(self,query_strinq,top n=3):
'''检索'''
search_query ={
"matcb":{
"keywords":self.keyword_fn(query_string
}
}
res = self.es_client.search(
index=self.index_name, query=search_query,size=top_n)
return{
hit["_source"["id"]:{
"text": hit["_source"j["text"],
"rank":i,
}
for i,hit in enumepate(res["hits"]["hits"])
}
from chinese_utils import to_keywords #使用中文的关键字提取函数
#引入配置文件
ELASTICSEARCH_BASE URL = od.getenv(ELASTICSEARCH_BASE_URL') ELASTICSEARCH_PASSWORD = os.getenv('ELASTICSEARCH_PASSWORD')
ELASTICSEARCH_NAME=os.getenv('ELASTICSEARCH_NAME')
es = Elasticsearch(
hosts=[ELASTICSEARCH_BASE_URL],#服务地址与端口
http_auth=(ELASTICSEARCH_NAME,ELASTICSEARCH_PASSWORD),#用户名,密码
)
#创建ES连接器
es_connector = MyEsConnector(es, "demo_es_rrf",to_keywords)
#文档灌库
es_connector.add_documents(documents)
#关键字检索
keyword_search_results=es_connector.search(query,3)
print(json.dumps(keyword_search_results,indent=4,ensure_ascii=False))
2.基于向量检索的排序
#创建向量数据库连接器
vecdb_connector = MyVectorDBConnector("demo_vec_rrf",get_embeddings)
#文档灌库
vecdb_connector.add_documents(documents)
#向量检索
vector_search_results=
"doc"+str(documents.index(doc)):{
"text": doc,
"rank":i
}
for i,doc in enumerate(
vecdb_connector.search(query,3)["documents"][0]
)
}
#把结果转成跟上面关键字检索结果一样的格式
print(json.dumps(vector_search_results,indent=4, ensure_ascii=False))
3.基于RRF的融合排序
参考资料:https://learn.microsoft.com/zh-cn/azure/search/hybrid-search-ranking
def rrf(ranks, k=1):
ret = {}
#遍历每次的排序结果
for rank in ranks:
#遍历排序中每个元素
for id,val in rank.items():
if id not in ret:
ret[id] = {"score": 0, "text": val["text"]}
#计算RRF得分
ret[id]["score"] += 1.0/(k+val["rank"])
#按RRF得分排序,并返回
return dict(sorted(ret.items(), key=lambda item: item[1]["score"], reverse=True))
import json
#融合两次检索的排序结果
reranked = rrf([keyword_search_results, vector_search_results])
print(json.dumps(reranked,indent=4,ensure_ascii=False))
PDF文档中的表格怎么处理
1.将每页PDF转成图片
#!pip install PyMuPDF
#!pip install matplotlib
import os
import fitz
from PIL import Image
def pdf2images(pdf file):
'''将PDF每页转成一个PNG图像!I#保存路径为原PDF文件名(不含扩展名)
output_directory_path, -= os.path.splitext(pdf_file)
if not os.path.exists(output_directory_path):
os.makedirs(output_directory_path)
#加载PDF文件
pdf_document=fitz.open(pdf_file)
#每页转一张图
for page_number in range(pdf_document.page_count):
#取一页
page = pdf_document[page_number]
#转图像
pix = page.get_pixmap()
#从位图创建PNG对象
image = Image.frombytes("RGB",[pix.width, pix.height], pix.samples)
#保存PNG文件
image.save(f"./{output_directory_path}/page_page_number + 1}.png")
#关闭PDF文件
pdf_document.close()
from PIL import Image import os
import matplotlib.pyplot as pt
def show_images(dir_path):
'''显示目录下的PNG图像!
for file in os.listdir(dir_path):
if file.endswith('.png'):
#打开图像
img = Image.open(os.path.join(dir_path, file))
#显示图像
plt.imshow(img)
plt.axis('off) #不显示坐标轴
plt.show()
2.识别文档图片中表格
class MaxResize(object):
'''缩放图像
def _init_(self,max_size=80o):
self.max_size = max_size
def __call__(self, image):
width,height=imaqe.size
current_max_size=max(width,height)
scale = self.max_size / current_max_size
resized_image=image.resize(
(int(round(scale * width)),int(round(scale height)))
return resized_image
#!pip install torchvision
#!pip install transformers
#!pip install timm
import torchvision.transforms as transforms
#图像预处理
detection_transform=transforms.Compose[
MaxResize(800)
#将原始的PILImage格式的数据格式化为可被pytorch快速处理的张量类型
transforms.ToTensor()
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
]
)
from transformers import AutoModelForobjectDetection
#加载TableTransformer模型
model = AutoModelForobjectDetection.from_pretrained(
"microsoft/table-transformer-detection"
)
#识别后的坐标换算与后处理
def box_cxcywh_to_xyxy(x):
'''坐标转换
x_c, y_c, w, h = x.unbind(-1)
b = [(x_c - 0.5 * w), (y_c - 0.5 * h), (x_c + 0.5 × w), (y_c + 0.5 * h)]
return torch.stack(b,dim=1)
def rescalebboxes(outbbox.size):
'''区域缩放
width,heiqht= size
boxes=box_cxcywh_to_xyxy(out_bbox)
boxes=boxes*torch.tensor(
[width,height,width,height],dtype=torch.float32
)
return boxes
def outputs_to_objects(outputs,img_size,id2label):
'''从模型输出中取定位框坐标!
m = outputs.logits.softmax(-1).max(-1)
pred_labels = list(m.indices.detach().cpu().numpy()[0]
pred_scores = list(m.values.detach().cpu().numpy()[0]
pred_bboxes = outputs["pred_boxes"].detach().cpu()[0]
pred_bboxes =[
elem.tolist)foreleminrescale_bboxes(pred_bboxes,img_size)
]
objects=[]
for label,score,bbox in zip(pned_labels,pred_scores,pred_bboxes):
classlabel=id2label[intlabel]
if not class_label=="no object":
objects.append(
{
"label": class_label,
"score": float(score),
"bbox":[float(elem) for elem in bbox],
}
)
return objects
import torch
#识别表格,并将表格部分单独存为图像文件
def detect_and_crop_save_table(file_path):
#加载图像(PDF页)
image =Image.open(file_path)
filename,-= os.path.splitext(os.path.basename(file_path))
#输出路径
cropped_table_directory = os.path.join(os.path.dirname(file_path),"table_images")
if not os.path.exists(cropped_table_directory):
os.makedirs(cropped_table_directory)
#预处理
pixel_values = detection_transform(image).unsqueeze(0)
#识别表格
with torch.no_grad:
outputs = model(pixel_values)
#后处理,得到表格子区域
id2label = model.config.id2label
id2label[len(model.config.id2Label)]="no object"
detected_tables = outputs_to_objects(outputs, image.size, id2label)
print(f"number of tables detected {len(detected_tables)子")
for idx in range(len(detected_tables)):
#将识别从的表格区域单独存为图像
cropped_table = image.crop(detected_tables[idx]["bbox"])
cropped_table.save(os.path.join(cropped_table_directory,f"{filename}_{idx}.png"))
3.基于GPT-4VisionAPI做表格问答
import base64
from openai import OpenAI
client = OpenAI()
def encode_image(image_path):
with open(image_path,"rb")as image_file:
return base64.b64encode(image_file.readO).decode('utf-8')
def image_qa(query, image_path):
base64_image=encode_image(image_path)
response=client.chat.completions.create(
model="gpt-4o",
temperature=0,
seed=42,
messages=[{
"role":"user",
"content":[
{"type":"text","text":query},
{"type";"image_url",
"image_url": {
"uri":f"data:image/jpeg;base64,{base64_image}"
},
},
],
}],
)
return response.choices[o].message.content
4.用GPT-4Vision生成表格(图像)描述,并向量化用于检索
import chromadb
from chromadb.config import Settings
class NewVectorDBConnector:
def __init__(self,collection_name,embedding_fn):
chroma_client = chromadb.client(Settings(allow_reset=True))
#为了演示,实际不需要每次reset
chroma_client.reset()#创建一个collection
self.collection=chroma_client.get_or_create_collection(
name=collection_name)
self.embedding_fn = embedding_fn
def add_documents(self, documents):
'''向collection中添加文档与向量
self.collection.add(
embeddings=self.embedding_fn(documents),#每个文档的向量
documents=documents,#文档的原文
ids=[f"id{i} for i inrange(len(documents))]#每个文档的id
)
def add_images(self, image_paths):
'''向collection中添加图像!
documents=[
image_qa("请简要描述图片中的信息",image)
for image in image_paths
self.collection.add(
embeddings=self.embedding_fn(documents),#每个文档的向量
documents=documents,#文档的原文
ids=[f"idi}foriinrange(len(documents))],#每个文档的id
metadatas=[{"image":image} for image inimage_paths] # 用 metadata 标记源图像路径
def search(self,query,top_n):
'''检索向量数据库!!
results = self.collection.query(
query_embeddings=self.embedding_fn([query])
n_results=top_n
return results
images =[]
dir_path = "llama2_page8/table_images"
for file in os.listdir(dir_path):
if file.endswith(.png'):
#打开图像
images.append(os.path.join(dir_path,file))
new_db_connector=NewVectorDBConnector("table_demo"get_embeddings) new_db_connector.add_images(images)
query="哪个模型在AGIEval数据集上表现最好。得分多少"
results = new_db_connector.search(query,1)
metadata = results["metadatas"][0]
print("====检索结果====")
print(metadata)
print("=-==回复====")
response = image_qa(query,metadata[0]["image"])
print(response)
一些面向RAG的文档解析辅助工具
·PyMuPDF:PDF文件处理基础库,带有基于规则的表格与图像抽取(不准)
·RAGFIoW:一款基于深度文档理解构建的开源RAG引擎,支持多种文档格式(火爆)
·Unstructured.io:一个开源+SaaS形式的文档解析库,支持多种文档格式
·LlamaParse:付费APl服务,由Llamalndex官方提供,解析不保证100%准确,实测偶有文字丢失或错位发生
Mathpix:付费API服务,效果较好,可解析段落结构、表格、公式等,贵!
说说GraphRAG
1.什么是GraphRAG:核心思想是将知识预先处理成知识图谱
2.优点:适合复杂问题,尤其是以查询为中心的总结,例如:XXX团队去年有哪些贡献
3.缺点:知识图谱的构建、清洗、维护更新等都有可观的成本
4.建议:
GraphRAG不是万能良药
领会其核心思想
遇到传统RAG无论如何优化都不好解决的问题时,视情使用
总结
RAG的流程·
离线步骤
1.文档加载
2.文档切分
3.向量化
4.灌入向量数据库·
在线步骤:
1.获得用户问题
2.用户问题向量化
3.检索向量数据库
4.将检索结果和用户问题填入Prompt模版
5.用最终获得的Prompt调用LLM
6.由LLM生成回复
我用了一个开源的RAG,不好使怎么办?
1.检查预处理效果:文档加载是否正确,切割的是否合理
2.测试检索效果:问题检索回来的文本片段是否包含答案
3.测试大模型能力:给定问题和包含答案文本片段的前提下,大模型能不能正确回答问题
5分钟学会Ollama + Open WebUI搭建本地大模型Web交互界面
vllm调用框架
pip install vllm
AI服务调用平台
编写一个使用硅基流动Deepseek接口多轮对话对token优化后的python例子
ollama搭建本地ai大模型并应用调用
1、下载ollama
1)https://ollama.com 进入网址,点击download下载
2)下载后直接安装即可。
2、启动配置模型
默认是启动cmd窗口直接输入
1 ollama run llama3
启动llama3大模型 或者启动千问大模型
1 ollama run qwen2
启动输入你需要输入的问题即可
3、配置UI界面
安装docker
并部署web操作界面
1 docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui --restart always ghcr.io/open-webui/open-webui:main
安装完毕后,安装包较大,需等待一段时间。
localhost:3000即可打开网址
4、搭建本地知识库
AnythingLLM
5、配置文件
开发11434端口,便于外部访问接口,如果跨域访问的话配置OLLAMA_ORIGINS=*
Windows版
只需要在系统环境变量中直接配置,
OLLAMA_HOST为变量名,"0.0.0.0:11434"为变量值
1 OLLAMA_HOST= "0.0.0.0:11434"
MAC版
配置OLLAMA_HOST
1 sudo sh -c 'echo "export OLLAMA_HOST=0.0.0.0:11434">>/etc/profile'launchctl setenv OLLAMA_HOST "0.0.0.0:11434"
Linux版
配置OLLAMA_HOST
1 Environment="OLLAMA\_HOST=0.0.0.0"
6、程序调用接口
golang实现例子:流式响应速度更快,用户体验更佳。
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
const (
obaseURL = "http://localhost:11434/api"
omodelID = "qwen2:0.5b" // 选择合适的模型
oendpoint = "/chat" //"/chat/completions"
)
// ChatCompletionRequest 定义了请求体的结构
type olChatCompletionRequest struct {
Model string `json:"model"`
Messages []struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"messages"`
Stream bool `json:"stream"`
//Temperature float32 `json:"temperature"`
}
// ChatCompletionResponse 定义了响应体的结构
type olChatCompletionResponse struct {
//Choices []struct {
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message"`
//} `json:"choices"`
}
// sendRequestWithRetry 发送请求并处理可能的429错误
func olsendRequestWithRetry(client *http.Client, requestBody []byte) (*http.Response, error) {
req, err := http.NewRequest("POST", obaseURL+oendpoint, bytes.NewBuffer(requestBody))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
//req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusTooManyRequests {
retryAfter := resp.Header.Get("Retry-After")
if retryAfter != "" {
duration, _ := time.ParseDuration(retryAfter)
time.Sleep(duration)
} else {
time.Sleep(5 * time.Second) // 默认等待5秒
}
return olsendRequestWithRetry(client, requestBody) // 递归重试
}
return resp, nil
}
func main() {
client := &http.Client{} // 创建一个全局的 HTTP 客户端实例
// 初始化对话历史记录
history := []struct {
Role string `json:"role"`
Content string `json:"content"`
}{
{"system", "你是一位唐代诗人,特别擅长模仿李白的风格。"},
}
// 创建标准输入的扫描器
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("请输入您的问题(或者输入 'exit' 退出): ")
scanner.Scan()
userInput := strings.TrimSpace(scanner.Text())
// 退出条件
if userInput == "exit" {
fmt.Println("感谢使用,再见!")
break
}
// 添加用户输入到历史记录
history = append(history, struct {
Role string `json:"role"`
Content string `json:"content"`
}{
"user",
userInput,
})
// 创建请求体
requestBody := olChatCompletionRequest{
Model: omodelID,
Messages: history,
Stream: false,
//Temperature: 0.7,
}
// 构建完整的请求体,包含历史消息
requestBody.Messages = append([]struct {
Role string `json:"role"`
Content string `json:"content"`
}{
{
Role: "system",
Content: "你是一位唐代诗人,特别擅长模仿李白的风格。",
},
}, history...)
// 将请求体序列化为 JSON
requestBodyJSON, err := json.Marshal(requestBody)
if err != nil {
fmt.Println("Error marshalling request body:", err)
continue
}
fmt.Println("wocao:" + string(requestBodyJSON))
// 发送请求并处理重试
resp, err := olsendRequestWithRetry(client, requestBodyJSON)
if err != nil {
fmt.Println("Error sending request after retries:", err)
continue
}
defer resp.Body.Close()
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
fmt.Printf("Received non-200 response status code: %d\n", resp.StatusCode)
continue
}
// 读取响应体
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body:", err)
continue
}
//fmt.Println("0000" + string(responseBody))
// 解析响应体
var completionResponse olChatCompletionResponse
err = json.Unmarshal(responseBody, &completionResponse)
if err != nil {
fmt.Println("Error unmarshalling response body:", err)
continue
}
fmt.Printf("AI 回复: %s\n", completionResponse.Message.Content) // choice.Message.Content
// 将用户的消息添加到历史记录中
history = append(history, struct {
Role string `json:"role"`
Content string `json:"content"`
}{
Role: completionResponse.Message.Role,
Content: completionResponse.Message.Content, // 假设用户的消息是第一个
})
}
}
golang例子:非流式响应
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
const (
obaseURL = "http://localhost:11434/api"
omodelID = "qwen2:0.5b" // 选择合适的模型
oendpoint = "/chat" //"/chat/completions"
)
// ChatCompletionRequest 定义了请求体的结构
type olChatCompletionRequest struct {
Model string `json:"model"`
Messages []struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"messages"`
Stream bool `json:"stream"`
//Temperature float32 `json:"temperature"`
}
// ChatCompletionResponse 定义了响应体的结构
type olChatCompletionResponse struct {
//Choices []struct {
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message"`
//} `json:"choices"`
}
// sendRequestWithRetry 发送请求并处理可能的429错误
func olsendRequestWithRetry(client *http.Client, requestBody []byte) (*http.Response, error) {
req, err := http.NewRequest("POST", obaseURL+oendpoint, bytes.NewBuffer(requestBody))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
//req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusTooManyRequests {
retryAfter := resp.Header.Get("Retry-After")
if retryAfter != "" {
duration, _ := time.ParseDuration(retryAfter)
time.Sleep(duration)
} else {
time.Sleep(5 * time.Second) // 默认等待5秒
}
return olsendRequestWithRetry(client, requestBody) // 递归重试
}
return resp, nil
}
func main() {
client := &http.Client{} // 创建一个全局的 HTTP 客户端实例
// 初始化对话历史记录
history := []struct {
Role string `json:"role"`
Content string `json:"content"`
}{
{"system", "你是一位唐代诗人,特别擅长模仿李白的风格。"},
}
// 创建标准输入的扫描器
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("请输入您的问题(或者输入 'exit' 退出): ")
scanner.Scan()
userInput := strings.TrimSpace(scanner.Text())
// 退出条件
if userInput == "exit" {
fmt.Println("感谢使用,再见!")
break
}
// 添加用户输入到历史记录
history = append(history, struct {
Role string `json:"role"`
Content string `json:"content"`
}{
"user",
userInput,
})
// 创建请求体
requestBody := olChatCompletionRequest{
Model: omodelID,
Messages: history,
Stream: true,
//Temperature: 0.7,
}
// 构建完整的请求体,包含历史消息
requestBody.Messages = append([]struct {
Role string `json:"role"`
Content string `json:"content"`
}{
{
Role: "system",
Content: "你是一位唐代诗人,特别擅长模仿李白的风格。",
},
}, history...)
// 将请求体序列化为 JSON
requestBodyJSON, err := json.Marshal(requestBody)
if err != nil {
fmt.Println("Error marshalling request body:", err)
continue
}
fmt.Println("wocao:" + string(requestBodyJSON))
// 发送请求并处理重试
resp, err := olsendRequestWithRetry(client, requestBodyJSON)
if err != nil {
fmt.Println("Error sending request after retries:", err)
continue
}
defer resp.Body.Close()
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
fmt.Printf("Received non-200 response status code: %d\n", resp.StatusCode)
continue
}
resutlmessage := ""
streamReader := resp.Body
buf := make([]byte, 1024) // 或者使用更大的缓冲区来提高读取性能
var completionResponse olChatCompletionResponse
fmt.Print("AI 回复:")
for {
n, err := streamReader.Read(buf)
if n > 0 {
// 处理接收到的数据,这里简单打印出来
//fmt.Print(string(buf[:n]))
err = json.Unmarshal(buf[:n], &completionResponse)
fmt.Print(string(completionResponse.Message.Content))
resutlmessage+=string(completionResponse.Message.Content)
if err != nil {
fmt.Println("Error unmarshalling response body:", err)
continue
}
}
if err != nil {
if err == io.EOF {
fmt.Println("")
break
}
panic(err)
}
}
// 将用户的消息添加到历史记录中
history = append(history, struct {
Role string `json:"role"`
Content string `json:"content"`
}{
Role: completionResponse.Message.Role,
Content: resutlmessage,//completionResponse.Message.Content, // 假设用户的消息是第一个
})
}
}
从零开始入门 LangChain
LangChain 是一个开源的基于 LLM(英语:Large Language Model,简称LLM) 的上层应用开发框架,LangChain 提供了一系列的工具和接口,让开发者可以轻松地构建和部署基于 LLM 的应用 。LangChain 围绕将不同组件“链接”在一起的核心概念构建,简化了与 GPT-3.5、GPT-4 等 LLM 合作的过程,使得我们可以轻松创建定制的高级用例。
目前, LangChain 支持 Python 和 TypeScript 两种语言。如果大家想体验 LangChain ,建议使用 python 语言,简单,易上手。LangChain 的官网是 LangChain官网,在这上面可以找到所有的使用案例和教程信息。
LangChain 解决了什么问题
通过上面的概念,我们可以看到 LangChain 实际上是基于大语言模型上层的一个应用框架,那么 LangChain 具体解决了大模型时代的哪些问题才让他脱颖而出呢。具体来说,主要有以下几个方面:
1.模型接口的统一
现在的大模型除了大家熟知的 ChatGPT,还有 Meta 开源的 LLaMA,清华大学的 GLM 等,这些模型的使用方法包括 api 和推理方式都相差甚远,如果你想从使用 ChatGPT 切换到调用 LLaMA,需要花费不少的精力去开发前置的模型使用模块,会有大量重复繁琐的工作。而 LangChain 对好多常见的 API 和大模型做了封装,可以直接拿来就用,节省了大量的时间。
2.打破了 LLM 提示词和返回内容 token 限制,为最新知识的检索、推理提供了更大的前景
像 ChatGPT 这样的语言模型,数据只更新到 2021 年,如何让大模型回答和学习到之后的知识就是一个很重要的问题。而且 ChatGPT 的 API 是有提示词和返回内容的限制的,3.5 是 4k,4 则是 8k,而我们往往需要从自己的数据、自己的文档中获取特定的信息,这可能是一本书、一个 PDF 文件、一个带有专有信息的数据库。这些信息的 token 数量会远高于 4k 的阈值,直接使用大模型是无法获取到相应的知识的,因为超过阈值的信息就被截断了。
LangChain 提供了对向量数据库的支持,能够把超长的 txt、pdf 等通过大模型转换为 embedding 的形式,存到向量数据库中,然后利用数据库进行检索。这样就可以支持更多长度的输入,解放了 LLM 的优势。
LangChain 的基本概念
LangChain 能解决大模型的两个痛点,包括模型接口复杂、输入长度受限离不开自己精心设计的模块。根据LangChain 的最新文档,目前在 LangChain 中一共有六大核心组件,分别是模型的输入输出 (Model I/O)、数据连接 (Data Connection)、内存记忆(Memory)、链(Chains)、代理(Agent)、回调(Callbacks)。下面我们将分别讲述每一个模块的功能和作用。
一)Model I/O
模型是任何 LLM 应用中最核心的一点,LangChain 可以让我们方便的接入各种各样的语言模型,并且提供了许多接口,主要有三个组件组成,包括模型(Models),提示词(Prompts)和解析器(Output parsers)。
1.Models
LangChain 中提供了多种不同的语言模型,按功能划分,主要有两种。
语言模型(LLMs):我们通常说的语言模型,给定输入的一个文本,会返回一个相应的文本。常见的语言模型有 GPT3.5,chatglm,GPT4All 等。
from langchain.llms import OpenAI
llm = OpenAI(openai_api_key="...")
聊天模型(Chat model):可以看做是封装好的拥有对话能力的 LLM,这些模型允许你使用对话的形式和其进行交互,能够支持将聊天信息作为输入,并返回聊天信息。这些聊天信息都是封装好的结构体,而非一个简单的文本字符串。常见的聊天模型有 GPT4、Llama 和 Llama2,以及微软云 Azure 相关的 GPT 模型。
from langchain.chat_models import ChatOpenAI
chat = ChatOpenAI(openai_api_key="...")
2.Prompts
提示词是模型的输入,通过编写提示词可以和模型进行交互。LangChain 中提供了许多模板和函数用于模块化构建提示词,这些模板可以提供更灵活的方法去生成提示词,具有更好的复用性。根据调用的模型方式不同,提示词模板主要分为普通模板以及聊天提示词模板。
1.提示模板(PromptTemplate)
提示模板是一种生成提示的方式,包含一个带有可替换内容的模板,从用户那获取一组参数并生成提示
提示模板用来生成 LLMs 的提示,最简单的使用场景,比如“我希望你扮演一个代码专家的角色,告诉我这个方法的原理 {code}”。
类似于 python 中用字典的方式格式化字符串,但在 langchain 中都被封装成了对象
一个简单的调用样例如下所示:
from langchain import PromptTemplate
template = """\
You are a naming consultant for new companies.
What is a good name for a company that makes {product}?
"""
prompt = PromptTemplate.from_template(template)
prompt.format(product="colorful socks")
输出结果:
# 实际输出
You are a naming consultant for new companies.
What is a good name for a company that makes colorful socks?
聊天提示模板(ChatPromptTemplate)
聊天模型接收聊天消息作为输入,这些聊天消息通常称为 Message,和原始的提示模板不一样的是,这些消息都会和一个角色进行关联。
在使用聊天模型时,建议使用聊天提示词模板,这样可以充分发挥聊天模型的潜力。
一个简单的使用示例如下:
from langchain.prompts import (
ChatPromptTemplate,
PromptTemplate,
SystemMessagePromptTemplate,
AIMessagePromptTemplate,
HumanMessagePromptTemplate,
)
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
template="You are a helpful assistant that translates {input_language} to {output_language}."
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template="{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
# get a chat completion from the formatted messages
chat_prompt.format_prompt(input_language="English", output_language="French", text="I love programming.").to_messages()
输出结果:
[SystemMessage(content='You are a helpful assistant that translates English to French.', additional_kwargs={}),
HumanMessage(content='I love programming.', additional_kwargs={})]
3.Output parsers
语言模型输出的是普通的字符串,有的时候我们可能想得到结构化的表示,比如 JSON 或者 CSV,一个有效的方法就是使用输出解析器。
输出解析器是帮助构建语言模型输出的类,主要实现了两个功能:
获取格式指令,是一个文本字符串需要指明语言模型的输出应该如何被格式化
解析,一种接受字符串并将其解析成固定结构的方法,可以自定义解析字符串的方式
一个简单的使用示例如下:
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field, validator
from typing import List
model_name = 'text-davinci-003'
temperature = 0.0
model = OpenAI(model_name=model_name, temperature=temperature)
# Define your desired data structure.
class Joke(BaseModel):
setup: str = Field(description="question to set up a joke")
punchline: str = Field(description="answer to resolve the joke")
# You can add custom validation logic easily with Pydantic.
@validator('setup')
def question_ends_with_question_mark(cls, field):
if field[-1] != '?':
raise ValueError("Badly formed question!")
return field
# Set up a parser + inject instructions into the prompt template.
parser = PydanticOutputParser(pydantic_object=Joke)
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# And a query intended to prompt a language model to populate the data structure.
joke_query = "Tell me a joke."
_input = prompt.format_prompt(query=joke_query)
output = model(_input.to_string())
parser.parse(output)
输出结果:
Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!'
二)Data Connection
有的时候,我们希望语言模型可以从自己的数据中进行查询,而不是仅依靠自己本身输出一个结果。数据连接器的组件就允许你使用内置的方法去读取、修改,存储和查询自己的数据,主要有下面几个组件组成。
文档加载器(Document loaders):连接不同的数据源,加载文档。
文档转换器(Document transformers):定义了常见的一些对文档加工的操作,比如切分文档,丢弃无用的数据
文本向量模型(Text embedding models):将非结构化的文本数据转换成一个固定维度的浮点数向量
向量数据库(Vector stores):存储和检索你的向量数据
检索器(Retrievers):用于检索你的数据
三)Chains
只使用一个 LLM 去开发应用,比如聊天机器人是很简单的,但更多的时候,我们需要用到许多 LLM 去共同完成一个任务,这样原来的模式就不足以支撑这种复杂的应用。
为此 LangChain 提出了 Chain 这个概念,也就是一个所有组件的序列,能够把一个个独立的 LLM 链接成一个组件,从而可以完成更复杂的任务。举个例子,我们可以创建一个 chain,用于接收用户的输入,然后使用提示词模板将其格式化,最后将格式化的结果输出到一个 LLM。通过这种链式的组合,就可以构成更多更复杂的 chain。
在 LangChain 中有许多实现好的 chain,以最基础的 LLMChain 为例,它主要实现的就是接收一个提示词模板,然后对用户输入进行格式化,然后输入到一个 LLM,最终返回 LLM 的输出。
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
llm = OpenAI(temperature=0.9)
prompt = PromptTemplate(
input_variables=["product"],
template="What is a good name for a company that makes {product}?",
)
from langchain.chains import LLMChain
chain = LLMChain(llm=llm, prompt=prompt)
# Run the chain only specifying the input variable.
print(chain.run("colorful socks"))
LLMChain 不仅支持 llm,同样也支持 chat llm,下面是一个调用示例:
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
)
human_message_prompt = HumanMessagePromptTemplate(
prompt=PromptTemplate(
template="What is a good name for a company that makes {product}?",
input_variables=["product"],
)
)
chat_prompt_template = ChatPromptTemplate.from_messages([human_message_prompt])
chat = ChatOpenAI(temperature=0.9)
chain = LLMChain(llm=chat, prompt=chat_prompt_template)
print(chain.run("colorful socks"))
四)Memory
大多数的 LLM 应用程序都会有一个会话接口,允许我们和 LLM 进行多轮的对话,并有一定的上下文记忆能力。但实际上,模型本身是不会记忆任何上下文的,只能依靠用户本身的输入去产生输出。而实现这个记忆功能,就需要额外的模块去保存我们和模型对话的上下文信息,然后在下一次请求时,把所有的历史信息都输入给模型,让模型输出最终结果。
而在 LangChain 中,提供这个功能的模块就称为 Memory,用于存储用户和模型交互的历史信息。在 LangChain 中根据功能和返回值的不同,会有多种不同的 Memory 类型,主要可以分为以下几个类别:
对话缓冲区内存(ConversationBufferMemory),最基础的内存模块,用于存储历史的信息
对话缓冲器窗口内存(ConversationBufferWindowMemory),只保存最后的 K 轮对话的信息,因此这种内存空间使用会相对较少
对话摘要内存(ConversationSummaryMemory),这种模式会对历史的所有信息进行抽取,生成摘要信息,然后将摘要信息作为历史信息进行保存。
对话摘要缓存内存(ConversationSummaryBufferMemory),这个和上面的作用基本一致,但是有最大 token 数的限制,达到这个最大 token 数的时候就会进行合并历史信息生成摘要
值得注意的是,对话摘要内存的设计出发点就是语言模型能支持的上下文长度是有限的(一般是 2048),超过了这个长度的数据天然的就被截断了。这个类会根据对话的轮次进行合并,默认值是 2,也就是每 2 轮就开启一次调用 LLM 去合并历史信息。
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(memory_key="chat_history")
memory.chat_memory.add_user_message("hi!")
memory.chat_memory.add_ai_message("whats up?")
参考官方的教程,Memory 同时支持 LLM 和 Chat model。
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
# llm
llm = OpenAI(temperature=0)
# Notice that "chat_history" is present in the prompt template
template = """You are a nice chatbot having a conversation with a human.
Previous conversation:
{chat_history}
New human question: {question}
Response:"""
prompt = PromptTemplate.from_template(template)
# Notice that we need to align the `memory_key`
memory = ConversationBufferMemory(memory_key="chat_history")
conversation = LLMChain(
llm=llm,
prompt=prompt,
verbose=True,
memory=memory
)
conversation({"question": "hi"})
from langchain.chat_models import ChatOpenAI
from langchain.prompts import (
ChatPromptTemplate,
MessagesPlaceholder,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
)
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
llm = ChatOpenAI()
prompt = ChatPromptTemplate(
messages=[
SystemMessagePromptTemplate.from_template(
"You are a nice chatbot having a conversation with a human."
),
# The `variable_name` here is what must align with memory
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template("{question}")
]
)
# Notice that we `return_messages=True` to fit into the MessagesPlaceholder
# Notice that `"chat_history"` aligns with the MessagesPlaceholder name.
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
conversation = LLMChain(
llm=llm,
prompt=prompt,
verbose=True,
memory=memory
)
conversation({"question": "hi"})
五)Agents
代理的核心思想就是使用 LLM 去选择对用户的输入,应该使用哪个特定的工具去进行操作。这里的工具可以是另外的一个 LLM,也可以是一个函数或者一个 chain。在代理模块中,有三个核心的概念。
1、代理(Agent),依托于强力的语言模型和提示词,代理是用来决定下一步要做什么,其核心也是构建一个优秀的提示词。这个提示词大致有下面几个作用:
角色定义,给代理设定一个符合自己的身份
上下文信息,提供给他更多的信息来要求他可以执行什么任务
丰富的提示策略,增加代理的推理能力
2、工具(Tools),代理会选择不同的工具去执行不同的任务。工具主要给代理提供调用自己的方法,并且会描述自己如何被使用。工具的这两点都十分重要,如果你没有提供可以调用工具的方法,那么代理就永远完不成自己的任务;同时如果没有正确的描述工具,代理就不知道如何去使用工具。
3、工具包(Toolkits),LangChain 提供了工具包的使用,在一个工具包里通常包含 3-5 个工具。
Agent 技术是目前大语言模型研究的一个前沿和热点方向,但是目前受限于大模型的实际效果,仅 GPT 4.0 可以有效的开展 Agent 相关的研究。我们相信在未来,随着大模型性能的优化和迭代,Agent 技术应该能有更好的发展和前景。
六)Callbacks
回调,字面解释是让系统回过来调用我们指定好的函数。在 LangChain 中就提供了一个这样的回调系统,允许你进行日志的打印、监控,以及流式传输等其他任务。通过直接在 API 中提供的回调参数,就可以简单的实现回调的功能。LangChain 内置了许多可以实现回调功能的对象,我们通常称为 handlers,用于定义在不同事件触发的时候可以实现的功能。
不管使用 Chains、Models、Tools、Agents,去调用 handlers,均通过是使用 callbacks 参数,这个参数可以在两个不同的地方进行使用:
构造函数中,但它的作用域只能是该对象。比如下面这个 LLMChain 的构造函数可以进行回调,但这个回调函数对于链接到它的 LLM 模型是不生效的。
LLMChain(callbacks=[handler], tags=['a-tag'])
在 run()/apply() 方法中调用,只有当前这一次请求才会相应这个回调函数,但是当前请求包含的子请求都会调用这个回调。比如,使用了一个 chain 去触发这个请求,连接到它的 LLM 模型也会调用这个回调。
chain.run(input, callbacks=[handler])
四、LangChain 的优势
和 LangChain 类似的 LLM 应用开发框架:
OpenAI 的 GPT-3.5/4 API
Hugging Face 的 Transformers(多模态机器学习模型,支持上千预训练模型)
Google 的 T5(NLP 框架)等
LangChain 的优势:
能力更强,更新 by days
代码设计优雅,模块化程度高,Chain、Agent、Memory 模块的抽象程度高,便于结合应用
集成工具完善,从数据预处理、LLM 模型、向量化到图数据库等
支持常用 LLM 和大量商业化 NLP 模型
商业化:Azure OpenAI、OpenAI
开源:Hugging Face、GPT4All
有大量的 LLM 用例供参考
五、基于 LangChain 的应用
从上文中,我们了解了 LangChain 的基本概念,以及主要的组件,利用这些能帮助我们快速上手构建 app。LangChain 能够在很多使用场景中进行应用,包括但不限于:
个人助手和聊天机器人;能够记住和你的每一次互动,并进行个性化的交互
基于文档的问答系统;在特定文档上回答问题,可以减少大模型的幻觉问题
表格数据查询;提供了对结构化数据的查询功能,如 CSV,PDF,SQL,DataFrame 等
API 交互;可以对接不同语言模型的API,并产生交互和调用
信息提取;从文本中提取结构化的信息,并输出
文档总结;利用 LLM 和 embedding 对长文档进行压缩和总结
而且在 github 上也有很多人开源了基于 LangChain 开发的开源应用:
gpt4-pdf-chatbot
chatPDF
Langchain-Chatchat
六、LangChain 的缺点
从实际使用体验来讲,这并不是一个完美的框架,也存在不少问题。
比如,LangChain 的提示词模板其实就是封装了字符串的类,但是 LangChain 中有很多类型的提示词模板,没有看出明显区别,而且也没有安全性,冗余比较多。而且有一些提升词是默认写好的,要修改的话,需要看源码才知道应该修改什么地方。
LangChain 内部封装了很多调用过程,debug 的过程比较困难。一旦出现了问题,排查所花费的时间可能会比较长。
之前有爆出过 LangChain 的代码在调用 python 去执行 agent 的时候会存在安全漏洞,有可能通过注入攻击的方式产生危险。但是这些类似的漏洞,需要官方去修复才可以,会给我们开发带来不必要的麻烦。
LangChain 的文档过于简单了,如果要实现一些官方没有提供的方法就需要动一些脑筋。
总结
LangChain 作为一个新兴的开源 LLM 开发框架,它的设计理念和一些实现方法都有值得我们借鉴的地方。当然一个开源项目必然有自己的优点的同时也会存在不少的缺点。我们在实际使用的时候,应该按照自己的需求和实际情况去选择,切记,不要简单的因为网上的评论而做出选择。毕竟,好不好用,只有你自己才清楚。
LangGraph 快速入门
LangGraph 并不是一个独立于 LangChain 的新框架,而是在 LLM 和 LangChain 的基础之上构建的一个扩展库,可以于 LangChain 现有的链(Chain)等无缝协作
LangGraph 能够协调多个 Chain、Agent、Tool 等共同协作,实现依赖外部工具、外部数据库且带有反馈的问答任务
编写一个使用硅基流动Deepseek接口多轮对话对token优化后的python例子
根据参考内容优化的硅基流动DeepSeek多轮对话Python示例,包含Token优化策略与完整错误处理:
import json
import requests
from typing import List, Dict
class DeepSeekChatOptimizer:
def __init__(self, api_key: str):
self.api_url = "https://api.siliconflow.cn/v1/chat/completions"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
self.message_history: List[Dict] = []
self.max_history_tokens = 3000 # 保留最近3000 tokens的对话历史
def _calculate_tokens(self, text: str) -> int:
"""简易token估算 (1个汉字≈1.3 tokens)"""
chinese_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
other_chars = len(text) - chinese_chars
return int(chinese_chars * 1.3 + other_chars * 0.25)
def _trim_history(self):
"""动态修剪对话历史"""
total_tokens = 0
trim_index = len(self.message_history)
# 从最新消息开始反向计算
for msg in reversed(self.message_history):
tokens = self._calculate_tokens(msg["content"])
if total_tokens + tokens > self.max_history_tokens:
break
total_tokens += tokens
trim_index -= 1
# 保留最近的可用历史
self.message_history = self.message_history[trim_index:]
def chat_round(self, user_input: str) -> str:
try:
# 添加用户消息
self.message_history.append({"role": "user", "content": user_input})
# 动态修剪历史
self._trim_history()
# 构造请求参数
payload = {
"model": "deepseek-ai/DeepSeek-R1",
"messages": self.message_history,
"temperature": 0.7,
"max_tokens": 512, # 限制单次生成长度
"top_p": 0.9,
"frequency_penalty": 0.5 # 降低重复内容
}
response = requests.post(
self.api_url,
headers=self.headers,
json=payload,
timeout=10
)
if response.status_code != 200:
error_msg = f"API Error {response.status_code}: {response.text}"
self.message_history.append({"role": "system", "content": error_msg})
return error_msg
result = response.json()
assistant_reply = result['choices'][0]['message']['content']
# 添加助手消息并返回
self.message_history.append({"role": "assistant", "content": assistant_reply})
return assistant_reply
except Exception as e:
error_msg = f"System Error: {str(e)}"
self.message_history.append({"role": "system", "content": error_msg})
return error_msg
# 使用示例
if __name__ == "__main__":
api_key = "your_api_key_here" # 替换实际API密钥
chat = DeepSeekChatOptimizer(api_key)
while True:
user_input = input("You: ")
if user_input.lower() in ["exit", "quit"]:
break
response = chat.chat_round(user_input)
print(f"Assistant: {response}")
print(f"当前历史记录数: {len(chat.message_history)}条")
上一篇:AI人工智能应用编程
下一篇:AI智体创建经验收集