基于Ollama,Milvus构建的建议知识检索系统

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
相关推荐
mOok ONSC3 小时前
SpringBoot项目中读取resource目录下的文件(六种方法)
spring boot·python·pycharm
ZPC82103 小时前
如何创建一个单例类 (Singleton)
开发语言·前端·人工智能
AppOS3 小时前
手把手教你 Openclaw 在 Mac 上本地化部署,保姆级教程!接入飞书打造私人 AI 助手
人工智能·macos·飞书
workflower4 小时前
AI制造-推荐初始步骤
java·开发语言·人工智能·软件工程·制造·需求分析·软件需求
wukangjupingbb4 小时前
解析Computational driven drug discovery: from structure to clinic
人工智能·机器学习
tctasia4 小时前
TCT Asia 2026现场观察:中国增材制造,已经进入“规模化时刻”(上)
大数据·人工智能·制造
AI周红伟4 小时前
AI自动盯盘与定时行情分析:OpenClaw股票辅助Agent集成完整使用指南-周红伟
运维·服务器·人工智能·音视频·火山引擎
GIS兵墩墩4 小时前
postgis--PostgreSQL16及其plpython3u扩展
python·postgis
new Object ~4 小时前
LangChain的短期记忆存储实现
python·langchain