python
复制代码
import traceback
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pymilvus import MilvusClient, DataType
from langchain_ollama import OllamaEmbeddings
from openai import OpenAI
from pydantic import BaseModel, Field
import json
class LLMResponse(BaseModel):
"""
Response model for the LLM output.
"""
question: str = Field(..., description="用户提问")
knowledge: str = Field(..., description="知识库查询结果")
response: str = Field(..., description="ai 最终返回结果")
class LocalMdSpliter:
"""
用于加载本地 Markdown 文件并进行文本切片的类。
"""
def __init__(self, path='/home/jhc/下载/mds'):
self.docs = self._load_documents(path)
def _load_documents(self, path):
"""
加载指定目录下的所有 .md 文件。
:param path: 目录路径
:return: 加载后的文档列表
"""
loader = DirectoryLoader(
path=path,
glob="*.md",
loader_cls=TextLoader,
show_progress=True
)
return loader.load()
def split_documents(self, chunk_size=500, chunk_overlap=50):
"""
将加载的文档切分为较小的文本块。
:param chunk_size: 每个块的最大字符数
:param chunk_overlap: 块与块之间的重叠字符数
:return: 切分后的文档块列表
"""
text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
return text_splitter.split_documents(self.docs)
class LocalProcess:
"""
本地Milvus
"""
def __init__(self, client, embedding):
self.client = client
self.embedding = embedding
def _load_docs(self):
"""
:return:
"""
lms = LocalMdSpliter()
docs = lms.split_documents()
return docs
def _clear_old_collections(self, collection_name):
"""
:param collection_name:
:return:
"""
if self.client.has_collection(collection_name):
self.client.drop_collection(collection_name)
def _build_schema(self):
"""
:return:
"""
schema = client.create_schema()
schema.add_field("id", DataType.INT64, auto_id=True, is_primary=True)
schema.add_field("content", DataType.VARCHAR, max_length=65535)
schema.add_field("path", DataType.VARCHAR, max_length=1000)
schema.add_field("content_emb", DataType.FLOAT_VECTOR, dim=768)
return schema
def _build_index(self):
"""
:return:
"""
index_params = client.prepare_index_params()
index_params.add_index("content_emb", index_name="content_emb_idx", index_type="AUTOINDEX")
return index_params
def create_collections(self, collection_name):
"""
:param collection_name:
:return:
"""
schema = self._build_schema()
index_params = self._build_index()
self._clear_old_collections(collection_name)
client.create_collection(collection_name=collection_name, schema=schema, index_params=index_params)
def insert_batchs(self, collection_name, batchs=100):
"""
:param collection_name:
:param batchs:
:return:
"""
docs = self._load_docs()
for i in range(0, len(docs), batchs):
batch_docs = docs[i:i + batchs]
text_list = [i.page_content for i in batch_docs]
path_list = [i.metadata["source"] for i in batch_docs]
eb_list = emb.embed_documents(text_list)
data = []
for text, path, eb in zip(text_list, path_list, eb_list):
data.append({
"content": text,
"path": path,
"content_emb": eb
})
self.client.insert(collection_name=collection_name, data=data)
class Msg:
"""
ai 消息类
"""
def __init__(self, system, limit=20):
self.system = system
self.limit = limit
self._message = []
self.add_system_msg(system)
def add_user_msg(self, msg):
"""
:param msg:
:return:
"""
self._clear_old_messages()
self._message.append({
"role": "user",
"content": msg
})
def add_assistant_msg(self, msg):
"""
:param msg:
:return:
"""
self._clear_old_messages()
self._message.append({
"role": "assistant",
"content": msg
})
def add_system_msg(self, msg):
"""
:param msg:
:return:
"""
self._clear_old_messages()
self._message.append({
"role": "system",
"content": msg
})
def _clear_old_messages(self):
"""
:return:
"""
print(len(self._message), self.limit)
while len(self._message) > self.limit:
self._message.pop(1)
def get_msg(self):
"""
:return:
"""
return self._message
class LLM:
"""
llm
"""
def __init__(self, url, system):
self.url = url
self.system = system
self.msg = Msg(system)
self.client = OpenAI(
base_url=self.url,
api_key="ollama"
)
def chat(self, messages, model):
"""
:param messages:
:param model:
:return:
"""
return self.client.chat.completions.create(
model=model,
messages=messages
)
if __name__ == '__main__':
emb = OllamaEmbeddings(
model="embeddinggemma:latest",
base_url="http://127.0.0.1:11434"
)
MILVUS_URI = "http://127.0.0.1:19530"
COLLECTION_NAME = "test"
BATCH_SIZE = 100
client = MilvusClient(uri=MILVUS_URI)
lp = LocalProcess(client, emb)
# # 添加数据
# lp.create_collections(COLLECTION_NAME)
# lp.insert_batchs(COLLECTION_NAME, BATCH_SIZE)
question = "C语言使用什么来申请和释放内存?"
question = "从用户在浏览器输入url开始到最终渲染出结果都经历了哪些步骤,详细说说"
system = """你是一个知识助手,能够基于用户给到的提示,以及知识库搜索得到的数据,自动分析并结合知识库搜索结果中的权重,构建符合用户要求的返回结果,并且回复格式参照如下格式
{"question":"","knowledge":"","response":""}
字段解释:
question:用户问题
knowledge:知识库检索结果
response:ai整合用户问题和知识库检索结果生成的最终结论
所有回复必须用中文
"""
ai_model = "deepseek-v3.2:cloud"
llm = LLM(url="http://localhost:11434/v1", system=system)
llm.msg.add_user_msg(question)
search_result = client.search(COLLECTION_NAME,
data=[emb.embed_query(question)],
limit=3,
output_fields=["content", "path"]
)
for sr in search_result[0]:
distance = sr["distance"]
content = sr["entity"]["content"]
path = sr["entity"]["path"]
# print(distance, content, path)
llm.msg.add_user_msg(f"这是在知识库:{path} 中查询的相关结果: {content} 权重为:{distance}")
end_status = False
LIMIT_LOOP_NUMS = 5
loop_nums = 0
while not end_status and loop_nums < LIMIT_LOOP_NUMS:
loop_nums += 1
try:
print(f"当前提示词:{llm.msg.get_msg()}")
llm_result = llm.chat(llm.msg.get_msg(), ai_model)
now_llm_res = llm_result.choices[0].message.content
now_llm_res_json = json.loads(now_llm_res)
end_status = True if llm_result.choices[0].finish_reason == "stop" else False
valid_response = LLMResponse(
**now_llm_res_json
)
assistant_msg = f"第 {loop_nums} 轮 结果 {now_llm_res} 清继续续生成"
print("assistant_msg normal", assistant_msg)
except Exception as e:
assistant_msg = f"第 {loop_nums} 轮 发生异常:\n{traceback.format_exc()} 请分析并解决错误"
print("assistant_msg error", assistant_msg)
finally:
llm.msg.add_assistant_msg(assistant_msg)
bash
复制代码
/home/jhc/PyCharmMiscProject/.venv/bin/python /home/jhc/PyCharmMiscProject/langchain_test/demo.py
PyTorch was not found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
0 20
1 20
2 20
3 20
4 20
当前提示词:[{'role': 'system', 'content': '你是一个知识助手,能够基于用户给到的提示,以及知识库搜索得到的数据,自动分析并结合知识库搜索结果中的权重,构建符合用户要求的返回结果,并且回复格式参照如下格式\n {"question":"","knowledge":"","response":""}\n 字段解释:\n question:用户问题\n knowledge:知识库检索结果\n response:ai整合用户问题和知识库检索结果生成的最终结论\n 所有回复必须用中文\n '}, {'role': 'user', 'content': '从用户在浏览器输入url开始到最终渲染出结果都经历了哪些步骤,详细说说'}, {'role': 'user', 'content': '这是在知识库:/home/jhc/下载/mds/实习面试题-绿盟科技面试题.md 中查询的相关结果: <p>1)<strong>表达式解析</strong>:</p>\n<ul>\n<li>使用 <code>ReactDOMServer.renderToString</code> 或 <code>ReactDOMServer.renderToStaticMarkup</code> 在服务器端将 React 组件转换为 HTML 字符串。</li>\n</ul>\n<p>2)<strong>创建服务</strong>:</p>\n<ul>\n<li>使用 Node.js 和 <code>express</code> 服务器框架来处理 HTTP 请求。</li>\n<li>服务接收到请求后,调用 React 渲染方法生成 HTML,并将 HTML 发送给客户端。</li>\n</ul>\n<p>3)<strong>客户端激活</strong>:</p>\n<ul>\n<li>客户端接收到 HTML 内容后,会通过 React 的 <code>ReactDOM.hydrate</code> 方法将静态 HTML 转换为可交互的 React 应用。</li>\n</ul>\n<p>为什么使用 SSR? 权重为:0.5187225341796875'}, {'role': 'user', 'content': '这是在知识库:/home/jhc/下载/mds/实习面试题-绿盟科技面试题.md 中查询的相关结果: <ul>\n<li>服务端渲染比客户端渲染复杂,需要处理更多的边界情况和优化。</li>\n</ul>\n<p>2)<strong>服务器负载</strong>:</p>\n<ul>\n<li>服务器需要处理更多渲染任务,因此需要提高服务器的处理能力。</li>\n</ul>\n<p>3)<strong>状态管理</strong>:</p>\n<ul>\n<li>需要将服务器的初始状态传递给客户端,以保证客户端和服务器的一致性。</li>\n</ul>\n<p>...</p> 权重为:0.5091856718063354'}, {'role': 'user', 'content': '这是在知识库:/home/jhc/下载/mds/实习面试题-绿盟科技面试题.md 中查询的相关结果: </ul>\n<p>为什么使用 SSR?\n1)<strong>SEO 优化</strong>:</p>\n<ul>\n<li>SSR 生成的 HTML 内容在传输到客户端前已经完整,搜索引擎能够更好地抓取和索引这些内容。</li>\n</ul>\n<p>2)<strong>提升性能</strong>:</p>\n<ul>\n<li>首屏渲染速度更快,用户体验更好。客户端渲染的应用,浏览器需要首先下载完整的 JavaScript 文件,然后再执行代码去生成内容。而 SSR 直接提供了已经渲染好的 HTML。</li>\n</ul>\n<p>3)<strong>减少闪烁</strong>:</p>\n<ul>\n<li>SSR 让用户在加载 JavaScript 前就看到了完整的页面,减少了由于 JS 渲染所带来的闪烁现象。</li>\n</ul>\n<p>不过,实现 SSR 也要注意以下挑战:\n1)<strong>复杂性增加</strong>:</p>\n<ul>\n<li>服务端渲染比客户端渲染复杂,需要处理更多的边界情况和优化。</li>\n</ul> 权重为:0.5052074193954468'}]
assistant_msg normal 第 1 轮 结果 {
"question": "从用户在浏览器输入url开始到最终渲染出结果都经历了哪些步骤,详细说说",
"knowledge": "知识库检索结果主要涉及服务器端渲染(SSR)的相关内容。包括:1. SSR的实现步骤:表达式解析(使用ReactDOMServer.renderToString等)、创建服务(使用Node.js和express)、客户端激活(使用ReactDOM.hydrate)。2. 使用SSR的原因:SEO优化、提升性能(首屏渲染更快)、减少闪烁。3. SSR的挑战:复杂性增加、服务器负载较高、需要状态管理以保证客户端与服务器一致性。",
"response": "从用户在浏览器输入URL到最终渲染出结果,整个过程涉及多个步骤,主要包括网络请求、服务器处理、资源加载和浏览器渲染。以下是详细步骤:\n\n1. **URL输入和解析**:用户在浏览器地址栏输入URL后,浏览器会解析URL,提取协议(如HTTP/HTTPS)、域名、路径和查询参数等。\n\n2. **DNS查询**:浏览器根据域名向DNS服务器发起查询,将域名解析为对应的IP地址。如果缓存中有记录,则直接使用,否则进行递归查询。\n\n3. **建立TCP连接**:浏览器使用解析到的IP地址与服务器建立TCP连接。如果使用HTTPS,还会进行TLS握手以加密连接。\n\n4. **发送HTTP请求**:浏览器构建HTTP请求(包括请求头、方法如GET等),并通过TCP连接发送到服务器。请求中可能包含缓存控制、Cookie等信息。\n\n5. **服务器处理请求**:服务器接收到请求后,根据URL和请求参数处理请求。这里可以涉及不同的渲染方式:\n - 如果是静态资源,服务器直接返回文件。\n - 如果是动态页面,服务器可能执行后端逻辑生成HTML。在服务器端渲染(SSR)场景中,如知识库所述,服务器使用框架(如React的ReactDOMServer.renderToString)将组件渲染为HTML字符串,然后返回给客户端。SSR有助于SEO优化和提升首屏性能,但会增加服务器负载和复杂性。\n\n6. **接收HTTP响应**:浏览器收到服务器的HTTP响应,包括状态码、响应头和响应体(如HTML、CSS、JavaScript文件)。\n\n7. **解析HTML和构建DOM树**:浏览器开始解析HTML文档,逐行读取并构建DOM(文档对象模型)树,表示页面的结构。\n\n8. **解析CSS和构建CSSOM树**:同时,浏览器解析CSS样式表,构建CSSOM(CSS对象模型)树,用于计算元素的样式。\n\n9. **生成渲染树和布局**:浏览器结合DOM树和CSSOM树,生成渲染树(只包含可见元素),然后进行布局(或回流),计算每个元素在屏幕上的位置和大小。\n\n10. **绘制页面**:浏览器根据渲染树进行绘制(或重绘),将像素渲染到屏幕上,显示初步内容。\n\n11. **JavaScript执行和交互**:如果页面包含JavaScript,浏览器会加载并执行JS代码。JS可能修改DOM或CSSOM,触发重新渲染。在SSR场景中,如知识库所述,客户端会使用ReactDOM.hydrate等方法将静态HTML激活为可交互的应用,确保状态一致性。\n\n12. **最终渲染完成**:所有资源加载和执行完成后,页面完全渲染,用户可以交互。浏览器可能继续处理异步请求或动态更新内容。\n\n整个过程依赖于网络、服务器性能和浏览器优化。SSR作为一种优化手段,可以在服务器端提前渲染内容,减少客户端负担,但需权衡服务器负载和开发复杂度。"
} 清继续续生成
5 20
Process finished with exit code 0