├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md └── server ├── requirements.txt ├── response.py └── services ├── api.py ├── assert ├── Chinook.db ├── law.csv └── nature-boardwalk.jpg ├── common ├── LimitedChatMessageHistory.py ├── MyJsonOutputParser.py └── MyVectorDB.py ├── consulting.py ├── multimodal.py ├── practice ├── 00.streaming_2.py ├── 00_assistant.py ├── 00_assistant_2.py ├── 00_chat.py ├── 00_chatbot.py ├── 00_tool_InjectedToolArg_2.py ├── 00_tool_InjectedToolArg_3.py ├── 00_tool_InjectedToolArg_4.py ├── 01_translation.py ├── 02_vector_store_pdf.py ├── 03_create_db_csv.py ├── 04_classification.py ├── 05_extraction_1.py ├── 06_extraction_2.py ├── 07_chatbot_1.py ├── 08_chatbot_2.py ├── 09_chatbot_3.py ├── 10_tool_1.py ├── 11_tool_ad_hoc.py ├── 12_tool_human_in_the_loop.py ├── 13_tool_InjectedToolArg_1.py ├── 14.tool_in_agent.py ├── 15_agent_executor.py ├── 16_agent_executor.py ├── 17.tool_in_RAG.py ├── 18_rag_graph.py ├── 19_rag_graph_2.py ├── 20_rag_graph_3.py ├── 21_rag_graph_with_query_analysis.py ├── 22_qa_sql.py ├── 23_qa_sql_agent.py ├── 23_qa_sql_agent_cn.py ├── 24_qa_sql_agent_2.py ├── 25_qa_sql_graph.py ├── 26_qa_sql_graph_2.py ├── 27.streaming.py ├── 28.qa_graph.py ├── 29.qa_graph_advanced.py ├── 30.summarize.py ├── 31.summarize_map_reduce.py ├── 32.websocket.py ├── assert │ ├── Chinook.db │ ├── Chinook_Sqlite.sql │ ├── LLM Powered Autonomous Agents _ Lil'Log.html │ ├── LLM Powered Autonomous Agents _ Lil'Log_files │ │ ├── CoH.png │ │ ├── agent-overview.png │ │ ├── algorithm-distillation-results.png │ │ ├── algorithm-distillation.png │ │ ├── api-bank-process.png │ │ ├── generative-agents.png │ │ ├── highlight.min.2eadbb982468c11a433a3e291f01326f2ba43f065e256bf792dbd79640a92316.js.下载 │ │ ├── hugging-gpt.png │ │ ├── js │ │ ├── memory.png │ │ ├── mips.png │ │ ├── react.png │ │ ├── reflexion-exp.png │ │ ├── reflexion.png │ │ ├── sea-otter.png │ │ └── tex-mml-chtml.js.下载 │ ├── animals.csv │ ├── animals_all-minilm-33m │ │ ├── 02b95cf8-ada8-4129-9d33-297c0bd3860e │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ └── chroma.sqlite3 │ ├── animals_llama3.1 │ │ ├── c0bfe162-fc8e-4749-9675-268e4491b1db │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ └── chroma.sqlite3 │ ├── animals_milkey │ │ └── m3e │ │ │ ├── 84ad69fd-743e-4966-a092-feaeb3ccb3e2 │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ │ └── chroma.sqlite3 │ ├── animals_mxbai-embed-large │ │ ├── 74493a2e-f195-413c-8d82-237020813700 │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ └── chroma.sqlite3 │ ├── animals_nomic-embed-text │ │ ├── 3136354c-7083-4ed7-a434-b15215d7390b │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ └── chroma.sqlite3 │ ├── animals_qwen2.5 │ │ ├── 129f6664-0acf-4322-81b0-8561abb32505 │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ └── chroma.sqlite3 │ ├── animals_shaw │ │ └── dmeta-embedding-zh │ │ │ ├── 7d285bf7-8f9a-4f7d-a328-a9a7757d7bd8 │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ │ └── chroma.sqlite3 │ ├── db_artists_albums │ │ ├── c6e6a8fa-e3e4-4d20-9de1-107800b02132 │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ └── chroma.sqlite3 │ ├── db_law │ │ ├── 4c898430-74c7-47dc-a6a0-f414c514c990 │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ └── chroma.sqlite3 │ ├── es_shaw │ │ └── dmeta-embedding-zh │ │ │ ├── 13a83a34-0f64-44d0-bf44-37007d68f247 │ │ │ ├── data_level0.bin │ │ │ ├── header.bin │ │ │ ├── length.bin │ │ │ └── link_lists.bin │ │ │ └── chroma.sqlite3 │ ├── law.csv │ ├── movies_small.csv │ ├── nke-10k-2023.pdf │ └── rag_shaw │ │ └── dmeta-embedding-zh │ │ ├── 427c1a6a-4f48-4648-adc8-9784f8769401 │ │ ├── data_level0.bin │ │ ├── header.bin │ │ ├── length.bin │ │ └── link_lists.bin │ │ └── chroma.sqlite3 ├── chat.html ├── utils.py └── vector_store.py ├── response.py ├── start.bat └── translation.py /.gitignore: -------------------------------------------------------------------------------- 1 | /server/services/llm/__pycache__ 2 | /server/services/llm/common/__pycache__ 3 | /server/services/__pycache__ 4 | __pycache__ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Practical local LLM programming 2 | Demonstrate how to use the `LLM(local large language model)` through practical examples. 3 | 4 | Including: chatbots, semantic retrieval, text tag classification, knowledge extraction from text, intelligent agents, RAG (Retrieval Augmented Generation), querying SQL data, extracting text summaries, etc. 5 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # Practical local LLM programming 2 | 通过编程实例演示如何玩转本地大型语言模型。 3 | 包括:聊天机器人、语义检索、文本标签分类、从文本中提取知识、智能体、RAG(Retrieval Augmented Generation,检索增强生成)、查询SQL数据、提取文本概要等。 4 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/requirements.txt -------------------------------------------------------------------------------- /server/response.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | #!/usr/bin/python 4 | # -*- coding:utf-8 -*- 5 | # @author : 刘立军 6 | # @time : 2024-12-27 7 | # @Description: 定义API的标准响应 8 | # @version : V0.5 9 | 10 | from pydantic import BaseModel, Field 11 | 12 | from enum import Enum 13 | 14 | # 操作结果枚举 15 | class code_enum(str,Enum): 16 | OK = 'ok' 17 | ERR = 'error' 18 | 19 | # API返回的消息体 20 | class response_model(BaseModel): 21 | code: code_enum = Field(description="操作结果" ) 22 | desc: str = Field(description="具体内容" ) -------------------------------------------------------------------------------- /server/services/api.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | #!/usr/bin/python 4 | # -*- coding:utf-8 -*- 5 | # @author : 刘立军 6 | # @time : 2025-01-06 7 | # @Description: 使用FastAPI和langchain翻译服务API 8 | # @version : V0.5 9 | 10 | 11 | import sys 12 | import os 13 | 14 | # 将上级目录加入path,这样就可以直接引用response等上级目录的模块 15 | parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 16 | sys.path.append(parent_dir) 17 | 18 | # 导入FastAPI和Pydantic 19 | from fastapi import FastAPI, Request 20 | from pydantic import BaseModel, Field 21 | 22 | from response import response_model,code_enum 23 | 24 | # 创建一个FastAPI实例 25 | app = FastAPI() 26 | 27 | from translation import translate 28 | 29 | class query_model_translation(BaseModel): 30 | lang: str = Field(min_length=2, max_length=20, description="语言名称" ) 31 | text: str = Field(min_length=2, max_length=500, description="待翻译的文本" ) 32 | 33 | 34 | """ 35 | !注意:设置端点时,建议养成都不加 / 的风格。 36 | 在使用API网关时,如果从API网关传过来的路径是以 / 结尾的话,因为和此端点路径不一致,此端点会自动返回301重定向,导致客户端发生400错误。 37 | """ 38 | 39 | @app.post("/trans/v1", response_model=response_model) 40 | async def translate(query: query_model_translation,request: Request): 41 | """ 42 | 翻译文本。 43 | 44 | 参数: 45 | - query_model_translation: 翻译请求内容。 46 | 47 | 返回: 48 | - response: 对象 49 | """ 50 | userid = request.headers.get("userid") 51 | print(f"userid: {userid}") 52 | 53 | try: 54 | r = translate(query.lang.strip(),query.text.strip()) 55 | return response_model(code=code_enum.OK,desc=r) 56 | except Exception as e: 57 | return response_model(code=code_enum.ERR,desc=str(e)) 58 | 59 | from consulting import consult 60 | 61 | class query_model_consult(BaseModel): 62 | question: str = Field(min_length=2, max_length=1000, description="咨询的问题内容" ) 63 | 64 | @app.post("/consult/v1", response_model=response_model) 65 | async def consult(query: query_model_consult,request: Request): 66 | """ 67 | 咨询专家 68 | 69 | 参数: 70 | - query_model_consult: 翻译请求内容。 71 | 72 | 返回: 73 | - response: 对象 74 | """ 75 | userid = request.headers.get("userid") 76 | if userid is None: 77 | userid = "test" 78 | print(f"userid: {userid}") 79 | try: 80 | r = consult(query.question.strip(),userid) 81 | return response_model(code=code_enum.OK,desc=r) 82 | except Exception as e: 83 | return response_model(code=code_enum.ERR,desc=str(e)) 84 | 85 | import uvicorn 86 | 87 | if __name__ == '__main__': 88 | """ 89 | 交互式API文档地址: 90 | http://127.0.0.1:5001/docs/ 91 | http://127.0.0.1:5001/redoc/ 92 | """ 93 | 94 | uvicorn.run(app, host="0.0.0.0", port=5001) 95 | -------------------------------------------------------------------------------- /server/services/assert/Chinook.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/assert/Chinook.db -------------------------------------------------------------------------------- /server/services/assert/nature-boardwalk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/assert/nature-boardwalk.jpg -------------------------------------------------------------------------------- /server/services/common/LimitedChatMessageHistory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-06 5 | # @function: 扩展的聊天历史记录类。 6 | # @version : V0.5 7 | # @Description :可以限制聊天记录的最大长度。max_size:设置为偶数。因为User和AI的消息会分别记录为1条,设置为偶数后,User和AI才会成对。 8 | 9 | ''' 10 | 在 https://python.langchain.com/v0.2/docs/tutorials/chatbot/ 中有使用trim_messages对消息历史进行裁剪的例子 11 | 但是这里依然需要用大模型来计算token,通过计算结果进行裁剪,比较耗费资源。 12 | 13 | 本例的方法没那么智能,也可能有时候会突破token大小限制出错,但是我想已经能解决绝大部分问题了。 14 | ''' 15 | 16 | from langchain.schema import BaseMessage 17 | from langchain_community.chat_message_histories import ChatMessageHistory 18 | 19 | class MessageHistory(ChatMessageHistory): 20 | """ 21 | 扩展的聊天历史记录类。可以限制聊天记录的最大长度。 22 | 23 | Args: 24 | max_size: 设置为偶数。因为User和AI的消息会分别记录为1条,设置为偶数后,User和AI才会成对。 25 | """ 26 | 27 | def __init__(self, max_size: int): 28 | super().__init__() 29 | self._max_size = max_size 30 | 31 | def add_message(self, message: BaseMessage): 32 | super().add_message(message) 33 | 34 | # 保持聊天记录在限制范围内 35 | if len(self.messages) > self._max_size: 36 | print('消息超限,马上压缩!') 37 | self.messages = self.messages[-self._max_size:] 38 | 39 | from langchain_core.chat_history import BaseChatMessageHistory 40 | from langchain_core.messages import AIMessage 41 | 42 | class SessionHistory(object): 43 | """ 44 | 处理消息历史 45 | """ 46 | def __init__(self,max_size: int): 47 | super().__init__() 48 | self._max_size = max_size 49 | self._store = {} 50 | 51 | def process(self,session_id: str) -> BaseChatMessageHistory: 52 | """ 53 | 处理聊天历史 54 | """ 55 | if session_id not in self._store: 56 | self._store[session_id] = MessageHistory(max_size=self._max_size) 57 | return self._store[session_id] 58 | 59 | def print_history(self,session_id): 60 | """ 61 | 查看聊天历史记录 62 | """ 63 | print("显示聊天历史记录...") 64 | for message in self._store[session_id].messages: 65 | if isinstance(message, AIMessage): 66 | prefix = "AI" 67 | else: 68 | prefix = "User" 69 | 70 | print(f"{prefix}: {message.content}\n") 71 | -------------------------------------------------------------------------------- /server/services/common/MyJsonOutputParser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-02-08 5 | # @function: 扩展的JsonOutputParser类。 6 | # @version : V0.5 7 | # @Description :扩展的JsonOutputParser类,用来支持langchain支持的不好的模型。 8 | 9 | from langchain_core.outputs import Generation 10 | from langchain_core.output_parsers import JsonOutputParser 11 | from typing import Any 12 | import re 13 | 14 | class ThinkJsonOutputParser(JsonOutputParser): 15 | def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any: 16 | """将 LLM 调用的结果解析为 JSON 对象。支持deepseek。 17 | 18 | Args: 19 | result: LLM 调用的结果。 20 | partial: 是否解析 partial JSON 对象。 21 | If True, 输出将是一个 JSON 对象,其中包含迄今为止已返回的所有键。 22 | If False, 输出将是完整的 JSON 对象。 23 | 默认值为 False. 24 | 25 | Returns: 26 | 解析后的 JSON 对象。 27 | 28 | Raises: 29 | OutputParserException: 如果输出不是有效的 JSON。 30 | """ 31 | 32 | text = result[0].text 33 | text = text.strip() 34 | 35 | # 判断是否为 deepseek生成的内容,如果是的话,提取其中的json字符串 36 | if '' in text and '' in text: 37 | match = re.search(r'\{.*\}', text.strip(), re.DOTALL) 38 | if match: 39 | text = match.group(0) 40 | result[0].text = text 41 | 42 | return super().parse_result(result, partial=partial) -------------------------------------------------------------------------------- /server/services/common/MyVectorDB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-02-11 5 | # @function: 在本地处理矢量数据库。 6 | # @version : V0.5 7 | # @Description :使用Chroma,处理本地适量数据库。 8 | 9 | from langchain_chroma import Chroma 10 | from langchain_ollama import OllamaEmbeddings 11 | from tqdm import tqdm 12 | 13 | from langchain.text_splitter import RecursiveCharacterTextSplitter 14 | 15 | class LocalVectorDBChroma: 16 | """使用Chroma在本地处理适量数据库""" 17 | 18 | def __init__(self,model_name,persist_directory,delimiter = ","): 19 | self._embedding = OllamaEmbeddings(model=model_name) 20 | self._persist_directory = persist_directory 21 | self._delimiter = delimiter 22 | 23 | def get_vector_store(self): 24 | return Chroma(persist_directory=self._persist_directory,embedding_function=self._embedding) 25 | 26 | def embed_documents_in_batches(self,documents,batch_size=3): 27 | """ 28 | 按批次嵌入,可以显示进度。 29 | vectordb会自动持久化存储在磁盘。 30 | """ 31 | 32 | vectordb = Chroma(persist_directory=self._persist_directory,embedding_function=self._embedding) 33 | for i in tqdm(range(0, len(documents), batch_size), desc="嵌入进度"): 34 | batch = documents[i:i + batch_size] 35 | 36 | # 从文本块生成嵌入,并将嵌入存储在本地磁盘。 37 | vectordb.add_documents(batch) 38 | 39 | def embed_csv(self,src_file_path): 40 | """嵌入csv""" 41 | 42 | from langchain_community.document_loaders import CSVLoader 43 | 44 | loader = CSVLoader(file_path=src_file_path, 45 | csv_args={"delimiter": self._delimiter}, 46 | autodetect_encoding=True) 47 | docs = loader.load() 48 | 49 | # 用于将长文本拆分成较小的段,便于嵌入和大模型处理。 50 | text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20) 51 | """ 52 | chunk_size: 每个文本块的最大长度/字符数 53 | chunk_overlap: 拆分的文本块之间重叠字符数 54 | """ 55 | documents = text_splitter.split_documents(docs) 56 | 57 | # 耗时较长,需要耐心等候... 58 | self.embed_documents_in_batches(documents) 59 | 60 | def embed_webpage(self,url): 61 | """嵌入网页""" 62 | 63 | from langchain_community.document_loaders import WebBaseLoader 64 | 65 | loader = WebBaseLoader(url,encoding="utf-8") # 增加encoding参数防止中文乱码 66 | docs = loader.load() 67 | documents = RecursiveCharacterTextSplitter( 68 | chunk_size=1000, chunk_overlap=200 69 | ).split_documents(docs) 70 | 71 | self.embed_documents_in_batches(documents) 72 | -------------------------------------------------------------------------------- /server/services/consulting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-06 5 | # @function: 基于langchian和实现的对话式RAG(RAG,Retrieval Augmented Generation,即:增强生成)实现知识问答 6 | # @version : V0.5 7 | # @Description :在问答的过程中,系统自动存储以往的问题和答案,产生“记忆”功能,提升会话体验。 8 | 9 | import os 10 | 11 | # 获取当前文件的绝对路径 12 | current_file_path = os.path.abspath(__file__) 13 | 14 | # 获取当前文件所在的目录 15 | current_dir = os.path.dirname(current_file_path) 16 | 17 | persist_directory = 'db_law' 18 | model_name = 'llama3.1' 19 | 20 | from langchain_chroma import Chroma 21 | from langchain_community.embeddings import OllamaEmbeddings 22 | from langchain_ollama import ChatOllama 23 | from langchain_core.prompts import ChatPromptTemplate 24 | from langchain.chains import create_retrieval_chain 25 | from langchain.chains.combine_documents import create_stuff_documents_chain 26 | from langchain_core.prompts import MessagesPlaceholder 27 | from langchain.chains import create_history_aware_retriever 28 | from langchain_core.messages import AIMessage 29 | from langchain_core.runnables.history import RunnableWithMessageHistory 30 | 31 | 32 | # 返回本地大模型 33 | def get_llm(): 34 | 35 | # temperature:用于控制生成语言模型中生成文本的随机性和创造性。 36 | # 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性。 37 | # 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 38 | # 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 39 | return ChatOllama(model=model_name,temperature=0.1,verbose=True) 40 | 41 | from langchain_core.chat_history import BaseChatMessageHistory 42 | 43 | # 处理聊天历史 44 | from common.LimitedChatMessageHistory import SessionHistory 45 | session_history = SessionHistory(max_size=20) 46 | 47 | def get_session_history(session_id: str) -> BaseChatMessageHistory: 48 | return session_history.process(session_id) 49 | 50 | 51 | def get_retriever(): 52 | 53 | # 使用本地矢量数据库创建矢量数据库实例 54 | vectorstore = Chroma(persist_directory=persist_directory, 55 | embedding_function=OllamaEmbeddings(model=model_name)) 56 | 57 | # 处理基于向量数据库的查询回答任务 58 | return vectorstore.as_retriever() 59 | 60 | def get_history_aware_retriever(): 61 | # 构建检索器,将问题放在特定的上下文中进行考虑和回答。 62 | contextualize_q_system_prompt = ( 63 | "Given a chat history and the latest user question which might reference context in the chat history, " 64 | "formulate a standalone question which can be understood without the chat history. " 65 | "Do NOT answer the question, just reformulate it if needed and otherwise return it as is." 66 | ) 67 | contextualize_q_prompt = ChatPromptTemplate.from_messages( 68 | [ 69 | ("system", contextualize_q_system_prompt), 70 | MessagesPlaceholder("chat_history"), 71 | ("human", "{input}"), 72 | ] 73 | ) 74 | 75 | llm = get_llm() 76 | retriever = get_retriever() 77 | history_aware_retriever = create_history_aware_retriever( 78 | llm, retriever, contextualize_q_prompt 79 | ) 80 | return history_aware_retriever 81 | 82 | def get_conversational_rag_chain(): 83 | 84 | history_aware_retriever = get_history_aware_retriever() 85 | 86 | # 将检索器纳入问答链,回答问题 87 | system_prompt = ( 88 | "You are an assistant for question-answering tasks. " 89 | "Use the following pieces of retrieved context to answer the question. " 90 | " If you don't know the answer, say that you don't know. " 91 | "Use three sentences maximum and keep the answer concise." 92 | "\n\n" 93 | "{context}" 94 | ) 95 | qa_prompt = ChatPromptTemplate.from_messages( 96 | [ 97 | ("system", system_prompt), 98 | MessagesPlaceholder("chat_history"), 99 | ("human", "{input}"), 100 | ] 101 | ) 102 | 103 | llm = get_llm() 104 | question_answer_chain = create_stuff_documents_chain(llm, qa_prompt) 105 | rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain) 106 | return RunnableWithMessageHistory( 107 | rag_chain, 108 | get_session_history, 109 | input_messages_key="input", 110 | history_messages_key="chat_history", 111 | output_messages_key="answer", 112 | ) 113 | 114 | conversational_rag_chain = get_conversational_rag_chain() 115 | 116 | # 带有历史记录的聊天方法 117 | # 显然,chat_history可以让模型更能“理解”上下文,做出更加妥帖的回答。 118 | def consult(query,session_id): 119 | 120 | # 调用链,返回结果 121 | response = conversational_rag_chain.invoke( 122 | {"input": query}, 123 | config={"configurable": {"session_id": session_id}}, 124 | ) 125 | return response["answer"] 126 | 127 | 128 | if __name__ == '__main__': 129 | 130 | session_id = "liu123" 131 | 132 | # 测试chat方法 133 | print (consult("你知道中华人民共和国产品质量法么?", session_id)) 134 | print (consult("请用一段文字概括一下它的内容。", session_id)) 135 | print (consult("在生产、销售的产品中掺杂、掺假 违反了哪个法律?哪个条款?", session_id)) 136 | print (consult("下面的问题与中华人民共和国产品质量法无关。宣扬邪教、迷信 违反了哪个法律?哪个条款?", session_id)) 137 | 138 | session_history.print_history(session_id) 139 | -------------------------------------------------------------------------------- /server/services/multimodal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-08 5 | # @function: 多模态识别图片 6 | # @version : V0.5 7 | # @Description :llama3.1不行。 8 | 9 | # https://python.langchain.com/docs/how_to/multimodal_inputs/ 10 | 11 | 12 | import os 13 | 14 | # 获取当前文件的绝对路径 15 | current_file_path = os.path.abspath(__file__) 16 | 17 | # 获取当前文件所在的目录 18 | current_dir = os.path.dirname(current_file_path) 19 | 20 | image_file_path = os.path.join(current_dir,'assert/nature-boardwalk.jpg') 21 | 22 | import base64 23 | 24 | def open_img(image_file_path): 25 | with open(image_file_path, "rb") as image_file: 26 | # 读取图片的二进制数据 27 | image_data = image_file.read() 28 | # 编码为 Base64 29 | base64_encoded = base64.b64encode(image_data).decode('utf-8') 30 | return base64_encoded 31 | 32 | image_data = open_img(image_file_path) 33 | 34 | from langchain_ollama import ChatOllama 35 | llm = ChatOllama(model="llama3.1",temperature=0.1,verbose=True) 36 | """ 37 | temperature:用于控制生成语言模型中生成文本的随机性和创造性。 38 | 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性; 39 | 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 40 | 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 41 | """ 42 | 43 | from langchain_core.messages import HumanMessage 44 | 45 | message = HumanMessage( 46 | content=[ 47 | {"type": "text", "text": "describe the weather in this image"}, 48 | { 49 | "type": "image_url", 50 | "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}, 51 | }, 52 | ], 53 | ) 54 | response = llm.invoke([message]) 55 | print(response.content) 56 | -------------------------------------------------------------------------------- /server/services/practice/00.streaming_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-02-27 5 | # @function: 流式输出 6 | # @version : V0.5 7 | # @Description :测试流式输出。未成功! 8 | 9 | llm_model_name = "qwen2.5" 10 | 11 | """ 12 | 1. 工具 13 | """ 14 | 15 | import random 16 | from langchain_core.tools import tool 17 | 18 | @tool 19 | def where_cat_is_hiding() -> str: 20 | """Where is the cat hiding right now?""" 21 | return random.choice(["under the bed", "on the shelf"]) 22 | 23 | 24 | @tool 25 | def get_items(place: str) -> str: 26 | """Use this tool to look up which items are in the given place.""" 27 | if "bed" in place: # For under the bed 28 | return "socks, shoes and dust bunnies" 29 | if "shelf" in place: # For 'shelf' 30 | return "books, penciles and pictures" 31 | else: # if the agent decides to ask about a different place 32 | return "cat snacks" 33 | 34 | """ 35 | 2. 初始化智能体 36 | """ 37 | 38 | from langchain_core.prompts import ChatPromptTemplate 39 | 40 | prompt = ChatPromptTemplate.from_messages([ 41 | ("system", "You are a helpful assistant"), 42 | ("placeholder", "{chat_history}"), 43 | ("human", "{input}"), 44 | ("placeholder", "{agent_scratchpad}"), 45 | ]) 46 | 47 | tools = [get_items, where_cat_is_hiding] 48 | 49 | from langchain.agents import create_tool_calling_agent 50 | from langchain_ollama import ChatOllama 51 | 52 | from langchain.callbacks.streaming_stdout_final_only import FinalStreamingStdOutCallbackHandler 53 | 54 | class CustomStreamingHandler(FinalStreamingStdOutCallbackHandler): 55 | def __init__(self): 56 | super().__init__() 57 | self.buffer = [] 58 | 59 | def on_llm_new_token(self, token: str, **kwargs): 60 | """每当 LLM 生成新 token 时调用""" 61 | self.buffer.append(token) 62 | print(token, end="^", flush=True) # 直接流式输出最终结果 63 | 64 | import sys 65 | from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler 66 | 67 | class CallbackHandler(StreamingStdOutCallbackHandler): 68 | """自定义输出""" 69 | def __init__(self): 70 | self.content: str = "" 71 | self.final_answer: bool = False 72 | 73 | def on_llm_new_token(self, token: str, **kwargs: any) -> None: 74 | """智能体会逐渐返回json格式的结果,这里只输出 action_input 的内容""" 75 | self.content += token 76 | if "Final Answer" in self.content: 77 | # 现在我们到了 Final Answer 部分,但不要打印。 78 | self.final_answer = True 79 | self.content = "" 80 | if self.final_answer: 81 | if '"action_input": "' in self.content: 82 | 83 | # 当字符串中包含 '}' 时,移除 '}' 和后面的字符。 84 | index = token.find('}') # 找到 '}' 的索引 85 | if index != -1: 86 | self.final_answer = False 87 | token = token[:index] 88 | 89 | sys.stdout.write(token) 90 | if index == -1: 91 | sys.stdout.write('^') 92 | sys.stdout.flush() 93 | def on_llm_end(self, response, **kwargs): 94 | return super().on_llm_end(response, **kwargs) 95 | 96 | from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler 97 | model = ChatOllama(model=llm_model_name,temperature=0.3,verbose=True,callbacks=[StreamingStdOutCallbackHandler()]) 98 | 99 | agent = create_tool_calling_agent(model, tools, prompt) 100 | 101 | from langchain.agents import AgentExecutor 102 | agent_executor = AgentExecutor(agent=agent, tools=tools).with_config( 103 | {"run_name": "Agent"} 104 | ) 105 | 106 | def ask(question): 107 | """咨询智能体""" 108 | #chunks = [] 109 | for chunk in agent_executor.stream({"input": question}): 110 | #chunks.append(chunk) 111 | print_normal(chunk) 112 | 113 | def print_normal(chunk): 114 | print(chunk) 115 | print("----") 116 | 117 | def print_simple(chunk): 118 | # Note: We use `pprint` to print only to depth 1, it makes it easier to see the output from a high level, before digging in. 119 | import pprint 120 | print("----") 121 | pprint.pprint(chunk, depth=1) 122 | 123 | def print_useful(chunk): 124 | # Agent Action 125 | if "actions" in chunk: 126 | for action in chunk["actions"]: 127 | print(f"Calling Tool: `{action.tool}` with input `{action.tool_input}`") 128 | # Observation 129 | elif "steps" in chunk: 130 | for step in chunk["steps"]: 131 | print(f"Tool Result: `{step.observation}`") 132 | # Final result 133 | elif "output" in chunk: 134 | print(f'Final Output: {chunk["output"]}') 135 | else: 136 | raise ValueError() 137 | print("---") 138 | 139 | from langchain.callbacks.base import BaseCallbackHandler 140 | 141 | class SafeLogger(BaseCallbackHandler): 142 | def on_llm_end(self, response, **kwargs): 143 | print("LLM finished. Skipping object serialization.") 144 | # 不尝试序列化 LLM 返回的消息,避免抛出 NotImplementedError 145 | 146 | agent_executor_safe = AgentExecutor( 147 | agent=agent, 148 | tools=tools, 149 | callbacks=[SafeLogger()], 150 | verbose=True, 151 | ).with_config({"run_name": "Agent"}) 152 | 153 | 154 | async def ask_2(question): 155 | async for event in agent_executor_safe.astream_events( 156 | {"input": question}, 157 | version="v1", 158 | ): 159 | kind = event["event"] 160 | if kind == "on_chain_start": 161 | if ( 162 | event["name"] == "Agent" 163 | ): # Was assigned when creating the agent with `.with_config({"run_name": "Agent"})` 164 | print( 165 | f"Starting agent: {event['name']} with input: {event['data'].get('input')}" 166 | ) 167 | elif kind == "on_chain_end": 168 | if ( 169 | event["name"] == "Agent" 170 | ): # Was assigned when creating the agent with `.with_config({"run_name": "Agent"})` 171 | print() 172 | print("--") 173 | print( 174 | f"Done agent: {event['name']} with output: {event['data'].get('output')['output']}" 175 | ) 176 | if kind == "on_chat_model_stream": 177 | content = event["data"]["chunk"].content 178 | if content: 179 | # Empty content in the context of OpenAI means 180 | # that the model is asking for a tool to be invoked. 181 | # So we only print non-empty content 182 | print(content, end="|") 183 | elif kind == "on_tool_start": 184 | print("--") 185 | print( 186 | f"Starting tool: {event['name']} with inputs: {event['data'].get('input')}" 187 | ) 188 | elif kind == "on_tool_end": 189 | print(f"Done tool: {event['name']}") 190 | print(f"Tool output was: {event['data'].get('output')}") 191 | print("--") 192 | 193 | 194 | agent_executor_stream = AgentExecutor(agent=agent, tools=tools,verbose=False,callbacks=[CallbackHandler()]).with_config( 195 | {"run_name": "Agent_stream"} 196 | ) 197 | 198 | #agent_executor_stream.agent.llm.callbacks =[CallbackHandler()] 199 | 200 | def ask_3(question): 201 | """咨询智能体""" 202 | agent_executor_stream.invoke({"input": question}) 203 | ''' 204 | for chunk in agent_executor_stream.stream({"input": question}): 205 | pass 206 | #print(chunk) 207 | ''' 208 | 209 | 210 | if __name__ == '__main__': 211 | 212 | #place = where_cat_is_hiding.invoke({}) 213 | #items = get_items.invoke({"place": "shelf"}) 214 | 215 | #ask("what's items are located where the cat is hiding?") 216 | 217 | ''' 218 | import asyncio 219 | asyncio.run(ask_2("where is the cat hiding? what items are in that location?")) 220 | ''' 221 | 222 | ask_3("what's items are located where the cat is hiding?") 223 | #ask_3("请参考哪吒闹海的故事架构,写一篇200-300字的神话故事。") -------------------------------------------------------------------------------- /server/services/practice/00_assistant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-07 5 | # @function: AI助理 6 | # @version : V0.5 7 | # @Description :修改提示词,大模型秒变助理。 8 | 9 | from langchain_ollama import ChatOllama 10 | 11 | # 返回本地大模型 12 | def get_llm(): 13 | 14 | # temperature:用于控制生成语言模型中生成文本的随机性和创造性。 15 | # 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性。 16 | # 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 17 | # 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 18 | return ChatOllama(model="llama3.1",temperature=0.3,verbose=True) 19 | 20 | 21 | from langchain_core.runnables.history import RunnableWithMessageHistory 22 | from langchain_core.chat_history import BaseChatMessageHistory 23 | 24 | # 处理聊天历史 25 | from common.LimitedChatMessageHistory import SessionHistory 26 | session_history = SessionHistory(max_size=20) 27 | 28 | def get_session_history(session_id: str) -> BaseChatMessageHistory: 29 | return session_history.process(session_id) 30 | 31 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 32 | 33 | def get_history_chain(): 34 | prompt = ChatPromptTemplate.from_messages( 35 | [ 36 | ( 37 | "system", 38 | "You are a helpful assistant. Answer all questions to the best of your ability.", 39 | ), 40 | MessagesPlaceholder(variable_name="messages"), 41 | ] 42 | ) 43 | 44 | chain = prompt | get_llm() 45 | return RunnableWithMessageHistory(chain, get_session_history) 46 | 47 | with_message_history = get_history_chain() 48 | 49 | from langchain_core.messages import HumanMessage 50 | 51 | def chat(human_message,session_id): 52 | """ 53 | 助理 54 | """ 55 | 56 | response = with_message_history.invoke( 57 | [HumanMessage(content=human_message)], 58 | config={"configurable": {"session_id": session_id}}, 59 | ) 60 | 61 | return response.content 62 | 63 | if __name__ == '__main__': 64 | 65 | session_id = "liu123" 66 | 67 | # 测试chat方法 68 | print (chat("你知道x-space的老板马斯克么?", session_id)) 69 | print (chat("他出生在哪个国家?", session_id)) 70 | print (chat("他和特朗普是什么关系?", session_id)) 71 | 72 | session_history.print_history(session_id) -------------------------------------------------------------------------------- /server/services/practice/00_assistant_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-07 5 | # @function: AI助理2 6 | # @version : V0.5 7 | # @Description :修改提示词,大模型秒变助理。 8 | 9 | from langchain_ollama import ChatOllama 10 | llm = ChatOllama(model="llama3.1",temperature=0.3,verbose=True) 11 | """ 12 | temperature:用于控制生成语言模型中生成文本的随机性和创造性。 13 | 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性。 14 | 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 15 | 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 16 | """ 17 | # 18 | 19 | from common.LimitedChatMessageHistory import SessionHistory 20 | from langchain_core.runnables.history import RunnableWithMessageHistory 21 | from langchain_core.chat_history import BaseChatMessageHistory 22 | 23 | session_history = SessionHistory(max_size=20) 24 | 25 | # 处理聊天历史 26 | def get_session_history(session_id: str) -> BaseChatMessageHistory: 27 | return session_history.process(session_id) 28 | 29 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 30 | 31 | def get_history_chain(): 32 | prompt = ChatPromptTemplate.from_messages( 33 | [ 34 | ( 35 | "system", 36 | "You are a helpful assistant. Answer all questions to the best of your ability in {language}.", 37 | ), 38 | MessagesPlaceholder(variable_name="messages"), 39 | ] 40 | ) 41 | 42 | chain = prompt | llm 43 | return RunnableWithMessageHistory(chain, get_session_history,input_messages_key="messages") 44 | 45 | with_message_history = get_history_chain() 46 | 47 | from langchain_core.messages import HumanMessage 48 | 49 | def chat(human_message,session_id,language="简体中文"): 50 | """ 51 | 助理 52 | """ 53 | 54 | response = with_message_history.invoke( 55 | {"messages":[HumanMessage(content=human_message)],"language":language}, 56 | config={"configurable": {"session_id": session_id}}, 57 | ) 58 | 59 | return response.content 60 | 61 | 62 | def stream(human_message,session_id,language="简体中文"): 63 | ''' 64 | 流式输出 65 | *实际测试时,感觉大模型还是一次性回复了结果,并没有流式输出! 66 | ''' 67 | for r in with_message_history.invoke( 68 | {"messages":[HumanMessage(content=human_message)],"language":language}, 69 | config={"configurable": {"session_id": session_id}}, 70 | ): 71 | #print(r[1],end="|") 72 | if r is not None and r[0] == "content": 73 | yield r[1] 74 | else: 75 | yield None 76 | 77 | if __name__ == '__main__': 78 | 79 | session_id = "liu123" 80 | 81 | language = "简体中文" 82 | 83 | # 测试chat方法 84 | #print (chat("Do you know Musk, the boss of x-space?",language, session_id)) 85 | 86 | ''' 87 | print (chat("你知道x-space的老板马斯克么?",language, session_id)) 88 | print (chat("他出生在哪个国家?",language, session_id)) 89 | print (chat("他和特朗普是什么关系?",language, session_id)) 90 | 91 | session_history.print_history(session_id) 92 | ''' 93 | 94 | # 测试stream方法 95 | for r in stream("你知道x-space的老板马斯克么?",session_id): 96 | if r is not None: 97 | print (r, end="|") 98 | 99 | for r in stream("请详细介绍一下他的主要事迹。",session_id): 100 | if r is not None: 101 | print (r, end="|") 102 | -------------------------------------------------------------------------------- /server/services/practice/00_chat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-06 5 | # @function: 测试llama3.1 8b模型 6 | # @version : V0.5 7 | 8 | from langchain_core.prompts import ChatPromptTemplate 9 | from langchain_ollama.llms import OllamaLLM 10 | 11 | # llama3.1 EntropyYue/chatglm3 12 | model_name = "EntropyYue/chatglm3" 13 | 14 | def ask(question): 15 | 16 | template = """Question: {question} 17 | 18 | Answer: Let's think step by step. 19 | 20 | 请用简体中文回复。 21 | """ 22 | 23 | prompt = ChatPromptTemplate.from_template(template) 24 | model = OllamaLLM(model=model_name) 25 | chain = prompt | model 26 | result = chain.invoke({"question": question}) 27 | return result 28 | 29 | if __name__ == '__main__': 30 | print(ask("你是谁?你有什么本事?")) -------------------------------------------------------------------------------- /server/services/practice/00_chatbot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-06 5 | # @function: 与本地大模型聊天,自动记录聊天历史 6 | # @version : V0.5 7 | # @Description :在问答的过程中,系统自动存储以往的问题和答案,产生“记忆”功能,提升会话体验。 8 | 9 | from langchain_ollama import ChatOllama 10 | 11 | # llama3.1 EntropyYue/chatglm3 12 | model_name = "llama3.1" 13 | 14 | # 返回本地大模型 15 | def get_llm(): 16 | 17 | # temperature:用于控制生成语言模型中生成文本的随机性和创造性。 18 | # 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性。 19 | # 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 20 | # 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 21 | return ChatOllama(model=model_name,temperature=0.3,verbose=True) 22 | 23 | from common.LimitedChatMessageHistory import SessionHistory 24 | from langchain_core.runnables.history import RunnableWithMessageHistory 25 | from langchain_core.chat_history import BaseChatMessageHistory 26 | 27 | session_history = SessionHistory(max_size=60) 28 | 29 | # 处理聊天历史 30 | def get_session_history(session_id: str) -> BaseChatMessageHistory: 31 | return session_history.process(session_id) 32 | 33 | from langchain_core.messages import HumanMessage 34 | 35 | from langchain_core.output_parsers import StrOutputParser 36 | def get_history_chain(): 37 | chain = get_llm() | StrOutputParser() 38 | return RunnableWithMessageHistory(chain, get_session_history) 39 | 40 | with_message_history = get_history_chain() 41 | 42 | def chat(human_message,session_id): 43 | """ 44 | 聊天 45 | """ 46 | 47 | response = with_message_history.invoke( 48 | [HumanMessage(content=human_message)], 49 | config={"configurable": {"session_id": session_id}}, 50 | ) 51 | 52 | return response 53 | 54 | 55 | if __name__ == '__main__': 56 | 57 | session_id = "liu123" 58 | 59 | print (chat("计算 5 加 3 乘以 2 的结果是多少?", session_id)) 60 | ''' 61 | print (chat("你知道x-space的老板马斯克么?", session_id)) 62 | print (chat("他出生在哪个国家?", session_id)) 63 | print (chat("他和特朗普是什么关系?", session_id)) 64 | ''' 65 | #session_history.print_history(session_id) 66 | -------------------------------------------------------------------------------- /server/services/practice/00_tool_InjectedToolArg_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-08 5 | # @function: 防止LLM生成某些参数 6 | # @version : V0.5 7 | # @Description :防止LLM生成某些参数。 8 | 9 | # https://python.langchain.com/docs/how_to/tool_runtime/ 10 | 11 | """ 12 | 您可能需要将仅在运行时才知道的值绑定到工具。例如,工具逻辑可能需要使用发出请求的用户的 ID。 13 | 大多数情况下,此类值不应由 LLM 控制。事实上,允许 LLM 控制用户 ID 可能会导致安全风险。 14 | 相反,LLM 应该只控制本应由 LLM 控制的工具参数,而其他参数(如用户 ID)应由应用程序逻辑固定。 15 | 本操作指南向您展示了如何防止模型生成某些工具参数并在运行时直接注入它们。 16 | """ 17 | 18 | from langchain_ollama import ChatOllama 19 | llm = ChatOllama(model="llama3.1",temperature=0.1,verbose=True) 20 | """ 21 | temperature:用于控制生成语言模型中生成文本的随机性和创造性。 22 | 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性; 23 | 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 24 | 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 25 | """ 26 | 27 | # Hiding arguments from the model 28 | 29 | from typing import List 30 | 31 | from langchain_core.tools import InjectedToolArg, tool 32 | from typing_extensions import Annotated 33 | 34 | user_to_pets = {} 35 | 36 | from pydantic import BaseModel, Field 37 | 38 | class UpdateFavoritePetsSchema(BaseModel): 39 | """添加或者更新最喜爱的宠物列表。""" 40 | 41 | pets: List[str] = Field(..., description="最喜爱的宠物列表。") 42 | user_id: Annotated[str, InjectedToolArg] = Field(..., description="用户ID。") 43 | 44 | 45 | @tool(args_schema=UpdateFavoritePetsSchema) 46 | def update_favorite_pets(pets, user_id): 47 | user_to_pets[user_id] = pets 48 | 49 | 50 | @tool(parse_docstring=True) 51 | def delete_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 52 | """Delete the list of favorite pets. 53 | 54 | Args: 55 | user_id: User's ID. 56 | """ 57 | print(f'delete_favorite_pets is called:{user_id}') 58 | if user_id in user_to_pets: 59 | del user_to_pets[user_id] 60 | 61 | 62 | @tool(parse_docstring=True) 63 | def list_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 64 | """List favorite pets if any. 65 | 66 | Args: 67 | user_id: User's ID. 68 | """ 69 | print(f'list_favorite_pets is called:{user_id}') 70 | return user_to_pets.get(user_id, []) 71 | 72 | # If we look at the input schemas for these tools, we'll see that user_id is still listed: 73 | print(f'get_input_schema:{update_favorite_pets.get_input_schema().model_json_schema()}') 74 | 75 | # But if we look at the tool call schema, which is what is passed to the model for tool-calling, user_id has been removed: 76 | print(f'tool_call_schema:{update_favorite_pets.tool_call_schema.model_json_schema()}') 77 | 78 | # So when we invoke our tool, we need to pass in user_id: 79 | user_id = "123" 80 | update_favorite_pets.invoke({"pets": ["lizard", "dog"], "user_id": user_id}) 81 | print(f'user_to_pets:{user_to_pets}') 82 | print(f'list_favorite_pets.invoke:{list_favorite_pets.invoke({"user_id": user_id})}') 83 | 84 | # But when the model calls the tool, no user_id argument will be generated: 85 | tools = [ 86 | update_favorite_pets, 87 | delete_favorite_pets, 88 | list_favorite_pets, 89 | ] 90 | llm_with_tools = llm.bind_tools(tools) 91 | 92 | query = "my favorite animals are cats and parrots" 93 | 94 | # 将文本转化为json结构 95 | print("---1、调用LLM,将请求转化为json结构---") 96 | ai_msg = llm_with_tools.invoke(query) 97 | print(f'result:{ai_msg.tool_calls}') 98 | 99 | # Injecting arguments at runtime 100 | from copy import deepcopy 101 | 102 | from langchain_core.runnables import chain 103 | 104 | @chain 105 | def inject_user_id(ai_msg): 106 | tool_calls = [] 107 | for tool_call in ai_msg.tool_calls: 108 | tool_call_copy = deepcopy(tool_call) 109 | tool_call_copy["args"]["user_id"] = user_id 110 | tool_calls.append(tool_call_copy) 111 | return tool_calls 112 | 113 | new_args = inject_user_id.invoke(ai_msg) 114 | print(f'inject_user_id:{new_args}') 115 | 116 | # And now we can chain together our model, injection code, and the actual tools to create a tool-executing chain: 117 | tool_map = {tool.name: tool for tool in tools} 118 | 119 | @chain 120 | def tool_router(tool_call): 121 | return tool_map[tool_call["name"]] 122 | 123 | # And now we can chain together our model, injection code, and the actual tools to create a tool-executing chain: 124 | chain = llm_with_tools | inject_user_id | tool_router.map() 125 | 126 | # 调用chain 127 | print("--通过chain实现:2、注入新参数user_id;3、直接调用tool生成结果;4、调用LLM,生成流畅的答案---") 128 | result = chain.invoke(query) 129 | print(f'chain.invoke:{result}') 130 | print(f'now user_to_pets :{user_to_pets}') -------------------------------------------------------------------------------- /server/services/practice/00_tool_InjectedToolArg_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-08 5 | # @function: 防止LLM生成某些参数 6 | # @version : V0.5 7 | # @Description :防止LLM生成某些参数。 8 | 9 | # https://python.langchain.com/docs/how_to/tool_runtime/ 10 | 11 | """ 12 | 您可能需要将仅在运行时才知道的值绑定到工具。例如,工具逻辑可能需要使用发出请求的用户的 ID。 13 | 大多数情况下,此类值不应由 LLM 控制。事实上,允许 LLM 控制用户 ID 可能会导致安全风险。 14 | 相反,LLM 应该只控制本应由 LLM 控制的工具参数,而其他参数(如用户 ID)应由应用程序逻辑固定。 15 | 本操作指南向您展示了如何防止模型生成某些工具参数并在运行时直接注入它们。 16 | """ 17 | 18 | from langchain_ollama import ChatOllama 19 | llm = ChatOllama(model="llama3.1",temperature=0.1,verbose=True) 20 | """ 21 | temperature:用于控制生成语言模型中生成文本的随机性和创造性。 22 | 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性; 23 | 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 24 | 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 25 | """ 26 | 27 | # Hiding arguments from the model 28 | 29 | from typing import List 30 | 31 | from langchain_core.tools import InjectedToolArg, tool 32 | from typing_extensions import Annotated 33 | 34 | user_to_pets = {} 35 | 36 | from typing import Optional, Type 37 | from langchain_core.tools import BaseTool 38 | from pydantic import BaseModel, Field 39 | 40 | class UpdateFavoritePetsSchema(BaseModel): 41 | """添加或者更新最喜爱的宠物列表。""" 42 | 43 | pets: List[str] = Field(..., description="最喜爱的宠物列表。") 44 | user_id: Annotated[str, InjectedToolArg] = Field(..., description="用户ID。") 45 | 46 | class UpdateFavoritePets(BaseTool): 47 | name: str = "update_favorite_pets" 48 | description: str = "添加或者更新最喜爱的宠物列表" 49 | args_schema: Optional[Type[BaseModel]] = UpdateFavoritePetsSchema 50 | 51 | def _run(self, pets, user_id): 52 | user_to_pets[user_id] = pets 53 | 54 | update_favorite_pets = UpdateFavoritePets() 55 | 56 | 57 | @tool(parse_docstring=True) 58 | def delete_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 59 | """Delete the list of favorite pets. 60 | 61 | Args: 62 | user_id: User's ID. 63 | """ 64 | print(f'delete_favorite_pets is called:{user_id}') 65 | if user_id in user_to_pets: 66 | del user_to_pets[user_id] 67 | 68 | 69 | @tool(parse_docstring=True) 70 | def list_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 71 | """List favorite pets if any. 72 | 73 | Args: 74 | user_id: User's ID. 75 | """ 76 | print(f'list_favorite_pets is called:{user_id}') 77 | return user_to_pets.get(user_id, []) 78 | 79 | # If we look at the input schemas for these tools, we'll see that user_id is still listed: 80 | print(f'get_input_schema:{update_favorite_pets.get_input_schema().model_json_schema()}') 81 | 82 | # But if we look at the tool call schema, which is what is passed to the model for tool-calling, user_id has been removed: 83 | print(f'tool_call_schema:{update_favorite_pets.tool_call_schema.model_json_schema()}') 84 | 85 | # So when we invoke our tool, we need to pass in user_id: 86 | user_id = "123" 87 | update_favorite_pets.invoke({"pets": ["lizard", "dog"], "user_id": user_id}) 88 | print(f'user_to_pets:{user_to_pets}') 89 | print(f'list_favorite_pets.invoke:{list_favorite_pets.invoke({"user_id": user_id})}') 90 | 91 | # But when the model calls the tool, no user_id argument will be generated: 92 | tools = [ 93 | update_favorite_pets, 94 | delete_favorite_pets, 95 | list_favorite_pets, 96 | ] 97 | llm_with_tools = llm.bind_tools(tools) 98 | 99 | query = "my favorite animals are cats and parrots" 100 | 101 | # 将文本转化为json结构 102 | print("---1、调用LLM,将请求转化为json结构---") 103 | ai_msg = llm_with_tools.invoke(query) 104 | print(f'result:{ai_msg.tool_calls}') 105 | 106 | # Injecting arguments at runtime 107 | from copy import deepcopy 108 | 109 | from langchain_core.runnables import chain 110 | 111 | @chain 112 | def inject_user_id(ai_msg): 113 | tool_calls = [] 114 | for tool_call in ai_msg.tool_calls: 115 | tool_call_copy = deepcopy(tool_call) 116 | tool_call_copy["args"]["user_id"] = user_id 117 | tool_calls.append(tool_call_copy) 118 | return tool_calls 119 | 120 | new_args = inject_user_id.invoke(ai_msg) 121 | print(f'inject_user_id:{new_args}') 122 | 123 | # And now we can chain together our model, injection code, and the actual tools to create a tool-executing chain: 124 | tool_map = {tool.name: tool for tool in tools} 125 | 126 | @chain 127 | def tool_router(tool_call): 128 | return tool_map[tool_call["name"]] 129 | 130 | # And now we can chain together our model, injection code, and the actual tools to create a tool-executing chain: 131 | chain = llm_with_tools | inject_user_id | tool_router.map() 132 | 133 | # 调用chain 134 | print("--通过chain实现:2、注入新参数user_id;3、直接调用tool生成结果;4、调用LLM,生成流畅的答案---") 135 | result = chain.invoke(query) 136 | print(f'chain.invoke:{result}') 137 | print(f'now user_to_pets :{user_to_pets}') -------------------------------------------------------------------------------- /server/services/practice/00_tool_InjectedToolArg_4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-08 5 | # @function: 防止LLM生成某些参数 6 | # @version : V0.5 7 | # @Description :防止LLM生成某些参数。 8 | 9 | # https://python.langchain.com/docs/how_to/tool_runtime/ 10 | 11 | """ 12 | 您可能需要将仅在运行时才知道的值绑定到工具。例如,工具逻辑可能需要使用发出请求的用户的 ID。 13 | 大多数情况下,此类值不应由 LLM 控制。事实上,允许 LLM 控制用户 ID 可能会导致安全风险。 14 | 相反,LLM 应该只控制本应由 LLM 控制的工具参数,而其他参数(如用户 ID)应由应用程序逻辑固定。 15 | 本操作指南向您展示了如何防止模型生成某些工具参数并在运行时直接注入它们。 16 | """ 17 | 18 | from langchain_ollama import ChatOllama 19 | llm = ChatOllama(model="llama3.1",temperature=0.1,verbose=True) 20 | """ 21 | temperature:用于控制生成语言模型中生成文本的随机性和创造性。 22 | 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性; 23 | 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 24 | 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 25 | """ 26 | 27 | # Hiding arguments from the model 28 | 29 | from typing import List 30 | 31 | from langchain_core.tools import InjectedToolArg, tool 32 | from typing_extensions import Annotated 33 | 34 | user_to_pets = {} 35 | 36 | from langchain_core.tools import BaseTool 37 | 38 | class UpdateFavoritePets(BaseTool): 39 | name: str = "update_favorite_pets" 40 | description: str = "添加或者更新最喜爱的宠物列表" 41 | 42 | def _run(self, pets: List[str], user_id: Annotated[str, InjectedToolArg]) -> None: 43 | user_to_pets[user_id] = pets 44 | 45 | update_favorite_pets = UpdateFavoritePets() 46 | 47 | 48 | @tool(parse_docstring=True) 49 | def delete_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 50 | """Delete the list of favorite pets. 51 | 52 | Args: 53 | user_id: User's ID. 54 | """ 55 | print(f'delete_favorite_pets is called:{user_id}') 56 | if user_id in user_to_pets: 57 | del user_to_pets[user_id] 58 | 59 | 60 | @tool(parse_docstring=True) 61 | def list_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 62 | """List favorite pets if any. 63 | 64 | Args: 65 | user_id: User's ID. 66 | """ 67 | print(f'list_favorite_pets is called:{user_id}') 68 | return user_to_pets.get(user_id, []) 69 | 70 | # If we look at the input schemas for these tools, we'll see that user_id is still listed: 71 | print(f'get_input_schema:{update_favorite_pets.get_input_schema().model_json_schema()}') 72 | 73 | # But if we look at the tool call schema, which is what is passed to the model for tool-calling, user_id has been removed: 74 | print(f'tool_call_schema:{update_favorite_pets.tool_call_schema.model_json_schema()}') 75 | 76 | # So when we invoke our tool, we need to pass in user_id: 77 | user_id = "123" 78 | update_favorite_pets.invoke({"pets": ["lizard", "dog"], "user_id": user_id}) 79 | print(f'user_to_pets:{user_to_pets}') 80 | print(f'list_favorite_pets.invoke:{list_favorite_pets.invoke({"user_id": user_id})}') 81 | 82 | # But when the model calls the tool, no user_id argument will be generated: 83 | tools = [ 84 | update_favorite_pets, 85 | delete_favorite_pets, 86 | list_favorite_pets, 87 | ] 88 | llm_with_tools = llm.bind_tools(tools) 89 | 90 | query = "my favorite animals are cats and parrots" 91 | 92 | # 将文本转化为json结构 93 | print("---1、调用LLM,将请求转化为json结构---") 94 | ai_msg = llm_with_tools.invoke(query) 95 | print(f'result:{ai_msg.tool_calls}') 96 | 97 | # Injecting arguments at runtime 98 | from copy import deepcopy 99 | 100 | from langchain_core.runnables import chain 101 | 102 | @chain 103 | def inject_user_id(ai_msg): 104 | tool_calls = [] 105 | for tool_call in ai_msg.tool_calls: 106 | tool_call_copy = deepcopy(tool_call) 107 | tool_call_copy["args"]["user_id"] = user_id 108 | tool_calls.append(tool_call_copy) 109 | return tool_calls 110 | 111 | new_args = inject_user_id.invoke(ai_msg) 112 | print(f'inject_user_id:{new_args}') 113 | 114 | # And now we can chain together our model, injection code, and the actual tools to create a tool-executing chain: 115 | tool_map = {tool.name: tool for tool in tools} 116 | 117 | @chain 118 | def tool_router(tool_call): 119 | return tool_map[tool_call["name"]] 120 | 121 | # And now we can chain together our model, injection code, and the actual tools to create a tool-executing chain: 122 | chain = llm_with_tools | inject_user_id | tool_router.map() 123 | 124 | # 调用chain 125 | print("--通过chain实现:2、注入新参数user_id;3、直接调用tool生成结果;4、调用LLM,生成流畅的答案---") 126 | result = chain.invoke(query) 127 | print(f'chain.invoke:{result}') 128 | print(f'now user_to_pets :{user_to_pets}') -------------------------------------------------------------------------------- /server/services/practice/01_translation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-24 5 | # @function: 使用大模型实现翻译功能 6 | # @version : V0.5 7 | 8 | # 实例化本地大模型 9 | from langchain_ollama.llms import OllamaLLM 10 | model = OllamaLLM(model="llama3.1") 11 | 12 | from langchain_core.messages import HumanMessage, SystemMessage 13 | 14 | def translate_1(text): 15 | """将文字翻译为意大利语""" 16 | messages = [ 17 | SystemMessage("Translate the following from English into Italian"), 18 | HumanMessage(text), 19 | ] 20 | 21 | return model.invoke(messages) 22 | 23 | def translate_1_stream(text): 24 | """将文字翻译为意大利语,流式输出""" 25 | messages = [ 26 | SystemMessage("Translate the following from English into Italian"), 27 | HumanMessage(text), 28 | ] 29 | for token in model.stream(messages): 30 | yield token 31 | 32 | from langchain_core.prompts import ChatPromptTemplate 33 | 34 | def translate_2(text,language): 35 | """用提示词模板构建提示词,翻译文字""" 36 | 37 | # 1. system提示词 38 | system_template = "Translate the following from English into {language}" 39 | 40 | # 2. 提示词模板 41 | prompt_template = ChatPromptTemplate.from_messages( 42 | [("system", system_template), ("user", "{text}")] 43 | ) 44 | 45 | # 3. 调用invoke构建提示词 46 | prompt = prompt_template.invoke({"language": language, "text": text}) 47 | print(prompt.to_messages()) 48 | 49 | response = model.invoke(prompt) 50 | return response 51 | 52 | if __name__ == '__main__': 53 | 54 | response = translate_1("Hello, how are you?") 55 | print(response) 56 | 57 | for token in translate_1_stream("Hello, how are you?"): 58 | print(token, end="|") 59 | 60 | 61 | response = translate_2("First up, let's learn how to use a language model by itself.","Chinese") 62 | print(response) 63 | -------------------------------------------------------------------------------- /server/services/practice/02_vector_store_pdf.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | #!/usr/bin/python 4 | # -*- coding:utf-8 -*- 5 | # @author : 刘立军 6 | # @time : 2025-01-14 7 | # @function: 利用本地大模型测试矢量数据库 8 | # @Description: 使用 nomic-embed-text 做英文嵌入检索很好,使用 llama3.1 效果一般 9 | # @version : V0.5 10 | 11 | # https://python.langchain.com/docs/tutorials/retrievers/ 12 | 13 | import os 14 | 15 | def get_file_path(): 16 | """获取文件路径。 17 | 用这种方式获取资源文件路径比较安全。 18 | """ 19 | 20 | # 获取当前文件的绝对路径 21 | current_file_path = os.path.abspath(__file__) 22 | 23 | # 获取当前文件所在的目录 24 | current_dir = os.path.dirname(current_file_path) 25 | 26 | file_path = os.path.join(current_dir,'assert/nke-10k-2023.pdf') 27 | 28 | return file_path 29 | 30 | def load_file(file_path): 31 | """加载pdf文件""" 32 | 33 | # Loading documents 34 | from langchain_community.document_loaders import PyPDFLoader 35 | 36 | loader = PyPDFLoader(file_path) 37 | 38 | docs = loader.load() 39 | 40 | print(f'加载文件成功,总文本数:{len(docs)}') 41 | 42 | # PyPDFLoader loads one Document object per PDF page. The first page is at index 0. 43 | print(f"page one:\n{docs[0].page_content[:200]}\n") 44 | print(f'page one metadata:\n{docs[0].metadata}') 45 | 46 | return docs 47 | 48 | 49 | def split_text(docs): 50 | """分割文档""" 51 | 52 | from langchain_text_splitters import RecursiveCharacterTextSplitter 53 | 54 | text_splitter = RecursiveCharacterTextSplitter( 55 | chunk_size=1000, chunk_overlap=200, add_start_index=True 56 | ) 57 | all_splits = text_splitter.split_documents(docs) 58 | 59 | print(f"Number of splits: {len(all_splits)}") 60 | 61 | return all_splits 62 | 63 | # Embeddings 64 | 65 | from langchain_ollama.embeddings import OllamaEmbeddings 66 | 67 | embeddings = OllamaEmbeddings(model="nomic-embed-text") 68 | """ 69 | nomic-embed-text: 一个高性能开放嵌入模型,只有27M,具有较大的标记上下文窗口。 70 | 在做英文的嵌入和检索时,明显比llama3.1要好,可惜做中文不行。 71 | """ 72 | 73 | def get_vector_store(): 74 | """获取内存矢量数据库""" 75 | 76 | from langchain_core.vectorstores import InMemoryVectorStore 77 | 78 | vector_store = InMemoryVectorStore(embeddings) 79 | 80 | file_path = get_file_path() 81 | docs = load_file(file_path) 82 | all_splits = split_text(docs) 83 | _ = vector_store.add_documents(documents=all_splits) 84 | 85 | return vector_store 86 | 87 | def similarity_search(query): 88 | """内存矢量数据库检索测试""" 89 | 90 | vector_store = get_vector_store() 91 | results = vector_store.similarity_search(query) 92 | return results 93 | 94 | def similarity_search_with_score(query): 95 | """内存矢量数据库检索测试 96 | 返回文档评分,分数越高,文档越相似。 97 | """ 98 | vector_store = get_vector_store() 99 | 100 | results = vector_store.similarity_search_with_score(query) 101 | return results 102 | 103 | def embed_query(query): 104 | """嵌入查询测试""" 105 | 106 | embedding = embeddings.embed_query(query) 107 | 108 | vector_store = get_vector_store() 109 | results = vector_store.similarity_search_by_vector(embedding) 110 | return results 111 | 112 | 113 | # Retrievers 114 | from typing import List 115 | 116 | from langchain_core.documents import Document 117 | from langchain_core.runnables import chain 118 | 119 | 120 | @chain 121 | def retriever(query: str) -> List[Document]: 122 | vector_store = get_vector_store() 123 | return vector_store.similarity_search(query, k=1) 124 | 125 | 126 | def retriever_batch_1(query:List[str]): 127 | r = retriever.batch(query) 128 | return r 129 | 130 | 131 | def retriever_batch_2(query:List[str]): 132 | vector_store = get_vector_store() 133 | retriever = vector_store.as_retriever( 134 | search_type="similarity", 135 | search_kwargs={"k": 1}, 136 | ) 137 | 138 | r = retriever.batch(query) 139 | return r 140 | 141 | if __name__ == '__main__': 142 | ''' 143 | load_file(get_file_path()) 144 | 145 | results = similarity_search("How many distribution centers does Nike have in the US?") 146 | print(f'similarity_search results[0]:\n{results[0]}') 147 | 148 | results = similarity_search_with_score("What was Nike's revenue in 2023?") 149 | doc, score = results[0] 150 | print(f"Score and doc: {score}\n{doc}") 151 | 152 | results = embed_query("How were Nike's margins impacted in 2023?") 153 | print(f'embed_query results[0]:\n{results[0]}') 154 | ''' 155 | query = [ 156 | "How many distribution centers does Nike have in the US?", 157 | "When was Nike incorporated?", 158 | ] 159 | 160 | results = retriever_batch_1(query) 161 | print(f'retriever.batch 1:\n{results}') 162 | results = retriever_batch_2(query) 163 | print(f'retriever.batch 2:\n{results}') 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /server/services/practice/03_create_db_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-26 5 | # @function: 生成知识库 6 | # @version : V0.5 7 | # @Description :将csv数据矢量化存储。安装 langchain_chroma 时需要C++编译器,所以可以先安装Visual Studio .Net。 8 | 9 | import os 10 | 11 | # 获取当前文件的绝对路径 12 | current_file_path = os.path.abspath(__file__) 13 | 14 | # 获取当前文件所在的目录 15 | current_dir = os.path.dirname(current_file_path) 16 | 17 | persist_directory = os.path.join(current_dir,'assert/db_law') 18 | 19 | from langchain_ollama import OllamaEmbeddings 20 | embedding = OllamaEmbeddings(model='llama3.1') 21 | 22 | from langchain_chroma import Chroma 23 | from tqdm import tqdm 24 | 25 | def embed_documents_in_batches(documents, batch_size=10): 26 | """ 27 | 按批次嵌入,可以显示进度。 28 | vectordb会自动持久化存储在磁盘。 29 | """ 30 | vectordb = Chroma(persist_directory=persist_directory,embedding_function=embedding) 31 | for i in tqdm(range(0, len(documents), batch_size), desc="嵌入进度"): 32 | batch = documents[i:i + batch_size] 33 | # 从文本块生成嵌入,并将嵌入存储在本地磁盘。 34 | vectordb.add_documents(batch) 35 | 36 | 37 | from langchain.text_splitter import RecursiveCharacterTextSplitter 38 | from langchain_community.document_loaders import CSVLoader 39 | 40 | def create(): 41 | """对文本矢量化并存储在本地磁盘""" 42 | 43 | data_file = os.path.join(current_dir,'assert/law.csv') 44 | 45 | loader = CSVLoader(file_path=data_file, 46 | csv_args={"delimiter": "#"}, 47 | autodetect_encoding=True) 48 | docs = loader.load() 49 | #print(f'加载文件成功,第一个文件内容:{docs[0]}') 50 | 51 | # 用于将长文本拆分成较小的段,便于嵌入和大模型处理。 52 | # 每个文本块的最大长度是1000个字符,拆分的文本块之间重叠部分为200。 53 | text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) 54 | texts = text_splitter.split_documents(docs) 55 | 56 | # 耗时较长,需要耐心等候... 57 | embed_documents_in_batches(texts,batch_size=3) 58 | 59 | def search(query): 60 | """查询矢量数据库""" 61 | vector_store = Chroma(persist_directory=persist_directory,embedding_function=embedding) 62 | results = vector_store.similarity_search_with_score(query,k=1) 63 | return results 64 | 65 | if __name__ == '__main__': 66 | #create() 67 | results = search("恶意商标申请") 68 | print(f'search results:\n{results}') 69 | -------------------------------------------------------------------------------- /server/services/practice/04_classification.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-27 5 | # @function: 给文本打标签/分类 6 | # @version : V0.5 7 | # @Description :给文本打标签/分类。 8 | 9 | # 参考:https://python.langchain.com/docs/tutorials/classification/ 10 | 11 | from langchain_ollama import ChatOllama 12 | llm = ChatOllama(model="llama3.1",temperature=0.2,verbose=True) 13 | 14 | from langchain_core.prompts import ChatPromptTemplate 15 | from pydantic import BaseModel, Field 16 | 17 | def simple_control(s): 18 | 19 | tagging_prompt = ChatPromptTemplate.from_template( 20 | """ 21 | Extract the desired information from the following passage. 22 | 23 | Only extract the properties mentioned in the 'Classification' function. 24 | 25 | Passage: 26 | {input} 27 | """ 28 | ) 29 | 30 | # 指定​​ Pydantic 模型控制返回内容格式 31 | class Classification(BaseModel): 32 | sentiment: str = Field(description="The sentiment of the text") 33 | aggressiveness: int = Field( 34 | description="How aggressive the text is on a scale from 1 to 10" 35 | ) 36 | language: str = Field(description="The language the text is written in") 37 | 38 | 39 | llm_structured = llm.with_structured_output(Classification) 40 | prompt = tagging_prompt.invoke({"input": s}) 41 | response = llm_structured.invoke(prompt) 42 | 43 | return response.model_dump() 44 | 45 | def finer_control(s): 46 | """ 47 | 官网使用OpenAI,我们使用的是本地大模型。 48 | 直接用官网的代码效果不好:sentiment无法按预期标记出happy,neutral,sad,依然只能标记出:positive、negative;aggressiveness的值一直为0。 49 | """ 50 | 51 | # 指定​​ Pydantic 模型控制返回内容格式 52 | class Classification(BaseModel): 53 | sentiment: str = Field(description="The sentiment of the text,it must be one of happy,neutral,sad") 54 | aggressiveness: int = Field(description="The aggressive of the text,it must be one of 1,2,3,4,5,6,7,8,9,10,the higher the number the more aggressive") 55 | language: str = Field(description="The language the text is written in,it must be one of English,Spanish,Chinese") 56 | 57 | 58 | tagging_prompt = ChatPromptTemplate.from_template( 59 | """ 60 | Extract the desired information from the following passage. 61 | 62 | Only extract the properties mentioned in the 'Classification' function. 63 | 64 | Passage: 65 | {input} 66 | """ 67 | ) 68 | 69 | 70 | llm_structured = llm.with_structured_output(Classification) 71 | 72 | prompt = tagging_prompt.invoke({"input": s}) 73 | response = llm_structured.invoke(prompt) 74 | return response.model_dump() 75 | 76 | 77 | if __name__ == '__main__': 78 | 79 | s = "I'm incredibly glad I met you! I think we'll be great friends!" 80 | result = simple_control(s) 81 | print(f'result:\n{result}') 82 | 83 | s = "Estoy muy enojado con vos! Te voy a dar tu merecido!" 84 | result = simple_control(s) 85 | print(f'result:\n{result}') 86 | 87 | s = "I'm incredibly glad I met you! I think we'll be great friends!" 88 | result = finer_control(s) 89 | print(f'finer_control result:\n{result}') 90 | 91 | s = "Weather is ok here, I can go outside without much more than a coat" 92 | result = finer_control(s) 93 | print(f'finer_control result:\n{result}') 94 | 95 | s="今天的天气糟透了,我什么都不想干!" 96 | result = finer_control(s) 97 | print(f'finer_control result:\n{result}') -------------------------------------------------------------------------------- /server/services/practice/05_extraction_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-28 5 | # @function: 从文本中提取结构化信息 6 | # @version : V0.5 7 | # @Description :从文本中提取结构化信息。 8 | 9 | # https://python.langchain.com/docs/tutorials/extraction/ 10 | 11 | 12 | # The Schema 13 | 14 | from typing import Optional 15 | from pydantic import BaseModel, Field 16 | 17 | class Person(BaseModel): 18 | """Information about a person.""" 19 | 20 | # ^ Doc-string for the entity Person. 21 | # This doc-string is sent to the LLM as the description of the schema Person, 22 | # and it can help to improve extraction results. 23 | 24 | # Note that: 25 | # 1. Each field is an `optional` -- this allows the model to decline to extract it! 26 | # 2. Each field has a `description` -- this description is used by the LLM. 27 | # Having a good description can help improve extraction results. 28 | name: Optional[str] = Field(default=None, description="The name of the person") 29 | hair_color: Optional[str] = Field( 30 | default=None, description="The color of the person's hair if known" 31 | ) 32 | height_in_meters: Optional[float] = Field( 33 | default=None, description="Height measured in meters" 34 | ) 35 | 36 | # The Extractor 37 | 38 | from langchain_core.prompts import ChatPromptTemplate 39 | from pydantic import BaseModel, Field 40 | 41 | # Define a custom prompt to provide instructions and any additional context. 42 | # 1) You can add examples into the prompt template to improve extraction quality 43 | # 2) Introduce additional parameters to take context into account (e.g., include metadata 44 | # about the document from which the text was extracted.) 45 | prompt_template = ChatPromptTemplate.from_messages( 46 | [ 47 | ( 48 | "system", 49 | "You are an expert extraction algorithm. " 50 | "Only extract relevant information from the text. " 51 | "If you do not know the value of an attribute asked to extract, " 52 | "return null for the attribute's value.", 53 | ), 54 | # Please see the how-to about improving performance with 55 | # reference examples. 56 | # MessagesPlaceholder('examples'), 57 | ("human", "{text}"), 58 | ] 59 | ) 60 | 61 | from langchain_ollama import ChatOllama 62 | 63 | def single_entry(model_name,text): 64 | 65 | structured_llm = ChatOllama(model=model_name,temperature=0.5,verbose=True).with_structured_output(schema=Person) 66 | 67 | prompt = prompt_template.invoke({"text": text}) 68 | response = structured_llm.invoke(prompt) 69 | return response 70 | 71 | from typing import List 72 | 73 | class Data(BaseModel): 74 | """Extracted data about people.""" 75 | 76 | # Creates a model so that we can extract multiple entities. 77 | people: List[Person] 78 | 79 | def multiple_entry(model_name,text): 80 | structured_llm = ChatOllama(model=model_name,temperature=0.5,verbose=True).with_structured_output(schema=Data) 81 | prompt = prompt_template.invoke({"text": text}) 82 | response = structured_llm.invoke(prompt) 83 | return response 84 | 85 | 86 | if __name__ == '__main__': 87 | print ('--------------------llama3------------------------------') 88 | 89 | # llama3.1无法自动把feet转换成meter,所以我们把这个问题简化了一些,在text中直接用meter做单位。 90 | text = "Alan Smith is 1.83 meters tall and has blond hair." 91 | response = single_entry("llama3.1",text) 92 | print(f'\n llama3.1 response:\n{response}') 93 | 94 | text = "Alan Smith is 6 feet tall and has blond hair." 95 | response = single_entry("llama3.1",text) 96 | print(f'\n llama3.1 response:\n{response}') 97 | 98 | text = "Alan Smith is 1.83 meters tall and has blond hair. John Doe is 1.72 meters tall and has brown hair." 99 | response = multiple_entry("llama3.1",text) 100 | print(f'\n llama3.1 response:\n{response}') 101 | 102 | text = "Alan Smith is 1.88 meters tall and has blond hair. John Doe is 7 feet tall and has brown hair." 103 | response = multiple_entry("llama3.1",text) 104 | print(f'\n llama3.1 response:\n{response}') 105 | 106 | 107 | print ('---------------------deepseek------------------------------') 108 | 109 | text = "Alan Smith is 1.83 meters tall and has blond hair." 110 | response = single_entry("MFDoom/deepseek-r1-tool-calling:7b",text) 111 | print(f'\n deepseek response:\n{response}') 112 | 113 | text = "Alan Smith is 6 feet tall and has blond hair." 114 | response = multiple_entry("MFDoom/deepseek-r1-tool-calling:7b",text) 115 | print(f'\n deepseek response:\n{response}') 116 | 117 | text = "Alan Smith is 1.83 meters tall and has blond hair. John Doe is 1.72 meters tall and has brown hair." 118 | response = multiple_entry("MFDoom/deepseek-r1-tool-calling:7b",text) 119 | print(f'\n deepseek response:\n{response}') 120 | 121 | text = "Alan Smith is 1.88 meters tall and has blond hair. John Doe is 7 feet tall and has brown hair." 122 | response = single_entry("MFDoom/deepseek-r1-tool-calling:7b",text) 123 | print(f'\n deepseek response:\n{response}') 124 | -------------------------------------------------------------------------------- /server/services/practice/06_extraction_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-28 5 | # @function: 从文本中提取结构化信息 6 | # @version : V0.5 7 | # @Description :从文本中提取结构化信息。 8 | 9 | # https://python.langchain.com/docs/tutorials/extraction/ 10 | 11 | 12 | # Reference examples 13 | 14 | from langchain_ollama import ChatOllama 15 | 16 | def reference(model_name): 17 | messages = [ 18 | {"role": "user", "content": "2 🦜 2"}, 19 | {"role": "assistant", "content": "4"}, 20 | {"role": "user", "content": "2 🦜 3"}, 21 | {"role": "assistant", "content": "5"}, 22 | {"role": "user", "content": "3 🦜 4"}, 23 | ] 24 | 25 | response = ChatOllama(model=model_name,temperature=0.5,verbose=True).invoke(messages) 26 | return response.content 27 | 28 | from typing import Optional 29 | from pydantic import BaseModel, Field 30 | 31 | class Person(BaseModel): 32 | """Information about a person.""" 33 | 34 | # ^ Doc-string for the entity Person. 35 | # This doc-string is sent to the LLM as the description of the schema Person, 36 | # and it can help to improve extraction results. 37 | 38 | # Note that: 39 | # 1. Each field is an `optional` -- this allows the model to decline to extract it! 40 | # 2. Each field has a `description` -- this description is used by the LLM. 41 | # Having a good description can help improve extraction results. 42 | name: Optional[str] = Field(default=None, description="The name of the person") 43 | hair_color: Optional[str] = Field( 44 | default=None, description="The color of the person's hair if known" 45 | ) 46 | height_in_meters: Optional[float] = Field( 47 | default=None, description="Height measured in meters" 48 | ) 49 | 50 | from langchain_core.utils.function_calling import tool_example_to_messages 51 | 52 | examples = [ 53 | ( 54 | "The ocean is vast and blue. It's more than 20,000 feet deep.", 55 | Person(), 56 | ), 57 | ( 58 | "Fiona traveled far from France to Spain.", 59 | Person(name="Fiona", height_in_meters=None, hair_color=None), 60 | ), 61 | ( 62 | "Alan Smith is 1.83 meters tall and has blond hair.", 63 | Person(name="Alan Smith", height_in_meters=1.83, hair_color="blond"), 64 | ), 65 | ] 66 | 67 | messages = [] 68 | 69 | for txt, tool_call in examples: 70 | if tool_call.name is None: 71 | # This final message is optional for some providers 72 | ai_response = "Detected people." 73 | else: 74 | ai_response = "Detected no people." 75 | messages.extend(tool_example_to_messages(txt, [tool_call], ai_response=ai_response)) 76 | 77 | for message in messages: 78 | message.pretty_print() 79 | 80 | def extract(model_name,text): 81 | structured_llm = ChatOllama(model=model_name,temperature=0,verbose=True).with_structured_output(schema=Person) 82 | user_message = {"role": "user", "content":text} 83 | response = structured_llm.invoke([user_message]) 84 | return response 85 | 86 | def extract_with_messages(model_name,text): 87 | structured_llm = ChatOllama(model=model_name,temperature=0,verbose=True).with_structured_output(schema=Person) 88 | user_message = {"role": "user", "content":text} 89 | structured_llm.invoke(messages + [user_message]) 90 | return response 91 | 92 | if __name__ == '__main__': 93 | ''' 94 | response = reference("llama3.1") 95 | print(f'\n llama3.1 response:\n{response}') 96 | 97 | response = reference("MFDoom/deepseek-r1-tool-calling:7b") 98 | print(f'\n deepseek-r1 response:\n{response}') 99 | ''' 100 | print('-----------------------llama-------------------------------') 101 | text = "Roy is 1.73 meters tall and has black hair." 102 | response = extract("llama3.1",text) 103 | print(f'\n llama3.1 response:\n{response}') 104 | response = extract_with_messages("llama3.1",text) 105 | print(f'\n llama3.1 response:\n{response}') 106 | 107 | text = "John Doe is 1.72 meters tall and has brown hair." 108 | response = extract("llama3.1",text) 109 | print(f'\n llama3.1 response:\n{response}') 110 | response = extract_with_messages("llama3.1",text) 111 | print(f'\n llama3.1 response:\n{response}') 112 | 113 | print('-----------------------deepseek-------------------------------') 114 | 115 | text = "Roy is 1.73 meters tall and has black hair." 116 | response = extract("MFDoom/deepseek-r1-tool-calling:7b",text) 117 | print(f'\n deepseek response:\n{response}') 118 | response = extract_with_messages("MFDoom/deepseek-r1-tool-calling:7b",text) 119 | print(f'\n deepseek response:\n{response}') 120 | 121 | text = "John Doe is 1.72 meters tall and has brown hair." 122 | response = extract("MFDoom/deepseek-r1-tool-calling:7b",text) 123 | print(f'\n llama3.1 response:\n{response}') 124 | response = extract_with_messages("llama3.1",text) 125 | print(f'\n llama3.1 response:\n{response}') -------------------------------------------------------------------------------- /server/services/practice/07_chatbot_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-02-04 5 | # @function: 用langgraph实现的chatbot 6 | # @version : V0.5 7 | 8 | from langchain_ollama import ChatOllama 9 | from langchain_core.messages import HumanMessage 10 | 11 | def chat(model_name): 12 | model = ChatOllama(model=model_name,temperature=0.3,verbose=True) 13 | response = model.invoke([HumanMessage(content="Hi! I'm Bob")]) 14 | print(f'chat_with_no_memory:{response.content}') 15 | 16 | # We can see that it doesn't take the previous conversation turn into context, and cannot answer the question. This makes for a terrible chatbot experience! 17 | response = model.invoke([HumanMessage(content="What's my name?")]) 18 | print(f'chat_with_no_memory 2:{response.content}') 19 | 20 | from langchain_core.messages import AIMessage 21 | 22 | def chat_with_memory(model_name): 23 | '''具有记忆功能''' 24 | model = ChatOllama(model=model_name,temperature=0.3,verbose=True) 25 | response = model.invoke( 26 | [ 27 | HumanMessage(content="Hi! I'm Bob"), 28 | AIMessage(content="Hello Bob! How can I assist you today?"), 29 | HumanMessage(content="What's my name?"), 30 | ] 31 | ) 32 | print(f'chat_with_memory:{response.content}') 33 | 34 | from langgraph.checkpoint.memory import MemorySaver 35 | from langgraph.graph import START, MessagesState, StateGraph 36 | 37 | # Message persistence 38 | def build_app(model_name): 39 | 40 | model = ChatOllama(model=model_name,temperature=0.3,verbose=True) 41 | 42 | # Define the function that calls the model 43 | def call_model(state: MessagesState): 44 | response = model.invoke(state["messages"]) 45 | return {"messages": response} 46 | 47 | # Define a new graph 48 | workflow = StateGraph(state_schema=MessagesState) 49 | 50 | # Define the (single) node in the graph 51 | workflow.add_edge(START, "model") 52 | workflow.add_node("model", call_model) 53 | 54 | # Add memory 55 | memory = MemorySaver() 56 | app = workflow.compile(checkpointer=memory) 57 | return app 58 | 59 | def message_persistence(model_name): 60 | app = build_app(model_name) 61 | 62 | config = {"configurable": {"thread_id": "abc123"}} 63 | 64 | query = "Hi! I'm Bob." 65 | 66 | input_messages = [HumanMessage(query)] 67 | output = app.invoke({"messages": input_messages}, config) 68 | print(output["messages"][-1].pretty_print()) # output contains all messages in state 69 | 70 | query = "What's my name?" 71 | 72 | input_messages = [HumanMessage(query)] 73 | output = app.invoke({"messages": input_messages}, config) 74 | print(output["messages"][-1].pretty_print()) 75 | 76 | # different thread_id 77 | config = {"configurable": {"thread_id": "abc234"}} 78 | 79 | input_messages = [HumanMessage(query)] 80 | output = app.invoke({"messages": input_messages}, config) 81 | print(output["messages"][-1].pretty_print()) 82 | 83 | config = {"configurable": {"thread_id": "abc123"}} 84 | 85 | input_messages = [HumanMessage(query)] 86 | output = app.invoke({"messages": input_messages}, config) 87 | print(output["messages"][-1].pretty_print()) 88 | 89 | if __name__ == '__main__': 90 | mode_name = "llama3.1" 91 | print(f'----------------------------{mode_name}---------------------------') 92 | chat(mode_name) 93 | chat_with_memory(mode_name) 94 | 95 | message_persistence(mode_name) 96 | 97 | mode_name = "deepseek-r1" 98 | print(f'----------------------------{mode_name}---------------------------') 99 | chat(mode_name) 100 | chat_with_memory(mode_name) 101 | 102 | message_persistence(mode_name) -------------------------------------------------------------------------------- /server/services/practice/08_chatbot_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-02-04 5 | # @function: 用langgraph实现的chatbot 6 | # @version : V0.5 7 | 8 | from langchain_ollama import ChatOllama 9 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 10 | from langgraph.checkpoint.memory import MemorySaver 11 | from langgraph.graph import START, MessagesState, StateGraph 12 | 13 | 14 | def build_app_with_prompt_1(model_name): 15 | model = ChatOllama(model=model_name,temperature=0.3,verbose=True) 16 | 17 | def call_model(state: MessagesState): 18 | prompt_template = ChatPromptTemplate.from_messages( 19 | [ 20 | ( 21 | "system", 22 | "You talk like a pirate. Answer all questions to the best of your ability.", 23 | ), 24 | MessagesPlaceholder(variable_name="messages"), 25 | ] 26 | ) 27 | 28 | prompt = prompt_template.invoke(state) 29 | response = model.invoke(prompt) 30 | return {"messages": response} 31 | 32 | workflow = StateGraph(state_schema=MessagesState) 33 | workflow.add_edge(START, "model") 34 | workflow.add_node("model", call_model) 35 | 36 | memory = MemorySaver() 37 | app = workflow.compile(checkpointer=memory) 38 | return app 39 | 40 | from langchain_core.messages import HumanMessage 41 | 42 | def test_app_1(model_name): 43 | app = build_app_with_prompt_1(model_name) 44 | 45 | config = {"configurable": {"thread_id": "abc345"}} 46 | query = "Hi! I'm Jim." 47 | 48 | input_messages = [HumanMessage(query)] 49 | output = app.invoke({"messages": input_messages}, config) 50 | print(output["messages"][-1].pretty_print()) 51 | 52 | query = "What is my name?" 53 | 54 | input_messages = [HumanMessage(query)] 55 | output = app.invoke({"messages": input_messages}, config) 56 | print(output["messages"][-1].pretty_print()) 57 | 58 | from typing import Sequence 59 | from langchain_core.messages import BaseMessage 60 | from langgraph.graph.message import add_messages 61 | from typing_extensions import Annotated, TypedDict 62 | 63 | # added a new language input to the prompt 64 | prompt_template = ChatPromptTemplate.from_messages( 65 | [ 66 | ( 67 | "system", 68 | "You are a helpful assistant. Answer all questions to the best of your ability in {language}.", 69 | ), 70 | MessagesPlaceholder(variable_name="messages"), 71 | ] 72 | ) 73 | 74 | class State(TypedDict): 75 | messages: Annotated[Sequence[BaseMessage], add_messages] 76 | language: str 77 | 78 | def build_app_with_prompt_2(model_name): 79 | model = ChatOllama(model=model_name,temperature=0.3,verbose=True) 80 | 81 | def call_model(state: State): 82 | prompt = prompt_template.invoke(state) 83 | response = model.invoke(prompt) 84 | return {"messages": [response]} 85 | 86 | workflow = StateGraph(state_schema=State) 87 | workflow.add_edge(START, "model") 88 | workflow.add_node("model", call_model) 89 | 90 | memory = MemorySaver() 91 | app = workflow.compile(checkpointer=memory) 92 | return app 93 | 94 | def test_app_2(model_name): 95 | app = build_app_with_prompt_2(model_name) 96 | 97 | config = {"configurable": {"thread_id": "abc456"}} 98 | language = "简体中文" 99 | 100 | query = "嘿,你好,我是刘大山。" 101 | 102 | input_messages = [HumanMessage(query)] 103 | output = app.invoke( 104 | {"messages": input_messages, "language": language}, 105 | config, 106 | ) 107 | print(output["messages"][-1].pretty_print()) 108 | 109 | query = "我叫什么名字?" 110 | 111 | input_messages = [HumanMessage(query)] 112 | output = app.invoke( 113 | {"messages": input_messages}, 114 | config, 115 | ) 116 | print(output["messages"][-1].pretty_print()) 117 | 118 | if __name__ == '__main__': 119 | mode_name = "llama3.1" 120 | #test_app_1(mode_name) 121 | test_app_2(mode_name) 122 | 123 | mode_name = "deepseek-r1" 124 | #test_app_1(mode_name) 125 | test_app_2(mode_name) -------------------------------------------------------------------------------- /server/services/practice/09_chatbot_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-02-06 5 | # @function: 用langgraph实现的chatbot 6 | # @version : V0.5 7 | 8 | from langchain_ollama import ChatOllama 9 | from langchain_core.messages import AIMessage, HumanMessage,SystemMessage, trim_messages 10 | 11 | def get_trimmer(model_name,max_tokens): 12 | """ 13 | 重要:请务必在在加载之前的消息之后,并且在提示词模板之前使用它。 14 | """ 15 | model = ChatOllama(model=model_name,temperature=0.3,verbose=True) 16 | trimmer = trim_messages( 17 | max_tokens=max_tokens, #设置裁剪后消息列表中允许的最大 token 数量 18 | strategy="last", #指定裁剪策略为保留最后的消息,即从消息列表的开头开始裁剪,直到满足最大 token 数量限制。 19 | token_counter=model, #通过model来计算消息中的 token 数量。 20 | include_system=True, #在裁剪过程中包含系统消息(SystemMessage) 21 | allow_partial=False, #不允许裁剪出部分消息,即要么保留完整的消息,要么不保留,不会出现只保留消息的一部分的情况。 22 | start_on="human", #从人类消息(HumanMessage)开始进行裁剪,即裁剪时会从第一个HumanMessage开始计算 token 数量,之前的系统消息等也会被包含在内进行整体裁剪考量。 23 | ) 24 | return trimmer 25 | 26 | messages = [ 27 | SystemMessage(content="你是个好助手"), 28 | HumanMessage(content="你好,我是刘大钧"), 29 | AIMessage(content="你好"), 30 | HumanMessage(content="我喜欢香草冰淇淋"), 31 | AIMessage(content="很好啊"), 32 | HumanMessage(content="3 + 3等于几?"), 33 | AIMessage(content="6"), 34 | HumanMessage(content="谢谢"), 35 | AIMessage(content="不客气"), 36 | HumanMessage(content="和我聊天有意思么?"), 37 | AIMessage(content="是的,很有意思"), 38 | ] 39 | 40 | def test_trimmer(model_name,max_tokens): 41 | t = get_trimmer(model_name,max_tokens) 42 | messages_trimed = t.invoke(messages) 43 | print(f'{model_name} messages_trimed:\n{messages_trimed}') 44 | 45 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 46 | 47 | # added a new language input to the prompt 48 | prompt_template = ChatPromptTemplate.from_messages( 49 | [ 50 | ( 51 | "system", 52 | "你是一个乐于助人的助手。请用{language}尽力回答所有问题。", 53 | ), 54 | MessagesPlaceholder(variable_name="messages"), 55 | ] 56 | ) 57 | 58 | 59 | from typing_extensions import Annotated, TypedDict 60 | from langgraph.graph.message import add_messages 61 | from typing import Sequence 62 | from langchain_core.messages import BaseMessage 63 | 64 | class State(TypedDict): 65 | messages: Annotated[Sequence[BaseMessage], add_messages] 66 | language: str 67 | 68 | 69 | from langgraph.checkpoint.memory import MemorySaver 70 | from langgraph.graph import START, StateGraph 71 | 72 | def build_app(model_name,max_tokens): 73 | model = ChatOllama(model=model_name,temperature=0.3,verbose=True) 74 | 75 | def call_model(state: State): 76 | trimmer = get_trimmer(model_name=model_name,max_tokens=max_tokens) 77 | trimmed_messages = trimmer.invoke(state["messages"]) 78 | prompt = prompt_template.invoke( 79 | {"messages": trimmed_messages, "language": state["language"]} 80 | ) 81 | response = model.invoke(prompt) 82 | return {"messages": [response]} 83 | 84 | workflow = StateGraph(state_schema=State) 85 | workflow.add_edge(START, "model") 86 | workflow.add_node("model", call_model) 87 | 88 | memory = MemorySaver() 89 | app = workflow.compile(checkpointer=memory) 90 | return app 91 | 92 | def test_app(model_name,max_tokens): 93 | app = build_app(model_name,max_tokens) 94 | 95 | config = {"configurable": {"thread_id": "abc456"}} 96 | language = "简体中文" 97 | 98 | query = "我叫什么名字?" 99 | 100 | input_messages = messages + [HumanMessage(query)] 101 | 102 | output = app.invoke( 103 | {"messages": input_messages, "language": language}, 104 | config, 105 | ) 106 | print(output["messages"][-1].pretty_print()) 107 | 108 | app = build_app(model_name,max_tokens) 109 | """ 110 | 重新构建app的目的是方便测试消息裁剪,否则app会缓存messages,导致下面的问题回答不出来。 111 | """ 112 | 113 | query = "我问过什么数学问题?" 114 | 115 | input_messages = messages + [HumanMessage(query)] 116 | output = app.invoke( 117 | {"messages": input_messages, "language": language}, 118 | config, 119 | ) 120 | print(output["messages"][-1].pretty_print()) 121 | 122 | def stream(human_message,thread_id,model_name,max_tokens=140,language="简体中文"): 123 | ''' 124 | 流式输出 125 | ''' 126 | app = build_app(model_name,max_tokens) 127 | for chunk, _ in app.stream( 128 | {"messages":[HumanMessage(content=human_message)],"language":language}, 129 | config={"configurable": {"thread_id": thread_id}}, 130 | stream_mode="messages", 131 | ): 132 | if isinstance(chunk, AIMessage): 133 | yield chunk.content 134 | 135 | def test_1(model_name): 136 | max_token = 140 137 | test_trimmer(model_name,max_token) 138 | test_app(model_name,max_token) 139 | 140 | def test_2(model_name): 141 | max_token = 140 142 | thread_id = "liupras" 143 | query = "请以葛优的语气,写一首幽默的打油诗。" 144 | language = "简体中文" 145 | 146 | print(f"---------{model_name}---------------") 147 | 148 | for r in stream(query,thread_id,model_name,max_tokens=max_token ,language=language): 149 | if r is not None: 150 | print (r, end="|") 151 | 152 | if __name__ == '__main__': 153 | 154 | #test_1("llama3.1") 155 | #test_1("deepseek-r1") 156 | 157 | test_2("llama3.1") 158 | test_2("deepseek-r1") 159 | 160 | -------------------------------------------------------------------------------- /server/services/practice/10_tool_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-07 5 | # @function: LLM调用tools计算加法和乘法 6 | # @version : V0.5 7 | # @Description :调用函数tools计算加法和乘法。 8 | 9 | # https://python.langchain.com/docs/how_to/tool_results_pass_to_model/ 10 | 11 | from langchain_core.tools import tool 12 | 13 | @tool 14 | def add(a: int, b: int) -> int: 15 | """计算a和b的和。""" 16 | print (f"add is called...{a}+{b}") 17 | return a + b 18 | 19 | @tool 20 | def multiply(a: int, b: int) -> int: 21 | """计算a和b的乘积。""" 22 | print (f"multiply is called...{a}*{b}") 23 | return a * b 24 | 25 | tools = [add, multiply] 26 | 27 | 28 | # 让模型调用一个工具,并把消息添加到历史记录中。 29 | from langchain_core.messages import HumanMessage 30 | from langchain_ollama import ChatOllama 31 | 32 | def caculate(model_name,query): 33 | print(f"\n---------{model_name}---------------") 34 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 35 | llm_with_tools = llm.bind_tools(tools) 36 | 37 | messages = [HumanMessage(query)] 38 | 39 | # 调用LLM,将query转化为json结构 40 | print("---1、调用LLM,将query转化为json结构---") 41 | ai_msg = llm_with_tools.invoke(messages) 42 | print(f' tool_calls is:{ai_msg.tool_calls}') 43 | 44 | messages.append(ai_msg) 45 | print(f'messages are:{messages}') 46 | 47 | # 使用模型生成的参数来调用工具函数 48 | 49 | # 调用 add 和 multiply,计算出结果 50 | print("---2、不调用LLM,直接调用 add 和 multiply,计算出结果---") 51 | for tool_call in ai_msg.tool_calls: 52 | selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()] 53 | tool_msg = selected_tool.invoke(tool_call) 54 | messages.append(tool_msg) 55 | print(f'now,messages are:{messages}') 56 | 57 | # 调用LLM,生成流畅的答案 58 | print("---3、调用LLM,生成流畅的答案---") 59 | result = llm_with_tools.invoke(messages) 60 | print(f'result is:{result.content}') 61 | 62 | if __name__ == '__main__': 63 | query = "3 * 12等于多少? 11 + 49等于多少?" 64 | 65 | caculate("llama3.1",query) 66 | #caculate("deepseek-llm",query) -------------------------------------------------------------------------------- /server/services/practice/11_tool_ad_hoc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-11 5 | # @function: ad_hoc tool 6 | # @version : V0.5 7 | 8 | 9 | # https://python.langchain.com/docs/how_to/tools_prompting/ 10 | 11 | from langchain_core.tools import tool 12 | 13 | def create_tools(): 14 | """创建tools""" 15 | @tool 16 | def add(x: int, y: int) -> int: 17 | """计算a和b的和。""" 18 | print (f"add is called...{x}+{y}") 19 | return x + y 20 | 21 | @tool 22 | def multiply(x: int, y: int) -> int: 23 | """计算a和b的乘积。""" 24 | print (f"multiply is called...{x}*{y}") 25 | return x * y 26 | 27 | tools = [add, multiply] 28 | 29 | for t in tools: 30 | print("--") 31 | print(t.name) 32 | print(t.description) 33 | print(t.args) 34 | 35 | return tools 36 | 37 | tools = create_tools() 38 | 39 | # 创建提示词 40 | from langchain_core.prompts import ChatPromptTemplate 41 | from langchain_core.tools import render_text_description 42 | 43 | rendered_tools = render_text_description(tools) 44 | print(rendered_tools) 45 | 46 | system_prompt = f"""\ 47 | 您是一名助理,有权使用以下工具集。 48 | 以下是每个工具的名称和说明: 49 | 50 | {rendered_tools} 51 | 52 | 根据用户输入,返回要使用的工具的名称和输入。 53 | 以 JSON blob 形式返回您的响应,其中包含“name”和“arguments”键。 54 | 55 | “arguments”应该是一个字典,其中的键对应于参数名称,值对应于请求的值。 56 | """ 57 | 58 | prompt = ChatPromptTemplate.from_messages( 59 | [("system", system_prompt), ("user", "{input}")] 60 | ) 61 | 62 | def too_call(model_name,query): 63 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 64 | 65 | chain = prompt | llm 66 | message = chain.invoke({"input": query}) 67 | print(f'response: \n{message.content}') 68 | 69 | from langchain_ollama import ChatOllama 70 | 71 | # 将上级目录加入path,这样就可以直接引用上级目录的模块 72 | import os,sys 73 | parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 74 | sys.path.append(parent_dir) 75 | 76 | from common.MyJsonOutputParser import ThinkJsonOutputParser 77 | 78 | def too_call_json(model_name,query): 79 | """以json格式输出""" 80 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 81 | 82 | chain = prompt | llm | ThinkJsonOutputParser() 83 | message =chain.invoke({"input": query}) 84 | print(f'JsonOutputParser: \n{message}') 85 | 86 | # Invoking the tool 87 | # The function will select the appropriate tool by name, and pass to it the arguments chosen by the mode 88 | from typing import Any, Dict, Optional, TypedDict 89 | from langchain_core.runnables import RunnableConfig 90 | 91 | class ToolCallRequest(TypedDict): 92 | """invoke_tool 函数使用的参数格式。""" 93 | 94 | name: str 95 | arguments: Dict[str, Any] 96 | 97 | 98 | def invoke_tool( 99 | tool_call_request: ToolCallRequest, config: Optional[RunnableConfig] = None 100 | ): 101 | """执行工具调用的函数。 102 | 103 | Args: 104 | tool_call_request: 包含键名和参数的字典。 105 | `name` 必须与已存在的工具名称匹配。 106 | `arguments` 是工具函数的参数。 107 | config: 这是 LangChain 使用的配置信息,其中包含回调、元数据等内容。 108 | 109 | Returns: 110 | requested tool 的输出 111 | """ 112 | tool_name_to_tool = {tool.name: tool for tool in tools} 113 | name = tool_call_request["name"] 114 | requested_tool = tool_name_to_tool[name] 115 | return requested_tool.invoke(tool_call_request["arguments"], config=config) 116 | 117 | r = invoke_tool({"name": "multiply", "arguments": {"x": 3, "y": 5}}) 118 | print(f'test invoke_tool:{r}') 119 | 120 | def invoke_chain(model_name,query): 121 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 122 | 123 | chain = prompt | llm | ThinkJsonOutputParser() | invoke_tool 124 | result =chain.invoke({"input": query}) 125 | print(f'invoke_chain:\n{result}') 126 | 127 | def invoke_chain_with_input(model_name,query): 128 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 129 | 130 | from langchain_core.runnables import RunnablePassthrough 131 | 132 | chain = ( 133 | prompt | llm | ThinkJsonOutputParser() | RunnablePassthrough.assign(output=invoke_tool) 134 | ) 135 | result = chain.invoke({"input": query}) 136 | print(f'invoke_chain with input:\n{result}') 137 | 138 | if __name__ == '__main__': 139 | query = "3 * 12等于多少?" 140 | ''' 141 | too_call("llama3.1",query) 142 | too_call("deepseek-r1",query) 143 | 144 | too_call_json("llama3.1",query) 145 | too_call_json("deepseek-r1",query) 146 | 147 | invoke_chain("llama3.1",query) 148 | invoke_chain("deepseek-r1",query) 149 | ''' 150 | invoke_chain_with_input("llama3.1",query) 151 | invoke_chain_with_input("deepseek-r1",query) 152 | 153 | query = "11 + 49等于多少?" -------------------------------------------------------------------------------- /server/services/practice/12_tool_human_in_the_loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-08 5 | # @function: 在Agent中增加人的干预 6 | # @version : V0.5 7 | # @Description :在Agent中增加人的干预。有些工具我们不信任模型能够自行执行。在这种情况下,我们可以做的一件事是在调用工具之前要求人工批准。 8 | 9 | # https://python.langchain.com/docs/how_to/tools_human/ 10 | 11 | from typing import Dict, List 12 | 13 | from langchain_core.messages import AIMessage 14 | from langchain_core.tools import tool 15 | 16 | def create_tools(): 17 | @tool 18 | def count_emails(last_n_days: int) -> int: 19 | """计算电子邮件数量的函数。""" 20 | print(f'count_emails is called:{last_n_days}') 21 | return last_n_days * 2 22 | 23 | @tool 24 | def send_email(message: str, recipient: str) -> str: 25 | """发送电子邮件的函数。""" 26 | print(f'send_email is called:{recipient}:{message}') 27 | return f"邮件已经成功发送至:{recipient}." 28 | 29 | 30 | tools = [count_emails, send_email] 31 | 32 | return tools 33 | 34 | tools = create_tools() 35 | 36 | from langchain_ollama import ChatOllama 37 | from langchain_core.messages import HumanMessage 38 | 39 | def test_tool_call(model_name,query): 40 | """测试tool_call,看看输出内容""" 41 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 42 | llm_with_tools = llm.bind_tools(tools) 43 | 44 | messages = [HumanMessage(query)] 45 | ai_msg = llm_with_tools.invoke(messages) 46 | print(f' tool_calls is:\n{ai_msg.tool_calls}') 47 | 48 | def call_tools(msg: AIMessage) -> List[Dict]: 49 | """调用工具的通用方法。""" 50 | 51 | tool_map = {tool.name: tool for tool in tools} 52 | tool_calls = msg.tool_calls.copy() 53 | for tool_call in tool_calls: 54 | tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"]) 55 | return tool_calls 56 | 57 | def tool_call(model_name,query): 58 | """使用chain调用tools很方便。这里直接输出json格式的结果""" 59 | 60 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 61 | llm_with_tools = llm.bind_tools(tools) 62 | chain = llm_with_tools | call_tools 63 | result = chain.invoke(query) 64 | print(f'chain.invoked:\n{result}') 65 | 66 | 67 | import json 68 | 69 | class NotApproved(Exception): 70 | """自定义异常。""" 71 | print(f'Not approved:{Exception}') 72 | 73 | 74 | def human_approval(msg: AIMessage) -> AIMessage: 75 | """负责传递其输入或引发异常。 76 | 77 | Args: 78 | msg: 聊天模型的输出 79 | 80 | Returns: 81 | msg: 消息的原始输出 82 | """ 83 | tool_strs = "\n\n".join( 84 | json.dumps(tool_call, indent=2) for tool_call in msg.tool_calls 85 | ) 86 | input_msg = ( 87 | f"您是否同意以下工具调用\n\n{tool_strs}\n\n" 88 | "除'Y/Yes'(不区分大小写)之外的任何内容都将被视为否。\n >>>" 89 | ) 90 | resp = input(input_msg) 91 | if resp.lower() not in ("yes", "y"): 92 | print("主人没有批准。") 93 | raise NotApproved(f"未批准使用工具:\n\n{tool_strs}") 94 | print("主人已批准。") 95 | return msg 96 | 97 | def approval(model_name,query): 98 | """由人类批准是否使用工具""" 99 | 100 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 101 | llm_with_tools = llm.bind_tools(tools) 102 | 103 | chain = llm_with_tools | human_approval | call_tools 104 | 105 | try: 106 | result = chain.invoke(query) 107 | print(f'human-in-the-loop chain.invoke:{result}') 108 | except NotApproved as e: 109 | print(f'Not approved:{e}') 110 | 111 | if __name__ == '__main__': 112 | query = "我过去7天收到了多少封电子邮件?" 113 | 114 | print('--------test_tool_call----------------------') 115 | test_tool_call("llama3.1",query) 116 | test_tool_call("MFDoom/deepseek-r1-tool-calling:7b",query) 117 | 118 | print('--------tool_call----------------------') 119 | tool_call("llama3.1",query) 120 | tool_call("MFDoom/deepseek-r1-tool-calling:7b",query) 121 | 122 | print('--------approval----------------------') 123 | approval("llama3.1",query) 124 | approval("MFDoom/deepseek-r1-tool-calling:7b",query) -------------------------------------------------------------------------------- /server/services/practice/13_tool_InjectedToolArg_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-08 5 | # @function: 如何将运行时值传递给工具 6 | # @version : V0.5 7 | # @Description :如何将运行时值传递给工具。 8 | 9 | # https://python.langchain.com/docs/how_to/tool_runtime/ 10 | 11 | from typing import List 12 | from typing_extensions import Annotated 13 | from langchain_core.tools import InjectedToolArg, tool 14 | 15 | user_to_pets = {} 16 | 17 | @tool(parse_docstring=True) 18 | def update_favorite_pets( 19 | pets: List[str], user_id: Annotated[str, InjectedToolArg] 20 | ) -> None: 21 | """添加或者更新最喜爱的宠物列表。 22 | 23 | Args: 24 | pets: 最喜爱的宠物列表。 25 | user_id: 用户ID。 26 | """ 27 | print(f'update_favorite_pets is called:{user_id}') 28 | user_to_pets[user_id] = pets 29 | 30 | 31 | @tool(parse_docstring=True) 32 | def delete_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 33 | """删除喜爱的宠物列表。 34 | 35 | Args: 36 | user_id: 用户 ID。 37 | """ 38 | print(f'delete_favorite_pets is called:{user_id}') 39 | if user_id in user_to_pets: 40 | del user_to_pets[user_id] 41 | 42 | 43 | @tool(parse_docstring=True) 44 | def list_favorite_pets(user_id: Annotated[str, InjectedToolArg]) -> None: 45 | """列出最喜欢的宠物。 46 | 47 | Args: 48 | user_id: 用户 ID。 49 | """ 50 | print(f'list_favorite_pets is called:{user_id}') 51 | return user_to_pets.get(user_id, []) 52 | 53 | def test_tool(): 54 | """测试工具""" 55 | 56 | # 查看这些工具的输入数据结构,我们会看到 user_id 仍然会列出来 57 | print(f'get_input_schema:{update_favorite_pets.get_input_schema().model_json_schema()}') 58 | 59 | # 但是如果我们查看工具调用数据结构(即传递给模型进行工具调用的内容),user_id 已被删除 60 | print(f'tool_call_schema:{update_favorite_pets.tool_call_schema.model_json_schema()}') 61 | 62 | user_id = "123" 63 | update_favorite_pets.invoke({"pets": ["lizard", "dog"], "user_id": user_id}) 64 | print(f'user_to_pets:{user_to_pets}') 65 | print(f'list_favorite_pets.invoke:{list_favorite_pets.invoke({"user_id": user_id})}') 66 | 67 | # 当模型调用该工具时,不会生成任何 user_id 参数/实参 68 | tools = [ 69 | update_favorite_pets, 70 | delete_favorite_pets, 71 | list_favorite_pets, 72 | ] 73 | 74 | from langchain_ollama import ChatOllama 75 | 76 | def invoke_tool(model_name,query): 77 | """测试生成的tool_call""" 78 | 79 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 80 | llm_with_tools = llm.bind_tools(tools) 81 | 82 | ai_msg = llm_with_tools.invoke(query) 83 | print(f'result:\n{ai_msg.tool_calls}') 84 | 85 | return ai_msg 86 | 87 | # 在运行时注入参数 88 | from copy import deepcopy 89 | 90 | from langchain_core.runnables import chain 91 | 92 | user_id ="u123" 93 | 94 | @chain 95 | def inject_user_id(ai_msg): 96 | tool_calls = [] 97 | for tool_call in ai_msg.tool_calls: 98 | tool_call_copy = deepcopy(tool_call) 99 | tool_call_copy["args"]["user_id"] = user_id 100 | tool_calls.append(tool_call_copy) 101 | return tool_calls 102 | 103 | def test_inject_user_id(model_name,query): 104 | ai_msg = invoke_tool(model_name,query) 105 | new_args = inject_user_id.invoke(ai_msg) 106 | print(f'inject_user_id:\n{new_args}') 107 | 108 | 109 | tool_map = {tool.name: tool for tool in tools} 110 | 111 | @chain 112 | def tool_router(tool_call): 113 | return tool_map[tool_call["name"]] 114 | 115 | def execute_tool(model_name,query): 116 | """调用工具,返回结果""" 117 | 118 | llm = ChatOllama(model=model_name,temperature=0.1,verbose=True) 119 | llm_with_tools = llm.bind_tools(tools) 120 | 121 | # 将模型、注入用户ID代码和实际的工具链接在一起,创建工具执行链 122 | chain = llm_with_tools | inject_user_id | tool_router.map() 123 | 124 | result = chain.invoke(query) 125 | print(f'chain.invoke:\n{result}') 126 | print(f'now user_to_pets :\n{user_to_pets}') 127 | 128 | if __name__ == '__main__': 129 | 130 | test_tool() 131 | 132 | query = "刘大军最喜欢的动物是狗和蜥蜴。" 133 | invoke_tool('llama3.1',query) 134 | invoke_tool('MFDoom/deepseek-r1-tool-calling:7b',query) 135 | 136 | test_inject_user_id('llama3.1',query) 137 | test_inject_user_id('MFDoom/deepseek-r1-tool-calling:7b',query) 138 | 139 | execute_tool('llama3.1',query) 140 | execute_tool('MFDoom/deepseek-r1-tool-calling:7b',query) 141 | -------------------------------------------------------------------------------- /server/services/practice/14.tool_in_agent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-11 5 | # @function: 在Agent中使用tool 6 | # @version : V0.5 7 | # @Description : 8 | 9 | # https://python.langchain.com/docs/how_to/convert_runnable_to_tool/ 10 | 11 | """ 12 | 1.确定重要文件路径 13 | """ 14 | 15 | import os,sys 16 | 17 | # 将上级目录加入path,这样就可以引用上级目录的模块不会报错 18 | parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 19 | sys.path.append(parent_dir) 20 | 21 | # 当前文件的绝对路径 22 | current_file_path = os.path.abspath(__file__) 23 | 24 | # 当前文件所在的目录 25 | current_dir = os.path.dirname(current_file_path) 26 | 27 | # csv源文件地址 28 | src_file_path = os.path.join(current_dir,'assert/animals.csv') 29 | 30 | def get_persist_directory(model_name): 31 | """矢量数据库存储路径""" 32 | model_name = model_name.replace(":","-") 33 | return os.path.join(current_dir,f'assert/animals_{model_name}') 34 | 35 | """ 36 | 2.在本地生成嵌入数据库 37 | """ 38 | 39 | from common.MyVectorDB import LocalVectorDBChroma 40 | def create_db(model_name): 41 | """生成本地矢量数据库""" 42 | 43 | persist_directory = get_persist_directory(model_name) 44 | if os.path.exists(persist_directory): 45 | return 46 | 47 | db = LocalVectorDBChroma(model_name,persist_directory) 48 | db.embed_csv(src_file_path) 49 | 50 | """ 51 | 3.智能体 52 | """ 53 | 54 | from langchain_ollama import ChatOllama 55 | from langgraph.prebuilt import create_react_agent 56 | 57 | def ask_agent(embed_model_name,chat_modal_name,query): 58 | """测试智能体""" 59 | 60 | persist_directory = get_persist_directory(embed_model_name) 61 | db = LocalVectorDBChroma(embed_model_name,persist_directory) 62 | 63 | # 基于Chroma 的 vector store 生成 检索器 64 | vector_store = db.get_vector_store() 65 | retriever = vector_store.as_retriever( 66 | search_type="similarity", 67 | search_kwargs={"k": 2}, 68 | ) 69 | 70 | # 将 检索器 包装为 工具 71 | tools = [ 72 | retriever.as_tool( 73 | name="animal_info_retriever", 74 | description="查询动物的信息", 75 | ) 76 | ] 77 | 78 | llm = ChatOllama(model=chat_modal_name,temperature=0.1,verbose=True) 79 | agent = create_react_agent(llm, tools) 80 | 81 | # 显示智能体的详细内容 82 | for chunk in agent.stream({"messages": [("human", query)]}): 83 | print(chunk) 84 | print("----") 85 | 86 | def test_model(embed_model_name,chat_modal_name): 87 | print(f'\n---------------------{embed_model_name}-----------------------------') 88 | create_db(embed_model_name) 89 | 90 | query = "猪的学名是什么?它对人类有什么用处?" 91 | ask_agent(embed_model_name,chat_modal_name,query) 92 | 93 | query = "蜜蜂的特点是什么?它对人类社会有什么作用?" 94 | ask_agent(embed_model_name,chat_modal_name,query) 95 | 96 | if __name__ == '__main__': 97 | 98 | test_model("shaw/dmeta-embedding-zh","qwen2.5") 99 | test_model("milkey/m3e","qwen2.5") 100 | test_model("mxbai-embed-large","qwen2.5") 101 | 102 | test_model("nomic-embed-text","llama3.1") 103 | test_model("all-minilm:33m","llama3.1") 104 | 105 | test_model("llama3.1","llama3.1") 106 | test_model("qwen2.5","qwen2.5") 107 | -------------------------------------------------------------------------------- /server/services/practice/15_agent_executor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-13 5 | # @function: AI助理 6 | # @version : V0.5 7 | # @Description :智能的调用多款工具解决实际问题。 8 | 9 | # https://python.langchain.com/docs/how_to/agent_executor/ 10 | 11 | import os 12 | os.environ['USER_AGENT'] = 'agent_executor' 13 | 14 | """ 15 | 1. 查天气的工具 16 | """ 17 | from langchain_core.tools import tool 18 | 19 | @tool(parse_docstring=True) 20 | def get_wheather_info( 21 | city_name: str = '' #不设置默认值可能导致LLM强行解析city_name出错或者强行调用这个tool 22 | ) -> str: 23 | """获取某个城市的天气信息。如果没有可靠的依据来确定 city_name,则不要调用 get_wheather_info! 24 | 25 | Args: 26 | city_name: 城市名称。 27 | """ 28 | print(f'Getting weather information for:{city_name}') 29 | if not city_name: 30 | return "缺少 city_name 参数,无法检索天气信息。" 31 | """ 32 | **这个返回很重要** 33 | 返回错误后,agent会放弃这个结果,用自己的能力回答问题,这样结果更加合理; 34 | 否则,agent会使用空的city_name调用这个tool,并且会拼凑出new york的天气或者别的天气方面的信息。 35 | """ 36 | else: 37 | return f"{city_name}的气温是25摄氏度。" 38 | 39 | from langchain_ollama import ChatOllama 40 | from langchain_core.messages import HumanMessage 41 | 42 | def test_llm(model_name,query): 43 | """测试大模型能否聊天""" 44 | 45 | llm = ChatOllama(model=model_name,temperature=0,verbose=True) 46 | response = llm.invoke([HumanMessage(content=query)]) 47 | print(f'{model_name} answer:\n{response.content}') 48 | 49 | def test_get_wheather_info(llm_model_name,city_name): 50 | """测试获取天气信息""" 51 | 52 | print(f'--------{llm_model_name}----------') 53 | 54 | """ 55 | print(f'get_wheather_info schema:{get_wheather_info.get_input_schema().model_json_schema()}') 56 | print(f'get_wheather_info tool_call_schema:{get_wheather_info.tool_call_schema.model_json_schema()}') 57 | print(f'invoke get_wheather_info test:{get_wheather_info.invoke({"city_name": city_name})}') 58 | """ 59 | 60 | tools = [ 61 | get_wheather_info, 62 | ] 63 | 64 | llm = ChatOllama(model=llm_model_name,temperature=0,verbose=True) 65 | llm_with_tools = llm.bind_tools(tools) 66 | 67 | query = f'{city_name}的天气怎么样?' 68 | ai_msg = llm_with_tools.invoke(query) 69 | print(f'get_wheather_info tool_calls:\n{ai_msg.tool_calls}') 70 | 71 | 72 | """ 73 | 2. 确定重要文件路径 74 | """ 75 | 76 | import os,sys 77 | 78 | # 将上级目录加入path,这样就可以引用上级目录的模块不会报错 79 | parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 80 | sys.path.append(parent_dir) 81 | 82 | # 当前文件的绝对路径 83 | current_file_path = os.path.abspath(__file__) 84 | 85 | # 当前文件所在的目录 86 | current_dir = os.path.dirname(current_file_path) 87 | 88 | def get_persist_directory(model_name): 89 | """矢量数据库存储路径""" 90 | 91 | model_name = model_name.replace(":","-") 92 | return os.path.join(current_dir,f'assert/es_{model_name}') 93 | 94 | """ 95 | 3.在本地生成嵌入数据库 96 | 生成以后,反复测试时用起来比较方便。 97 | """ 98 | 99 | from common.MyVectorDB import LocalVectorDBChroma 100 | def create_db(model_name): 101 | """生成本地矢量数据库""" 102 | 103 | persist_directory = get_persist_directory(model_name) 104 | if os.path.exists(persist_directory): 105 | return 106 | 107 | db = LocalVectorDBChroma(model_name,persist_directory) 108 | db.embed_webpage("http://wfcoding.com/articles/programmer/p0102/") 109 | 110 | def test_search(embed_model_name,query): 111 | """查询矢量数据库""" 112 | 113 | persist_directory = get_persist_directory(embed_model_name) 114 | db = LocalVectorDBChroma(embed_model_name,persist_directory) 115 | vector_store = db.get_vector_store() 116 | 117 | results = vector_store.similarity_search_with_score(query,k=2) 118 | print(results) 119 | 120 | """ 121 | 4. 创建一个检索器 122 | """ 123 | 124 | def create_retriever(embed_model_name): 125 | """创建检索器""" 126 | 127 | persist_directory = get_persist_directory(embed_model_name) 128 | db = LocalVectorDBChroma(embed_model_name,persist_directory) 129 | 130 | # 基于Chroma 的 vector store 生成 检索器 131 | vector_store = db.get_vector_store() 132 | retriever = vector_store.as_retriever( 133 | search_type="similarity", 134 | search_kwargs={"k": 2}, 135 | ) 136 | return retriever 137 | 138 | """ 139 | 5. 创建工具集 tools 140 | """ 141 | 142 | from langchain.tools.retriever import create_retriever_tool 143 | 144 | def create_tools(embed_model_name): 145 | """创建工具集""" 146 | 147 | retriever_tool = create_retriever_tool( 148 | create_retriever(embed_model_name), 149 | "elastic_search", 150 | "只有当您搜索有关 elasticsearch 的知识时才能使用此工具!", 151 | ) 152 | 153 | tools = [get_wheather_info, retriever_tool] 154 | return tools 155 | 156 | 157 | def test_tools(llm_model_name,embed_model_name,queries): 158 | """测试工具集""" 159 | 160 | llm = ChatOllama(model=llm_model_name,temperature=0,verbose=True) 161 | tools = create_tools(embed_model_name) 162 | llm_with_tools = llm.bind_tools(tools) 163 | 164 | print(f'--------{llm_model_name}----------') 165 | 166 | for query in queries: 167 | response = llm_with_tools.invoke([HumanMessage(content=query)]) 168 | print(f"ContentString:\n {response.content}") 169 | print(f"ToolCalls: \n{response.tool_calls}") 170 | 171 | 172 | """ 173 | 6. 创建智能体 174 | """ 175 | 176 | from langchain_core.prompts import ChatPromptTemplate 177 | from langchain.agents import create_tool_calling_agent 178 | 179 | def create_agent(llm_model_name,embed_model_name): 180 | """创建智能体""" 181 | 182 | from langchain_core.tools import render_text_description 183 | 184 | tools = create_tools(embed_model_name) 185 | #rendered_tools = render_text_description(tools) 186 | #print(rendered_tools) 187 | 188 | # 此prompt是基于hwchase17/openai-functions-agent修改的 189 | systemprompt = """\ 190 | 您是一名助理,有权使用以下工具集。 191 | 下面是每个工具的名称和说明: 192 | 193 | [get_wheather_info, elastic_search] 194 | 195 | - **仅在需要时使用上述工具集!** 196 | - 如果没有可靠的依据来确定 city_name,则不要调用 get_wheather_info! 197 | """ 198 | prompt = ChatPromptTemplate([ 199 | ("system", systemprompt), 200 | ("placeholder", "{chat_history}"), 201 | ("human", "{input}"), 202 | ("placeholder", "{agent_scratchpad}"), 203 | ]) 204 | 205 | llm = ChatOllama(model=llm_model_name,temperature=0,verbose=True) 206 | agent = create_tool_calling_agent(llm, tools, prompt) 207 | return agent 208 | 209 | from langchain.agents import AgentExecutor 210 | 211 | def create_agent_executor(llm_model_name,embed_model_name): 212 | """创建agent_executor""" 213 | 214 | tools = create_tools(embed_model_name) 215 | agent = create_agent(llm_model_name,embed_model_name) 216 | 217 | agent_executor = AgentExecutor(agent=agent, tools=tools) 218 | """实际上在create_agent中已经创建了tools,这里还要再传入tools,似乎有点多余。""" 219 | 220 | return agent_executor 221 | 222 | def test_agent_executor(llm_model_name,embed_model_name,queries): 223 | """测试AgentExecutor""" 224 | 225 | print(f'--------{llm_model_name}----------') 226 | 227 | agent_executor = create_agent_executor(llm_model_name,embed_model_name) 228 | 229 | for query in queries: 230 | r = agent_executor.invoke({"input": query}) 231 | print(f'agent_executor.invoke:\n{r}') 232 | 233 | 234 | def test(llm_model_name,embed_model_name): 235 | """集中测试方法""" 236 | 237 | create_db(embed_model_name) 238 | 239 | query = "如何实现elasticsearch的深度分页?" 240 | test_search(embed_model_name,query) 241 | 242 | query = "你好,你擅长能做什么?" 243 | 244 | test_llm(llm_model_name,query) 245 | test_get_wheather_info(llm_model_name,"深圳") 246 | 247 | queries = ["你好,你擅长能做什么?","上海的天气怎么样?","如何实现elasticsearch的深度分页?"] 248 | 249 | test_tools(llm_model_name,embed_model_name,queries) 250 | test_agent_executor(llm_model_name,embed_model_name,queries) 251 | 252 | if __name__ == '__main__': 253 | 254 | 255 | test_get_wheather_info("qwen2.5","北京") 256 | test_get_wheather_info("llama3.1","北京") 257 | test_get_wheather_info("MFDoom/deepseek-r1-tool-calling:7b","北京") 258 | 259 | 260 | queries = ["你好,你擅长能做什么?","上海的天气怎么样?","如何实现elasticsearch的深度分页?"] 261 | test_tools("qwen2.5","shaw/dmeta-embedding-zh",queries) 262 | test_tools("llama3.1","shaw/dmeta-embedding-zh",queries) 263 | test_tools("MFDoom/deepseek-r1-tool-calling:7b","shaw/dmeta-embedding-zh",queries) 264 | 265 | test_agent_executor("qwen2.5","shaw/dmeta-embedding-zh",queries) 266 | test_agent_executor("llama3.1","shaw/dmeta-embedding-zh",queries) 267 | test_agent_executor("MFDoom/deepseek-r1-tool-calling:7b","shaw/dmeta-embedding-zh",queries) 268 | 269 | test("qwen2.5","shaw/dmeta-embedding-zh") 270 | test("llama3.1","shaw/dmeta-embedding-zh") 271 | test("MFDoom/deepseek-r1-tool-calling:7b","shaw/dmeta-embedding-zh") -------------------------------------------------------------------------------- /server/services/practice/17.tool_in_RAG.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-11 5 | # @function: 使用tool的简单RAG 6 | # @version : V0.5 7 | # @Description :去掉answer_style后回答比较好,有这个参数时回答很离谱。 8 | 9 | # https://python.langchain.com/docs/how_to/convert_runnable_to_tool/ 10 | 11 | """ 12 | 确定文件路径 13 | """ 14 | 15 | import os,sys 16 | 17 | # 将上级目录加入path,这样就可以引用上级目录的模块不会报错 18 | parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 19 | sys.path.append(parent_dir) 20 | 21 | # 当前文件的绝对路径 22 | current_file_path = os.path.abspath(__file__) 23 | 24 | # 当前文件所在的目录 25 | current_dir = os.path.dirname(current_file_path) 26 | 27 | def get_persist_directory(model_name): 28 | """矢量数据库存储路径""" 29 | model_name = model_name.replace(":","-") 30 | return os.path.join(current_dir,f'assert/animals_{model_name}') 31 | 32 | """ 33 | 1. 创建检索器 34 | """ 35 | from common.MyVectorDB import LocalVectorDBChroma 36 | def create_retriever(embed_model_name): 37 | """创建检索器""" 38 | 39 | persist_directory = get_persist_directory(embed_model_name) 40 | db = LocalVectorDBChroma(embed_model_name,persist_directory) 41 | 42 | # 基于Chroma 的 vector store 生成 检索器 43 | vector_store = db.get_vector_store() 44 | retriever = vector_store.as_retriever( 45 | search_type="similarity", 46 | search_kwargs={"k": 1}, 47 | ) 48 | return retriever 49 | 50 | embed_model_name = "shaw/dmeta-embedding-zh" 51 | retriever = create_retriever(embed_model_name) 52 | 53 | def search(query): 54 | """查询矢量数据库""" 55 | 56 | persist_directory = get_persist_directory(embed_model_name) 57 | db = LocalVectorDBChroma(embed_model_name,persist_directory) 58 | vector_store = db.get_vector_store() 59 | 60 | results = vector_store.similarity_search_with_score(query,k=1) 61 | return results 62 | 63 | """ 64 | 2. 设置提示词 65 | """ 66 | 67 | from langchain_ollama import ChatOllama 68 | from operator import itemgetter 69 | from langchain_core.output_parsers import StrOutputParser 70 | from langchain_core.prompts import ChatPromptTemplate 71 | 72 | system_prompt = """ 73 | 您是问答任务的助手。 74 | 请使用以下**上下文**回答问题。如果您不知道答案,就说您不知道。 75 | 最多使用三句话并保持答案简洁。 76 | 77 | 下面请回答问题。 78 | 79 | 问题: {question} 80 | 81 | 上下文: {context} 82 | """ 83 | 84 | prompt = ChatPromptTemplate.from_messages([("system", system_prompt)]) 85 | 86 | """ 87 | 3. RAG 链 88 | """ 89 | 90 | def create_rag_chain(llm_model_name): 91 | """创建RAG链""" 92 | 93 | llm = ChatOllama(model=llm_model_name,temperature=0,verbose=True) 94 | rag_chain = ( 95 | { 96 | "context": itemgetter("question") | retriever, 97 | "question": itemgetter("question") 98 | } 99 | | prompt 100 | | llm 101 | | StrOutputParser() 102 | ) 103 | 104 | print(f'json_schema:{rag_chain.input_schema.model_json_schema()}') 105 | 106 | return rag_chain 107 | 108 | 109 | def test_rag_chain(llm_model_name,question): 110 | """测试 rag 链""" 111 | 112 | print(f"------------{llm_model_name}-----------") 113 | rag_chain = create_rag_chain(llm_model_name) 114 | 115 | res = rag_chain.invoke({"question":question}) 116 | print(res) 117 | 118 | def test_rag_chain_stream(llm_model_name,question): 119 | """测试 rag 链,流式输出""" 120 | 121 | print(f"------------{llm_model_name}-----------") 122 | rag_chain = create_rag_chain(llm_model_name) 123 | for chunk in rag_chain.stream({"question":question}): 124 | print(chunk,end="-") 125 | 126 | """ 127 | 4. 智能体 128 | """ 129 | from langgraph.prebuilt import create_react_agent 130 | 131 | def create_agent(llm_model_name): 132 | """生成智能体""" 133 | 134 | rag_chain = create_rag_chain(llm_model_name) 135 | rag_tool = rag_chain.as_tool( 136 | name="animal_expert", 137 | description="获取有关动物的信息。", 138 | ) 139 | 140 | llm = ChatOllama(model=llm_model_name,temperature=0,verbose=True) 141 | agent = create_react_agent(llm, [rag_tool]) 142 | 143 | return agent 144 | 145 | def test_agent(llm_model_name,question): 146 | 147 | agent = create_agent(llm_model_name) 148 | 149 | for chunk in agent.stream( 150 | {"messages": [("human",question)]} 151 | ): 152 | print(chunk) 153 | print("----") 154 | 155 | if __name__ == '__main__': 156 | 157 | query = "猪的学名是什么?它对人类有什么用处?" 158 | ''' 159 | test_rag_chain("qwen2.5",query) 160 | test_rag_chain_stream("qwen2.5",query) 161 | 162 | test_rag_chain("deepseek-r1",query) 163 | test_rag_chain_stream("deepseek-r1",query) 164 | 165 | test_rag_chain("llama3.1",query) 166 | test_rag_chain_stream("llama3.1",query) 167 | ''' 168 | 169 | query = "蜜蜂的特点是什么?它对人类社会有什么作用?" 170 | test_agent("qwen2.5",query) 171 | test_agent("llama3.1",query) 172 | #test_agent("deepseek-r1",query) # 不支持stream 173 | -------------------------------------------------------------------------------- /server/services/practice/18_rag_graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-17 5 | # @function: 用langgraph实现的rag 6 | # @version : V0.5 7 | 8 | # https://python.langchain.com/docs/tutorials/rag/ 9 | 10 | import os 11 | os.environ['USER_AGENT'] = 'rag_graph' 12 | 13 | """ 14 | 确定文件路径 15 | """ 16 | 17 | import sys 18 | 19 | # 当前文件的绝对路径 20 | current_file_path = os.path.abspath(__file__) 21 | 22 | # 当前文件所在的目录 23 | current_dir = os.path.dirname(current_file_path) 24 | 25 | def get_persist_directory(model_name): 26 | """矢量数据库存储路径""" 27 | model_name = model_name.replace(":","-") 28 | return os.path.join(current_dir,f'assert/animals_{model_name}') 29 | 30 | """ 31 | 1. 创建矢量数据库对象 32 | """ 33 | 34 | from langchain_chroma import Chroma 35 | from langchain_ollama import OllamaEmbeddings 36 | 37 | embed_model_name = "shaw/dmeta-embedding-zh" 38 | vector_store = Chroma(persist_directory=get_persist_directory(embed_model_name),embedding_function=OllamaEmbeddings(model=embed_model_name)) 39 | 40 | """ 41 | 2. 设置提示词 42 | """ 43 | 44 | from langchain_core.documents import Document 45 | from langgraph.graph import START, StateGraph 46 | from typing_extensions import List, TypedDict 47 | 48 | #prompt = hub.pull("rlm/rag-prompt") 49 | from langchain_core.prompts import ChatPromptTemplate 50 | 51 | prompt = ChatPromptTemplate.from_messages([ 52 | ("human", """你是问答任务的助手。 53 | 请使用以下检索到的**上下文**来回答问题。 54 | 如果你不知道答案,就说你不知道。最多使用三句话,并保持答案简洁。 55 | 56 | 问题: {question} 57 | 58 | 上下文: {context} 59 | 60 | 回答:"""), 61 | ]) 62 | 63 | """ 64 | 3. 使用langgraph构建 RAG 系统 65 | """ 66 | 67 | class State(TypedDict): 68 | """状态:在 langgraph 中传递""" 69 | 70 | question: str 71 | context: List[Document] 72 | answer: str 73 | 74 | def retrieve(state: State): 75 | """节点:检索""" 76 | 77 | retrieved_docs = vector_store.similarity_search(state["question"],k=2) 78 | return {"context": retrieved_docs} 79 | 80 | from langchain_ollama import ChatOllama 81 | 82 | def build_graph(llm_model_name): 83 | """构建langgraph""" 84 | 85 | def generate(state: State): 86 | """节点:生成 """ 87 | 88 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 89 | docs_content = "\n\n".join(doc.page_content for doc in state["context"]) 90 | messages = prompt.invoke({"question": state["question"], "context": docs_content}) 91 | response = llm.invoke(messages) 92 | return {"answer": response.content} 93 | 94 | # 定义步骤 95 | graph_builder = StateGraph(State).add_sequence([retrieve, generate]) 96 | graph_builder.add_edge(START, "retrieve") 97 | graph = graph_builder.compile() 98 | 99 | return graph 100 | 101 | def ask(llm_model_name,question): 102 | """提问""" 103 | 104 | print(f'--------{llm_model_name}----------') 105 | graph = build_graph(llm_model_name) 106 | response = graph.invoke({"question": question}) 107 | print(f'the answer is: \n{response["answer"]}') 108 | 109 | if __name__ == '__main__': 110 | 111 | graph = build_graph("qwen2.5") 112 | 113 | from utils import show_graph 114 | show_graph(graph) 115 | 116 | question = "大象的学名是什么?它有什么显著特点?对人类有什么帮助?" 117 | ask("qwen2.5",question) 118 | ask("deepseek-r1",question) 119 | ask("llama3.1",question) 120 | 121 | -------------------------------------------------------------------------------- /server/services/practice/19_rag_graph_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-19 5 | # @function: 用langgraph实现的rag 6 | # @version : V0.5 7 | 8 | # https://python.langchain.com/docs/tutorials/qa_chat_history/ 9 | 10 | import os 11 | os.environ['USER_AGENT'] = 'rag_graph_2' 12 | 13 | """ 14 | 确定文件路径 15 | """ 16 | 17 | import sys 18 | 19 | # 当前文件的绝对路径 20 | current_file_path = os.path.abspath(__file__) 21 | 22 | # 当前文件所在的目录 23 | current_dir = os.path.dirname(current_file_path) 24 | 25 | def get_persist_directory(model_name): 26 | """矢量数据库存储路径""" 27 | model_name = model_name.replace(":","-") 28 | return os.path.join(current_dir,f'assert/animals_{model_name}') 29 | 30 | """ 31 | 1. 创建矢量数据库对象 32 | """ 33 | 34 | from langchain_chroma import Chroma 35 | from langchain_ollama import OllamaEmbeddings 36 | 37 | embed_model_name = "shaw/dmeta-embedding-zh" 38 | vector_store = Chroma(persist_directory=get_persist_directory(embed_model_name),embedding_function=OllamaEmbeddings(model=embed_model_name)) 39 | 40 | """ 41 | 2. 实现Langgraph 链 42 | """ 43 | 44 | from langchain_core.tools import tool 45 | 46 | @tool(response_format="content_and_artifact",parse_docstring=True) # docstring的内容对agent自动推理影响比较大 47 | def retrieve(query: str): 48 | """检索与 query参数内容 相关的信息 49 | 50 | Args: 51 | query: 要搜索的字符串。 52 | """ 53 | 54 | print(f"start retrieve:{query}") 55 | 56 | # 定义相似度阈值。因为这种相似性检索并不考虑相似性大小,如果不限制可能会返回相似性不大的文档, 可能会影响问答效果。 57 | similarity_threshold = 0.8 58 | retrieved_docs = vector_store.similarity_search_with_score(query, k=3) 59 | 60 | # 根据相似度分数过滤结果 61 | filtered_docs = [ 62 | doc for doc, score in retrieved_docs if score <= similarity_threshold 63 | ] 64 | 65 | serialized = "\n\n".join( 66 | (f"Source: {doc.metadata}\n" f"Content: {doc.page_content}") 67 | for doc in filtered_docs 68 | ) 69 | 70 | if not serialized: 71 | return "抱歉,我找不到任何相关信息。", None 72 | else: 73 | return serialized, filtered_docs 74 | 75 | 76 | from langchain_ollama import ChatOllama 77 | from langchain_core.messages import SystemMessage 78 | 79 | from langgraph.graph import MessagesState, StateGraph 80 | from langgraph.graph import END 81 | from langgraph.prebuilt import ToolNode, tools_condition 82 | 83 | def build_graph(llm_model_name): 84 | """构建 langgraph 链""" 85 | 86 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 87 | 88 | # 1: 生成可能包含工具调用(tool_call)的 AIMessage。 89 | def query_or_respond(state: MessagesState): 90 | """生成用于检索或响应的工具调用。""" 91 | 92 | llm_with_tools = llm.bind_tools([retrieve]) 93 | response = llm_with_tools.invoke(state["messages"]) 94 | """ 95 | 这里会自动进行指代消解:根据上下文自动修改问题,把问题中的代词替换成上下文中的内容 96 | """ 97 | # MessagesState 将消息附加到 state 而不是覆盖 98 | return {"messages": [response]} 99 | 100 | # 2: 执行检索 101 | tools = ToolNode([retrieve]) 102 | 103 | # 3: 使用检索到的内容生成响应。 104 | def generate(state: MessagesState): 105 | """生成回答。""" 106 | 107 | # 获取生成的 ToolMessages 108 | recent_tool_messages = [] 109 | for message in reversed(state["messages"]): 110 | if message.type == "tool": 111 | recent_tool_messages.append(message) 112 | else: 113 | break 114 | tool_messages = recent_tool_messages[::-1] 115 | 116 | # 获取 ToolMessages 的内容,并格式化为提示词 117 | docs_content = "\n\n".join(doc.content for doc in tool_messages) 118 | system_message_content = ( 119 | "你是一个负责答疑任务的助手。 " 120 | "使用以下检索到的上下文来回答问题。 " 121 | "如果你不知道答案,就说你不知道。 " 122 | "最多使用三句话并保持答案简洁。 " 123 | "\n\n" 124 | f"{docs_content}" 125 | ) 126 | conversation_messages = [ 127 | message 128 | for message in state["messages"] 129 | if message.type in ("human", "system") 130 | or (message.type == "ai" and not message.tool_calls) 131 | ] 132 | prompt = [SystemMessage(system_message_content)] + conversation_messages 133 | 134 | # 执行 135 | response = llm.invoke(prompt) 136 | # MessagesState 将消息附加到 state 而不是覆盖 137 | return {"messages": [response]} 138 | 139 | # 串联节点和边,构建图 140 | graph_builder = StateGraph(MessagesState) 141 | 142 | graph_builder.add_node(query_or_respond) 143 | graph_builder.add_node(tools) 144 | graph_builder.add_node(generate) 145 | 146 | graph_builder.set_entry_point("query_or_respond") 147 | graph_builder.add_conditional_edges( 148 | "query_or_respond", 149 | tools_condition, 150 | {END: END, "tools": "tools"}, 151 | ) 152 | graph_builder.add_edge("tools", "generate") 153 | graph_builder.add_edge("generate", END) 154 | 155 | graph = graph_builder.compile() 156 | return graph 157 | 158 | def ask(llm_model_name,question): 159 | """提问""" 160 | 161 | graph = build_graph(llm_model_name) 162 | for step in graph.stream( 163 | {"messages": [{"role": "user", "content": question}]}, 164 | stream_mode="values", 165 | ): 166 | step["messages"][-1].pretty_print() 167 | 168 | if __name__ == '__main__': 169 | 170 | graph = build_graph("qwen2.5") 171 | 172 | from utils import show_graph 173 | show_graph(graph) 174 | 175 | query1 = "马的学名是什么?它有什么用途?" 176 | query2 = "中国有多少个省份?" 177 | 178 | ask("qwen2.5",query1) 179 | ask("qwen2.5",query2) 180 | ask("llama3.1",query1) 181 | ask("llama3.1",query2) 182 | ask("MFDoom/deepseek-r1-tool-calling:7b",query1) 183 | ask("MFDoom/deepseek-r1-tool-calling:7b",query2) -------------------------------------------------------------------------------- /server/services/practice/20_rag_graph_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-19 5 | # @function: 用langgraph实现的rag 6 | # @version : V0.5 7 | 8 | # https://python.langchain.com/docs/tutorials/qa_chat_history/ 9 | 10 | import os 11 | os.environ['USER_AGENT'] = 'rag_graph_2' 12 | 13 | """ 14 | 确定文件路径 15 | """ 16 | 17 | import sys 18 | 19 | # 当前文件的绝对路径 20 | current_file_path = os.path.abspath(__file__) 21 | 22 | # 当前文件所在的目录 23 | current_dir = os.path.dirname(current_file_path) 24 | 25 | def get_persist_directory(model_name): 26 | """矢量数据库存储路径""" 27 | model_name = model_name.replace(":","-") 28 | return os.path.join(current_dir,f'assert/animals_{model_name}') 29 | 30 | """ 31 | 1. 创建矢量数据库对象 32 | """ 33 | 34 | from langchain_chroma import Chroma 35 | from langchain_ollama import OllamaEmbeddings 36 | 37 | embed_model_name = "shaw/dmeta-embedding-zh" 38 | vector_store = Chroma(persist_directory=get_persist_directory(embed_model_name),embedding_function=OllamaEmbeddings(model=embed_model_name)) 39 | 40 | """ 41 | 2. 实现Langgraph 链 42 | """ 43 | 44 | from langchain_core.tools import tool 45 | 46 | @tool(response_format="content_and_artifact",parse_docstring=True) # docstring的内容对agent自动推理影响比较大 47 | def retrieve(query: str): 48 | """检索与 query参数内容 相关的信息 49 | 50 | Args: 51 | query: 要搜索的字符串。 52 | """ 53 | 54 | print(f"start retrieve:{query}") 55 | 56 | # 定义相似度阈值。因为这种相似性检索并不考虑相似性大小,如果不限制可能会返回相似性不大的文档, 可能会影响问答效果。 57 | similarity_threshold = 0.6 58 | retrieved_docs = vector_store.similarity_search_with_score(query, k=3) 59 | 60 | # 根据相似度分数过滤结果 61 | filtered_docs = [ 62 | doc for doc, score in retrieved_docs if score <= similarity_threshold 63 | ] 64 | 65 | serialized = "\n\n".join( 66 | (f"Source: {doc.metadata}\n" f"Content: {doc.page_content}") 67 | for doc in filtered_docs 68 | ) 69 | 70 | if not serialized: 71 | return "抱歉,我找不到任何相关信息。", None 72 | else: 73 | return serialized, filtered_docs 74 | 75 | 76 | from langchain_ollama import ChatOllama 77 | from langchain_core.messages import SystemMessage 78 | 79 | from langgraph.graph import MessagesState, StateGraph,END 80 | from langgraph.prebuilt import ToolNode, tools_condition 81 | from langgraph.checkpoint.memory import MemorySaver 82 | 83 | def build_graph_with_memory(llm_model_name): 84 | """构建 langgraph 链""" 85 | 86 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 87 | 88 | # 1: 生成可能包含要发送的工具调用的 AIMessage。 89 | def query_or_respond(state: MessagesState): 90 | """生成用于检索或响应的工具调用。""" 91 | 92 | llm_with_tools = llm.bind_tools([retrieve]) 93 | response = llm_with_tools.invoke(state["messages"]) 94 | """ 95 | 这里会自动进行指代消解:根据上下文自动修改问题,把问题中的代词替换成上下文中的内容 96 | """ 97 | # MessagesState 将消息附加到 state 而不是覆盖 98 | return {"messages": [response]} 99 | 100 | # 2: 执行检索 101 | tools = ToolNode([retrieve]) 102 | 103 | # 3: 使用检索到的内容生成响应。 104 | def generate(state: MessagesState): 105 | """生成回答。""" 106 | 107 | # 获取生成的 ToolMessages 108 | recent_tool_messages = [] 109 | for message in reversed(state["messages"]): 110 | if message.type == "tool": 111 | recent_tool_messages.append(message) 112 | else: 113 | break 114 | tool_messages = recent_tool_messages[::-1] 115 | 116 | # 获取 ToolMessages 的内容,并格式化为提示词 117 | docs_content = "\n\n".join(doc.content for doc in tool_messages) 118 | system_message_content = ( 119 | "你是一个负责答疑任务的助手。 " 120 | "使用以下检索到的上下文来回答问题。 " 121 | "如果你不知道答案,就说你不知道。 " 122 | "最多使用三句话并保持答案简洁。 " 123 | "\n\n" 124 | f"{docs_content}" 125 | ) 126 | conversation_messages = [ 127 | message 128 | for message in state["messages"] 129 | if message.type in ("human", "system") 130 | or (message.type == "ai" and not message.tool_calls) 131 | ] 132 | prompt = [SystemMessage(system_message_content)] + conversation_messages 133 | 134 | # 执行 135 | response = llm.invoke(prompt) 136 | # MessagesState 将消息附加到 state 而不是覆盖 137 | return {"messages": [response]} 138 | 139 | # 4: 串联节点和边,构建图 140 | graph_builder = StateGraph(MessagesState) 141 | 142 | graph_builder.add_node(query_or_respond) 143 | graph_builder.add_node(tools) 144 | graph_builder.add_node(generate) 145 | 146 | graph_builder.set_entry_point("query_or_respond") 147 | graph_builder.add_conditional_edges( 148 | "query_or_respond", 149 | tools_condition, 150 | {END: END, "tools": "tools"}, 151 | ) 152 | graph_builder.add_edge("tools", "generate") 153 | graph_builder.add_edge("generate", END) 154 | 155 | graph = graph_builder.compile() 156 | 157 | # 增加记忆功能 158 | memory = MemorySaver() 159 | graph = graph_builder.compile(checkpointer=memory) 160 | 161 | return graph 162 | 163 | 164 | def ask_with_history(graph,thread_id,question): 165 | """提问,记录聊天历史""" 166 | 167 | print('---ask_with_history---') 168 | conf = {"configurable": {"thread_id": thread_id}} 169 | for step in graph.stream( 170 | {"messages": [{"role": "user", "content": question}]}, 171 | stream_mode="values", 172 | config = conf, 173 | ): 174 | step["messages"][-1].pretty_print() 175 | 176 | 177 | """ 178 | 3. 智能体 179 | """ 180 | 181 | """ 182 | 智能体利用 LLM 的推理能力在执行过程中做出决策。使用代理可以让您在检索过程中减轻额外的判断力。 183 | 虽然它们的行为比上述“链”更难预测,但它们能够执行多个检索步骤来处理查询,或者在单个搜索中进行迭代。 184 | """ 185 | 186 | from langgraph.prebuilt import create_react_agent 187 | 188 | def create_agent(llm_model_name): 189 | """创建智能体""" 190 | 191 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 192 | memory = MemorySaver() 193 | agent_executor = create_react_agent(llm, tools=[retrieve], checkpointer=memory) 194 | return agent_executor 195 | 196 | 197 | def ask_agent(agent,thread_id,question): 198 | """咨询智能体""" 199 | 200 | print('---ask_agent---') 201 | conf = {"configurable": {"thread_id": thread_id}} 202 | for step in agent.stream( 203 | {"messages": [{"role": "user", "content": question}]}, 204 | stream_mode="values", 205 | config=conf, 206 | ): 207 | step["messages"][-1].pretty_print() 208 | 209 | def show_graph(): 210 | """图形化显示链和智能体结构""" 211 | 212 | from utils import show_graph 213 | 214 | graph = build_graph_with_memory("qwen2.5") 215 | show_graph(graph) 216 | 217 | agent = create_agent("qwen2.5") 218 | show_graph(agent) 219 | 220 | 221 | def test_model(llm_model_name): 222 | """测试大语言模型""" 223 | 224 | print(f'------{llm_model_name}------') 225 | 226 | question1 = "羊的学名是什么?" 227 | question2 = "它有什么特点?" 228 | thread_id = "liu2233" 229 | 230 | graph = build_graph_with_memory(llm_model_name) 231 | ask_with_history(graph,thread_id,question1) 232 | ask_with_history(graph,thread_id,question2) 233 | 234 | agent = create_agent(llm_model_name) 235 | ask_agent(agent,thread_id,question1) 236 | ask_agent(agent,thread_id,question2) 237 | 238 | if __name__ == '__main__': 239 | 240 | test_model('qwen2.5') 241 | test_model('llama3.1') 242 | test_model("MFDoom/deepseek-r1-tool-calling:7b") 243 | 244 | show_graph() -------------------------------------------------------------------------------- /server/services/practice/21_rag_graph_with_query_analysis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-17 5 | # @function: 用langgraph实现的有Query analysis的rag 6 | # @version : V0.5 7 | 8 | # https://python.langchain.com/docs/tutorials/rag/ 9 | 10 | import os 11 | os.environ['USER_AGENT'] = 'rag_graph_with_query_analysis' 12 | 13 | """ 14 | 确定重要文件路径 15 | """ 16 | 17 | import sys 18 | 19 | # 当前文件的绝对路径 20 | current_file_path = os.path.abspath(__file__) 21 | 22 | # 当前文件所在的目录 23 | current_dir = os.path.dirname(current_file_path) 24 | 25 | # 待矢量化的源文件地址 26 | src_url = os.path.join("http://wfcoding.com/articles/practice/0318/") 27 | 28 | def get_persist_directory(model_name): 29 | """矢量数据库存储路径""" 30 | model_name = model_name.replace(":","-") 31 | return os.path.join(current_dir,f'assert/rag_{model_name}') 32 | 33 | """ 34 | 1. 创建本地嵌入数据库 35 | """ 36 | 37 | embed_model_name = "shaw/dmeta-embedding-zh" 38 | batch_size = 3 39 | 40 | import bs4 41 | from langchain_community.document_loaders import WebBaseLoader 42 | from langchain_core.documents import Document 43 | from langchain_text_splitters import RecursiveCharacterTextSplitter 44 | 45 | from tqdm import tqdm 46 | from langchain_chroma import Chroma 47 | from langchain_ollama import OllamaEmbeddings 48 | 49 | def create_db(model_name,url): 50 | """生成本地矢量数据库""" 51 | 52 | persist_directory = get_persist_directory(model_name) 53 | 54 | # 判断矢量数据库是否存在,如果存在则不再做索引,方便反复测试 55 | if os.path.exists(persist_directory): 56 | return 57 | 58 | embedding = OllamaEmbeddings(model=model_name) 59 | vectordb = Chroma(persist_directory=persist_directory,embedding_function=embedding) 60 | 61 | # 加载并分块博客内容 62 | loader = WebBaseLoader( 63 | web_paths=(url,), 64 | bs_kwargs=dict( 65 | parse_only=bs4.SoupStrainer( 66 | class_=("post-header","post-content") # 指解析css class 为post-header和post-content 的内容 67 | ) 68 | ), 69 | ) 70 | docs = loader.load() 71 | 72 | text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) 73 | all_splits = text_splitter.split_documents(docs) 74 | 75 | # 在文档的 元数据(metadata) 中添加 section 标签 76 | 77 | total_documents = len(all_splits) 78 | third = total_documents // 3 79 | 80 | for i, document in enumerate(all_splits): 81 | if i < third: 82 | document.metadata["section"] = "开头" 83 | elif i < 2 * third: 84 | document.metadata["section"] = "中间" 85 | else: 86 | document.metadata["section"] = "结尾" 87 | 88 | print(f'Metadata: {all_splits[0].metadata}') 89 | 90 | 91 | for i in tqdm(range(0, len(all_splits), batch_size), desc="嵌入进度"): 92 | batch = all_splits[i:i + batch_size] 93 | 94 | # 从文本块生成嵌入,并将嵌入存储在本地磁盘。 95 | vectordb.add_documents(batch) 96 | 97 | 98 | create_db(embed_model_name,src_url) 99 | vector_store = Chroma(persist_directory=get_persist_directory(embed_model_name),embedding_function=OllamaEmbeddings(model=embed_model_name)) 100 | 101 | def similarity_search_with_score(state): 102 | """矢量数据库检索测试 103 | 返回文档评分,分数越高,文档越相似。 104 | """ 105 | 106 | results = vector_store.similarity_search_with_score( 107 | state["query"], 108 | k = 2, 109 | filter={"section": state["section"]}, 110 | ) 111 | return results 112 | 113 | """ 114 | 2. 检索和生成回答 115 | """ 116 | 117 | from typing_extensions import List, TypedDict, Annotated 118 | 119 | class Search(TypedDict): 120 | """查询检索的参数""" 121 | 122 | query: Annotated[str, ..., "查询的关键词"] 123 | section: Annotated[str, ..., "要查询的部分,必须是'开头、中间、结尾'之一。"] 124 | 125 | class State(TypedDict): 126 | question: str 127 | query: Search 128 | context: List[Document] 129 | answer: str 130 | 131 | def retrieve(state: State): 132 | """检索""" 133 | 134 | query = state["query"] 135 | retrieved_docs = vector_store.similarity_search( 136 | query["query"], 137 | filter={"section": query["section"]}, 138 | ) 139 | return {"context": retrieved_docs} 140 | 141 | # 定义提示词 142 | #prompt = hub.pull("rlm/rag-prompt") 143 | from langchain_core.prompts import ChatPromptTemplate 144 | from langchain_ollama import ChatOllama 145 | from langgraph.graph import START, StateGraph 146 | 147 | def create_graph(llm_model_name): 148 | """创建langgraph""" 149 | 150 | llm = ChatOllama(model=llm_model_name,temperature=0,verbose=True) 151 | 152 | def analyze_query(state: State): 153 | """分析查询,推理出查询参数""" 154 | 155 | structured_llm = llm.with_structured_output(Search) 156 | query = structured_llm.invoke(state["question"]) 157 | return {"query": query} 158 | 159 | def generate(state: State): 160 | """生成回答""" 161 | 162 | prompt = ChatPromptTemplate.from_messages([ 163 | ("human", """你是问答任务的助手。 164 | 请使用以下检索到的**上下文**来回答问题。 165 | 如果你不知道答案,就说你不知道。最多使用三句话,并保持答案简洁。 166 | 167 | 问题: {question} 168 | 169 | 上下文: {context} 170 | 171 | 回答:"""), 172 | ]) 173 | 174 | docs_content = "\n\n".join(doc.page_content for doc in state["context"]) 175 | messages = prompt.invoke({"question": state["question"], "context": docs_content}) 176 | response = llm.invoke(messages) 177 | return {"answer": response.content} 178 | 179 | graph_builder = StateGraph(State).add_sequence([analyze_query, retrieve, generate]) 180 | graph_builder.add_edge(START, "analyze_query") 181 | graph = graph_builder.compile() 182 | 183 | return graph 184 | 185 | 186 | def ask(llm_model_name,question): 187 | """问答""" 188 | 189 | graph = create_graph(llm_model_name) 190 | for step in graph.stream( 191 | {"question": question}, 192 | stream_mode="updates", 193 | ): 194 | print(f"{step}\n\n----------------\n") 195 | 196 | 197 | if __name__ == '__main__': 198 | 199 | similarity_search_with_score({"query":"langgraph 结尾","section":"结尾"}) 200 | 201 | from utils import show_graph 202 | graph = create_graph("llama3.1") 203 | show_graph(graph) 204 | 205 | q = "文章的结尾讲了langgraph的哪些优点?" 206 | ask("qwen2.5",q) 207 | ask("llama3.1",q) 208 | ask("MFDoom/deepseek-r1-tool-calling:7b",q) 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /server/services/practice/22_qa_sql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-19 5 | # @function: sql问答 6 | # @version : V0.5 7 | # @Description :sql问答。 8 | 9 | # https://python.langchain.com/v0.2/docs/tutorials/sql_qa/ 10 | # https://sqlitestudio.pl/ 11 | 12 | """ 13 | 确定文件位置 14 | """ 15 | import os 16 | 17 | # 获取当前执行的程序文件的文件夹路径 18 | current_folder = os.path.dirname(os.path.abspath(__file__)) 19 | 20 | db_file_path = os.path.join(current_folder, 'assert/Chinook.db') 21 | 22 | """ 23 | 1. 测试数据库 24 | """ 25 | 26 | from langchain_community.utilities import SQLDatabase 27 | 28 | db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}") 29 | 30 | def test_db(): 31 | """测试数据库""" 32 | 33 | print(db.dialect) 34 | print(db.get_usable_table_names()) 35 | #print(db.get_table_info()) 36 | print(db.run("SELECT * FROM Artist LIMIT 1;")) 37 | 38 | """ 39 | 2. 执行SQL 40 | """ 41 | from langchain_ollama import ChatOllama 42 | from langchain.chains import create_sql_query_chain 43 | 44 | def execute_query(llm_model_name,question: str): 45 | """把问题转换为SQL语句并执行""" 46 | 47 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 48 | chain = create_sql_query_chain(llm, db) 49 | print(chain.get_prompts()[0].pretty_print()) 50 | 51 | # 转化问题为SQL 52 | response = chain.invoke({"question": question}) 53 | print(f'response SQL is:\n{response}') 54 | 55 | # 执行SQL 56 | result = db.run(response) 57 | print(f'result is:\n{result}') 58 | 59 | from langchain_community.tools import QuerySQLDataBaseTool 60 | 61 | def execute_query_2(llm_model_name,question: str): 62 | """把问题转换为SQL语句并执行""" 63 | 64 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 65 | execute_query = QuerySQLDataBaseTool(db=db) 66 | write_query = create_sql_query_chain(llm, db) 67 | chain = write_query | execute_query 68 | response = chain.invoke({"question": question}) 69 | print(f'response SQL is:\n{response}') 70 | 71 | """ 72 | 3. 回答问题 73 | """ 74 | 75 | from operator import itemgetter 76 | 77 | from langchain_core.output_parsers import StrOutputParser 78 | from langchain_core.prompts import PromptTemplate 79 | from langchain_core.runnables import RunnablePassthrough 80 | 81 | def ask(llm_model_name,question: str): 82 | answer_prompt = PromptTemplate.from_template( 83 | """Given the following user question, corresponding SQL query, and SQL result, answer the user question. 84 | 85 | Question: {question} 86 | SQL Query: {query} 87 | SQL Result: {result} 88 | Answer: """ 89 | ) 90 | 91 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 92 | execute_query = QuerySQLDataBaseTool(db=db) 93 | write_query = create_sql_query_chain(llm, db) 94 | chain = ( 95 | RunnablePassthrough.assign(query=write_query).assign( 96 | result=itemgetter("query") | execute_query 97 | ) 98 | | answer_prompt 99 | | llm 100 | | StrOutputParser() 101 | ) 102 | 103 | response = chain.invoke({"question": question}) 104 | print(f'Answer is:\n{response}') 105 | 106 | def test_model(model_name): 107 | 108 | qs = [ 109 | "How many Employees are there?", 110 | "Which country's customers spent the most?", 111 | "Describe the PlaylistTrack table." #区分大小写,待改进。比如:用 PlaylistTrack 可以工作,但是用 playlisttrack 不准确 112 | ] 113 | for q in qs: 114 | execute_query(model_name,q) 115 | execute_query_2(model_name,q) 116 | ask(model_name,q) 117 | 118 | 119 | if __name__ == '__main__': 120 | test_db() 121 | test_model("llama3.1") 122 | test_model("qwen2.5") 123 | test_model("deepseek-r1") 124 | -------------------------------------------------------------------------------- /server/services/practice/23_qa_sql_agent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-19 5 | # @function: sql问答agent 6 | # @version : V0.5 7 | # @Description :sql问答agent。 8 | 9 | # https://python.langchain.com/v0.2/docs/tutorials/sql_qa/ 10 | 11 | 12 | """ 13 | 确定文件位置 14 | """ 15 | import os 16 | 17 | # 获取当前执行的程序文件的文件夹路径 18 | current_folder = os.path.dirname(os.path.abspath(__file__)) 19 | 20 | db_file_path = os.path.join(current_folder, 'assert/Chinook.db') 21 | 22 | from langchain_community.utilities import SQLDatabase 23 | 24 | db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}") 25 | 26 | def test_db(): 27 | print(db.dialect) 28 | print(db.get_usable_table_names()) 29 | #print(db.get_table_info()) 30 | print(db.run("SELECT * FROM Artist LIMIT 10;")) 31 | 32 | """ 33 | 1. 将SQLite服务转化为工具 34 | """ 35 | 36 | from langchain_ollama import ChatOllama 37 | 38 | from langchain_community.agent_toolkits import SQLDatabaseToolkit 39 | 40 | def create_tools(llm_model_name): 41 | """创建工具""" 42 | 43 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 44 | toolkit = SQLDatabaseToolkit(db=db, llm=llm) 45 | 46 | tools = toolkit.get_tools() 47 | print(tools) 48 | 49 | return tools 50 | 51 | """ 52 | 2. 系统提示词 53 | """ 54 | 55 | from langchain_core.messages import SystemMessage 56 | 57 | system = """You are an agent designed to interact with a SQL database. 58 | Given an input question, create a syntactically correct SQLite query to run, then look at the results of the query and return the answer. 59 | Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 5 results. 60 | You can order the results by a relevant column to return the most interesting examples in the database. 61 | Never query for all the columns from a specific table, only ask for the relevant columns given the question. 62 | You have access to tools for interacting with the database. 63 | Only use the given tools. Only use the information returned by the tools to construct your final answer. 64 | You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again. 65 | 66 | DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database. 67 | 68 | You have access to the following tables: {table_names} 69 | """.format( 70 | table_names=db.get_usable_table_names() 71 | ) 72 | 73 | system_message = SystemMessage(content=system) 74 | 75 | """ 76 | 3. 智能体 77 | """ 78 | 79 | from langgraph.prebuilt import create_react_agent 80 | from langchain_core.messages import HumanMessage 81 | 82 | def ask(llm_model_name,question): 83 | """询问智能体""" 84 | 85 | tools = create_tools(llm_model_name) 86 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 87 | agent_executor = create_react_agent(llm, tools, state_modifier=system_message) 88 | 89 | for s in agent_executor.stream( 90 | {"messages": [HumanMessage(content=question)]} 91 | ): 92 | print(s) 93 | print("----") 94 | 95 | def test_model(llm_model_name): 96 | """测试大模型""" 97 | 98 | print(f'=========={llm_model_name}==========\n') 99 | 100 | questions = [ 101 | "How many Employees are there?", 102 | "Which country's customers spent the most?", 103 | "Describe the PlaylistTrack table" #区分大小写,待改进。比如:用 PlaylistTrack 可以工作,但是用 playlisttrack 不行 104 | ] 105 | 106 | for question in questions: 107 | ask(llm_model_name,question) 108 | 109 | if __name__ == '__main__': 110 | 111 | test_model("qwen2.5") 112 | 113 | test_model("llama3.1") 114 | 115 | test_model("MFDoom/deepseek-r1-tool-calling:7b") -------------------------------------------------------------------------------- /server/services/practice/23_qa_sql_agent_cn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-19 5 | # @function: sql问答agent 6 | # @version : V0.5 7 | # @Description :sql问答agent。 8 | 9 | # https://python.langchain.com/v0.2/docs/tutorials/sql_qa/ 10 | 11 | 12 | """ 13 | 确定文件位置 14 | """ 15 | import os 16 | 17 | # 获取当前执行的程序文件的文件夹路径 18 | current_folder = os.path.dirname(os.path.abspath(__file__)) 19 | 20 | db_file_path = os.path.join(current_folder, 'assert/Chinook.db') 21 | 22 | 23 | from langchain_community.utilities import SQLDatabase 24 | 25 | db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}") 26 | 27 | def test_db(): 28 | print(db.dialect) 29 | print(db.get_usable_table_names()) 30 | #print(db.get_table_info()) 31 | print(db.run("SELECT * FROM Artist LIMIT 10;")) 32 | 33 | """ 34 | 1. 将SQLite服务转化为工具 35 | """ 36 | 37 | from langchain_ollama import ChatOllama 38 | 39 | from langchain_community.agent_toolkits import SQLDatabaseToolkit 40 | 41 | def create_tools(llm_model_name): 42 | """创建工具""" 43 | 44 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 45 | toolkit = SQLDatabaseToolkit(db=db, llm=llm) 46 | 47 | tools = toolkit.get_tools() 48 | print(tools) 49 | 50 | return tools 51 | 52 | """ 53 | 2. 系统提示词 54 | """ 55 | 56 | from langchain_core.messages import SystemMessage 57 | 58 | system = """您是设计用于与 SQL 数据库交互的代理。用中文回答问题。 59 | 给定一个输入问题,创建一个语法正确的 SQLite 查询来运行,然后查看查询结果并返回答案。 60 | 除非用户指定他们希望获得的特定数量的示例,否则请始终将查询限制为最多 5 个结果。 61 | 您可以按相关列对结果进行排序,以返回数据库中最有趣的示例。 62 | 切勿查询特定表中的所有列,仅询问给定问题的相关列。 63 | 您可以使用与数据库交互的工具。 64 | 仅使用给定的工具。仅使用工具返回的信息来构建最终答案。 65 | 在执行查询之前,您必须仔细检查查询。如果在执行查询时出现错误,请重写查询并重试。 66 | 67 | 请勿对数据库执行任何 DML 语句(INSERT、UPDATE、DELETE、DROP 等)。 68 | 69 | 您有权访问以下数据库表: {table_names} 70 | """.format( 71 | table_names=db.get_usable_table_names() 72 | ) 73 | 74 | system_message = SystemMessage(content=system) 75 | 76 | """ 77 | 3. 智能体 78 | """ 79 | 80 | from langgraph.prebuilt import create_react_agent 81 | from langchain_core.messages import HumanMessage 82 | 83 | def ask(llm_model_name,question): 84 | """询问智能体""" 85 | 86 | tools = create_tools(llm_model_name) 87 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 88 | agent_executor = create_react_agent(llm, tools, state_modifier=system_message) 89 | 90 | for s in agent_executor.stream( 91 | {"messages": [HumanMessage(content=question)]} 92 | ): 93 | print(s) 94 | print("----") 95 | 96 | def test_model(llm_model_name): 97 | """测试大模型""" 98 | 99 | questions = [ 100 | "有多少名员工?", 101 | "哪个国家的顾客花费最多?", 102 | "描述 PlaylistTrack 表" 103 | ] 104 | 105 | for question in questions: 106 | ask(llm_model_name,question) 107 | 108 | if __name__ == '__main__': 109 | 110 | test_model("qwen2.5") 111 | test_model("llama3.1") 112 | test_model("MFDoom/deepseek-r1-tool-calling:7b") -------------------------------------------------------------------------------- /server/services/practice/24_qa_sql_agent_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-19 5 | # @function: sql问答agent 6 | # @version : V0.5 7 | # @Description :sql问答agent。 8 | 9 | # https://python.langchain.com/v0.2/docs/tutorials/sql_qa/ 10 | 11 | 12 | """ 13 | 确定文件位置 14 | """ 15 | import os 16 | 17 | # 获取当前执行的程序文件的文件夹路径 18 | current_folder = os.path.dirname(os.path.abspath(__file__)) 19 | 20 | db_file_path = os.path.join(current_folder, 'assert/Chinook.db') 21 | 22 | """ 23 | 1. 创建SQLite对象 24 | """ 25 | 26 | from langchain_community.utilities import SQLDatabase 27 | 28 | db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}") 29 | 30 | def test_db(): 31 | print(db.dialect) 32 | print(db.get_usable_table_names()) 33 | #print(db.get_table_info()) 34 | print(db.run("SELECT * FROM Artist LIMIT 10;")) 35 | 36 | """ 37 | 2. 将SQLite服务转化为工具 38 | """ 39 | 40 | from langchain_ollama import ChatOllama 41 | 42 | from langchain_community.agent_toolkits import SQLDatabaseToolkit 43 | 44 | def create_tools(llm_model_name): 45 | """创建工具""" 46 | 47 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 48 | toolkit = SQLDatabaseToolkit(db=db, llm=llm) 49 | 50 | tools = toolkit.get_tools() 51 | print(tools) 52 | 53 | return tools 54 | 55 | """ 56 | 3. 创建矢量数据库 57 | """ 58 | 59 | from langchain_ollama import OllamaEmbeddings 60 | embeddings = OllamaEmbeddings(model="nomic-embed-text") 61 | 62 | from langchain_chroma import Chroma 63 | 64 | persist_directory = os.path.join(current_folder,'assert/db_artists_albums') 65 | vectordb = Chroma(persist_directory=persist_directory,embedding_function=embeddings) 66 | 67 | from tqdm import tqdm 68 | 69 | def embed_texts_in_batches(documents, batch_size=10): 70 | """ 71 | 按批次嵌入,可以跟踪进度。 72 | vectordb会自动持久化存储在磁盘。 73 | """ 74 | 75 | for i in tqdm(range(0, len(documents), batch_size), desc="嵌入进度"): 76 | batch = documents[i:i + batch_size] 77 | # 从文本块生成嵌入,并将嵌入存储在Chroma向量数据库中,同时设置数据库持久化路径。 78 | # 耗时较长,需要耐心等候... 79 | vectordb.add_texts(batch) 80 | 81 | import ast 82 | import re 83 | 84 | def query_as_list(db, query): 85 | res = db.run(query) 86 | res = [el for sub in ast.literal_eval(res) for el in sub if el] 87 | res = [re.sub(r"\b\d+\b", "", string).strip() for string in res] 88 | return list(set(res)) 89 | 90 | def create_db(): 91 | """创建矢量数据库""" 92 | 93 | if os.path.exists(persist_directory): 94 | print("数据库已创建") 95 | return 96 | 97 | artists = query_as_list(db, "SELECT Name FROM Artist") 98 | print(f'artists:\n{artists[:5]}\n') 99 | albums = query_as_list(db, "SELECT Title FROM Album") 100 | print(f'albums:\n{albums[:5]}\n') 101 | 102 | documents = artists + albums 103 | embed_texts_in_batches(documents) 104 | print('db_artists_albums persisted.') 105 | 106 | create_db() 107 | 108 | """ 109 | 4. 创建检索工具 110 | """ 111 | 112 | retriever = vectordb.as_retriever(search_kwargs={"k": 5}) # 返回5条信息 113 | 114 | from langchain.agents.agent_toolkits import create_retriever_tool 115 | description = """Use to look up values to filter on. Input is an approximate spelling of the proper noun, output is \ 116 | valid proper nouns. Use the noun most similar to the search.""" 117 | retriever_tool = create_retriever_tool( 118 | retriever, 119 | name="search_proper_nouns", 120 | description=description, 121 | ) 122 | 123 | 124 | """ 125 | 5. 系统提示词 126 | """ 127 | 128 | from langchain_core.messages import SystemMessage 129 | 130 | system = """You are an agent designed to interact with a SQL database. 131 | Given an input question, create a syntactically correct SQLite query to run, then look at the results of the query and return the answer. 132 | Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 5 results. 133 | You can order the results by a relevant column to return the most interesting examples in the database. 134 | Never query for all the columns from a specific table, only ask for the relevant columns given the question. 135 | You have access to tools for interacting with the database. 136 | Only use the given tools. Only use the information returned by the tools to construct your final answer. 137 | You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again. 138 | 139 | DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database. 140 | 141 | You have access to the following tables: {table_names} 142 | 143 | If you need to filter on a proper noun, you must ALWAYS first look up the filter value using the "search_proper_nouns" tool! 144 | Do not try to guess at the proper name - use this function to find similar ones.""".format( 145 | table_names=db.get_usable_table_names() 146 | ) 147 | 148 | system_message = SystemMessage(content=system) 149 | 150 | 151 | """ 152 | 6. 智能体 153 | """ 154 | 155 | from langgraph.prebuilt import create_react_agent 156 | from langchain_core.messages import HumanMessage 157 | 158 | def ask(llm_model_name,question): 159 | """询问智能体""" 160 | 161 | tools = create_tools(llm_model_name) 162 | tools.append(retriever_tool) 163 | 164 | llm = ChatOllama(model=llm_model_name,temperature=1, verbose=True) 165 | agent_executor = create_react_agent(llm, tools, state_modifier=system_message) 166 | 167 | for s in agent_executor.stream( 168 | {"messages": [HumanMessage(content=question)]} 169 | ): 170 | print(s) 171 | print("----") 172 | 173 | def test_model(llm_model_name): 174 | """测试大模型""" 175 | 176 | print(f'=========={llm_model_name}==========') 177 | questions = [ 178 | "How many Employees are there?", 179 | "Which country's customers spent the most?", 180 | "How many albums does Itzhak Perlmam have?", 181 | ] 182 | 183 | for question in questions: 184 | ask(llm_model_name,question) 185 | 186 | if __name__ == '__main__': 187 | 188 | print(retriever_tool.invoke("Itzhak Perlmam")) 189 | 190 | test_model("qwen2.5") 191 | test_model("llama3.1") -------------------------------------------------------------------------------- /server/services/practice/25_qa_sql_graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-20 5 | # @function: sql问答 6 | # @version : V0.5 7 | # @Description :sql问答。 8 | 9 | # https://python.langchain.com/docs/tutorials/sql_qa/ 10 | 11 | 12 | """ 13 | 确定文件位置 14 | """ 15 | 16 | import os 17 | 18 | # 获取当前执行的程序文件的文件夹路径 19 | current_folder = os.path.dirname(os.path.abspath(__file__)) 20 | 21 | db_file_path = os.path.join(current_folder, 'assert/Chinook.db') 22 | 23 | """ 24 | 1. 创建SQLite对象 25 | """ 26 | 27 | from langchain_community.utilities import SQLDatabase 28 | 29 | db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}") 30 | 31 | def test_db(): 32 | """测试SQLite数据库""" 33 | print(db.dialect) 34 | print(db.get_usable_table_names()) 35 | #print(db.get_table_info()) 36 | print(db.run("SELECT * FROM Artist LIMIT 10;")) 37 | 38 | 39 | """ 40 | 2. 状态 41 | """ 42 | 43 | from typing_extensions import TypedDict 44 | 45 | class State(TypedDict): 46 | question: str 47 | query: str 48 | result: str 49 | answer: str 50 | 51 | from langchain_ollama import ChatOllama 52 | llm = ChatOllama(model="llama3.1",temperature=0, verbose=True) 53 | 54 | def set_llm(llm_model_name): 55 | """设置大模型,用于测试不同大模型""" 56 | global llm 57 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 58 | 59 | """ 60 | 3. 定义langgraph节点 61 | """ 62 | 63 | from typing_extensions import Annotated 64 | 65 | class QueryOutput(TypedDict): 66 | """生成的SQL查询语句""" 67 | 68 | query: Annotated[str, ..., "Syntactically valid SQL query."] 69 | 70 | 71 | 72 | # 提示词 73 | 74 | system = """You are an agent designed to interact with a SQL database. 75 | Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer. 76 | Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 5 results. 77 | You can order the results by a relevant column to return the most interesting examples in the database. 78 | Never query for all the columns from a specific table, only ask for the relevant columns given the question. 79 | You have access to tools for interacting with the database. 80 | Only use the given tools. Only use the information returned by the tools to construct your final answer. 81 | You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again. 82 | 83 | DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database. 84 | 85 | You have access to the following tables: {table_names} 86 | """.format( 87 | table_names=db.get_usable_table_names(), 88 | dialect=db.dialect 89 | ) 90 | 91 | from langchain_core.prompts import ChatPromptTemplate 92 | query_prompt_template = ChatPromptTemplate.from_messages([ 93 | ("system", system), 94 | ("user", "Question:{input}") 95 | ]) 96 | 97 | def test_prompt(): 98 | """测试提示词""" 99 | assert len(query_prompt_template.messages) == 1 100 | query_prompt_template.messages[0].pretty_print() 101 | 102 | def write_query(state: State): 103 | """根据问题生成SQL查询语句""" 104 | prompt = query_prompt_template.invoke( 105 | { 106 | "input": state["question"], 107 | } 108 | ) 109 | structured_llm = llm.with_structured_output(QueryOutput) 110 | result = structured_llm.invoke(prompt) 111 | print(f'Query is:\n{result["query"]}') 112 | return {"query": result["query"]} 113 | 114 | 115 | from langchain_community.tools.sql_database.tool import QuerySQLDatabaseTool 116 | 117 | def execute_query(state: State): 118 | """执行SQL查询""" 119 | execute_query_tool = QuerySQLDatabaseTool(db=db) 120 | result = execute_query_tool.invoke(state["query"]) 121 | print(f'Result is:\n{result}') 122 | return {"result": result} 123 | 124 | 125 | def generate_answer(state: State): 126 | """使用检索到的信息作为上下文来回答问题。""" 127 | prompt = ( 128 | "Given the following user question, corresponding SQL query, " 129 | "and SQL result, answer the user question.\n\n" 130 | f'Question: {state["question"]}\n' 131 | f'SQL Query: {state["query"]}\n' 132 | f'SQL Result: {state["result"]}' 133 | ) 134 | response = llm.invoke(prompt) 135 | print(f'answer is:\n{response.content}') 136 | return {"answer": response.content} 137 | 138 | """ 139 | 4. langgraph链 140 | """ 141 | 142 | from langgraph.graph import START, StateGraph 143 | 144 | graph_builder = StateGraph(State).add_sequence( 145 | [write_query, execute_query, generate_answer] 146 | ) 147 | graph_builder.add_edge(START, "write_query") 148 | graph = graph_builder.compile() 149 | 150 | def ask(question): 151 | """问答""" 152 | for step in graph.stream( 153 | {"question": question}, stream_mode="updates" 154 | ): 155 | print(step) 156 | 157 | def test_model(llm_model_name): 158 | """测试大模型""" 159 | 160 | print(f'============{llm_model_name}==========') 161 | 162 | set_llm(llm_model_name) 163 | 164 | questions = [ 165 | "How many Employees are there?", 166 | "Which country's customers spent the most?", 167 | "Describe the PlaylistTrack table", # 区分大小写,待改进。比如:用 PlaylistTrack 可以工作,但是用 playlisttrack 不准确 168 | ] 169 | 170 | for question in questions: 171 | write_query({"question": question}) 172 | 173 | for question in questions: 174 | ask({"question": question}) 175 | 176 | 177 | 178 | if __name__ == '__main__': 179 | 180 | from utils import show_graph 181 | show_graph(graph) 182 | 183 | test_db() 184 | test_prompt() 185 | 186 | test_model("qwen2.5") 187 | test_model("llama3.1") 188 | #test_model("deepseek-r1") 189 | -------------------------------------------------------------------------------- /server/services/practice/26_qa_sql_graph_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-20 5 | # @function: sql问答 6 | # @version : V0.5 7 | # @Description :sql问答。 8 | 9 | # https://python.langchain.com/docs/tutorials/sql_qa/ 10 | 11 | 12 | """ 13 | 确定文件位置 14 | """ 15 | 16 | import os 17 | 18 | # 获取当前执行的程序文件的文件夹路径 19 | current_folder = os.path.dirname(os.path.abspath(__file__)) 20 | 21 | db_file_path = os.path.join(current_folder, 'assert/Chinook.db') 22 | 23 | """ 24 | 1. 创建SQLite对象 25 | """ 26 | 27 | from langchain_community.utilities import SQLDatabase 28 | 29 | db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}") 30 | 31 | def test_db(): 32 | """测试SQLite数据库""" 33 | print(db.dialect) 34 | print(db.get_usable_table_names()) 35 | #print(db.get_table_info()) 36 | print(db.run("SELECT * FROM Artist LIMIT 10;")) 37 | 38 | 39 | 40 | """ 41 | 2. 状态 42 | """ 43 | 44 | from typing_extensions import TypedDict 45 | 46 | class State(TypedDict): 47 | question: str 48 | query: str 49 | result: str 50 | answer: str 51 | 52 | from langchain_ollama import ChatOllama 53 | llm = ChatOllama(model="llama3.1",temperature=0, verbose=True) 54 | 55 | def set_llm(llm_model_name): 56 | """设置大模型,用于测试不同大模型""" 57 | global llm 58 | llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True) 59 | 60 | """ 61 | 3. 定义langgraph节点 62 | """ 63 | 64 | # 提示词 65 | system = """You are an agent designed to interact with a SQL database. 66 | Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer. 67 | Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 5 results. 68 | You can order the results by a relevant column to return the most interesting examples in the database. 69 | Never query for all the columns from a specific table, only ask for the relevant columns given the question. 70 | You have access to tools for interacting with the database. 71 | Only use the given tools. Only use the information returned by the tools to construct your final answer. 72 | You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again. 73 | 74 | DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database. 75 | 76 | You have access to the following tables: {table_names} 77 | """.format( 78 | table_names=db.get_usable_table_names(), 79 | dialect=db.dialect 80 | ) 81 | 82 | from langchain_core.prompts import ChatPromptTemplate 83 | query_prompt_template = ChatPromptTemplate.from_messages([ 84 | ("system", system), 85 | ("user", "Question:{input}") 86 | ]) 87 | 88 | def test_prompt(): 89 | """测试提示词""" 90 | assert len(query_prompt_template.messages) == 1 91 | query_prompt_template.messages[0].pretty_print() 92 | 93 | from typing_extensions import Annotated 94 | 95 | class QueryOutput(TypedDict): 96 | """生成的SQL查询语句""" 97 | 98 | query: Annotated[str, ..., "Syntactically valid SQL query."] 99 | 100 | def write_query(state: State): 101 | """根据问题生成SQL查询语句""" 102 | prompt = query_prompt_template.invoke( 103 | { 104 | "input": state["question"], 105 | } 106 | ) 107 | structured_llm = llm.with_structured_output(QueryOutput) 108 | result = structured_llm.invoke(prompt) 109 | #print(f'Query is:\n{result["query"]}') 110 | return {"query": result["query"]} 111 | 112 | 113 | from langchain_community.tools.sql_database.tool import QuerySQLDatabaseTool 114 | 115 | def execute_query(state: State): 116 | """执行SQL查询""" 117 | execute_query_tool = QuerySQLDatabaseTool(db=db) 118 | result = execute_query_tool.invoke(state["query"]) 119 | #print(f'Result is:\n{result}') 120 | return {"result": result} 121 | 122 | 123 | def generate_answer(state: State): 124 | """使用检索到的信息作为上下文来回答问题。""" 125 | prompt = ( 126 | "Given the following user question, corresponding SQL query, " 127 | "and SQL result, answer the user question.\n\n" 128 | f'Question: {state["question"]}\n' 129 | f'SQL Query: {state["query"]}\n' 130 | f'SQL Result: {state["result"]}' 131 | ) 132 | response = llm.invoke(prompt) 133 | #print(f'answer is:\n{response.content}') 134 | return {"answer": response.content} 135 | 136 | """ 137 | 4. langgraph链 138 | """ 139 | 140 | from langgraph.graph import START, StateGraph 141 | 142 | graph_builder = StateGraph(State).add_sequence( 143 | [write_query, execute_query, generate_answer] 144 | ) 145 | graph_builder.add_edge(START, "write_query") 146 | graph = graph_builder.compile() 147 | 148 | def ask(question): 149 | """问答""" 150 | for step in graph.stream( 151 | {"question": question}, stream_mode="updates" 152 | ): 153 | print(step) 154 | 155 | """ 156 | 5. 在链中增加人工审核 157 | """ 158 | 159 | from langgraph.checkpoint.memory import MemorySaver 160 | 161 | memory = MemorySaver() 162 | graph_with_human = graph_builder.compile(checkpointer=memory, interrupt_before=["execute_query"]) 163 | 164 | def ask_with_human(question,thread_id): 165 | """问答:增加了人工审核""" 166 | config = {"configurable": {"thread_id": thread_id}} 167 | for step in graph_with_human.stream( 168 | {"question": question}, 169 | config, 170 | stream_mode="updates", 171 | ): 172 | print(step) 173 | 174 | try: 175 | user_approval = input("您确定要执行查询么?(yes/no): ") 176 | except Exception: 177 | user_approval = "no" 178 | 179 | if user_approval.lower() == "yes": 180 | # 如果获得批准,再继续执行 181 | for step in graph_with_human.stream(None, config, stream_mode="updates"): 182 | print(step) 183 | else: 184 | print("操作已被取消。") 185 | 186 | def test_model(llm_model_name): 187 | """测试大模型""" 188 | 189 | print(f'============{llm_model_name}==========') 190 | 191 | set_llm(llm_model_name) 192 | 193 | thread_id = "liu23" 194 | questions = [ 195 | "How many Employees are there?", 196 | "Which country's customers spent the most?", 197 | ] 198 | 199 | for question in questions: 200 | ask_with_human( question,thread_id) 201 | 202 | if __name__ == '__main__': 203 | #test_db() 204 | #test_prompt() 205 | 206 | #write_query({"question": "How many Employees are there?"}) 207 | 208 | #execute_query({"query": "SELECT COUNT(EmployeeId) AS EmployeeCount FROM Employee;"}) 209 | 210 | #from utils import show_graph 211 | #show_graph(graph_with_human) 212 | 213 | test_model("qwen2.5") 214 | test_model("llama3.1") 215 | -------------------------------------------------------------------------------- /server/services/practice/27.streaming.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-02-26 5 | # @function: 流式输出 6 | # @version : V0.5 7 | # @Description :测试流式输出。 8 | 9 | """ 10 | langchain 的流式输出 11 | """ 12 | from langchain_ollama import ChatOllama 13 | from langchain_core.messages import HumanMessage,AIMessage 14 | 15 | def chat(llm_model_name,question): 16 | """与大模型聊天,一次性输出""" 17 | model = ChatOllama(model=llm_model_name,temperature=0.3,verbose=True) 18 | response = model.invoke([HumanMessage(content=question)]) 19 | print(f'AI:\n{response.content}') 20 | 21 | def chat_stream(llm_model_name,question): 22 | """与大模型聊天,流式输出""" 23 | model = ChatOllama(model=llm_model_name,temperature=0.3,verbose=True) 24 | for chunk in model.stream([HumanMessage(content=question)]): 25 | if isinstance(chunk, AIMessage) and chunk.content !='': 26 | print(chunk.content,end="^") 27 | 28 | from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler 29 | 30 | def chat_stream_2(llm_model_name,question): 31 | """与大模型聊天,流式输出""" 32 | model = ChatOllama(model=llm_model_name,temperature=0.3,verbose=True,callbacks=[StreamingStdOutCallbackHandler()]) 33 | model.invoke([HumanMessage(content=question)]) 34 | 35 | class CustomStreamingHandler(StreamingStdOutCallbackHandler): 36 | """自定义流式回调处理器,在流式输出时使用 ^ 作为分隔符""" 37 | 38 | def on_llm_new_token(self, token: str, **kwargs) -> None: 39 | """重写方法,修改输出格式""" 40 | print(token, end="^", flush=True) # 使用 `^` 作为分隔符 41 | 42 | def chat_stream_3(llm_model_name,question): 43 | """与大模型聊天,流式输出""" 44 | model = ChatOllama(model=llm_model_name,temperature=0.3,verbose=True,callbacks=[CustomStreamingHandler()]) 45 | model([HumanMessage(content=question)]) 46 | 47 | """ 48 | 智能体的流式输出 49 | """ 50 | 51 | # 初始化对话存储 52 | from langchain.memory import ConversationBufferWindowMemory 53 | memory = ConversationBufferWindowMemory( 54 | memory_key="chat_history", 55 | k=5, 56 | return_messages=True, 57 | output_key="output" 58 | ) 59 | 60 | llm_model_name = "qwen2.5" 61 | model = ChatOllama(model=llm_model_name,temperature=0.3,verbose=True,callbacks=[CustomStreamingHandler()]) 62 | 63 | from langchain_community.agent_toolkits.load_tools import load_tools 64 | 65 | # 创建一个工具来观察它如何影响流的输出 66 | tools = load_tools(["llm-math"], llm=model) 67 | 68 | from langchain.agents import AgentType, initialize_agent 69 | 70 | # 创建智能体 71 | agent = initialize_agent( 72 | agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION, 73 | tools=tools, 74 | llm=model, 75 | memory=memory, 76 | verbose=True, 77 | max_iterations=3, 78 | early_stopping_method="generate", 79 | return_intermediate_steps=False 80 | ) 81 | 82 | def chat_agent(quesion): 83 | """与智能体聊天,它会把所有内容都流式输出""" 84 | agent.invoke(quesion) 85 | 86 | def chat_agent_2(quesion): 87 | """与智能体聊天,它会把所有内容都流式输出""" 88 | from langchain.callbacks.streaming_stdout_final_only import ( 89 | FinalStreamingStdOutCallbackHandler, 90 | ) 91 | 92 | agent.agent.llm_chain.llm.callbacks = [ 93 | FinalStreamingStdOutCallbackHandler( 94 | answer_prefix_tokens=["Final", "Answer"] # 流式输出 Final 和 Answer 后面的内容 95 | ) 96 | ] 97 | 98 | agent.invoke(quesion) 99 | 100 | import sys 101 | 102 | class CallbackHandler(StreamingStdOutCallbackHandler): 103 | """自定义输出""" 104 | def __init__(self): 105 | self.content: str = "" 106 | self.final_answer: bool = False 107 | 108 | def on_llm_new_token(self, token: str, **kwargs: any) -> None: 109 | """智能体会逐渐返回json格式的结果,这里只输出 action_input 的内容""" 110 | self.content += token 111 | if "Final Answer" in self.content: 112 | # 现在我们到了 Final Answer 部分,但不要打印。 113 | self.final_answer = True 114 | self.content = "" 115 | if self.final_answer: 116 | if '"action_input": "' in self.content: 117 | 118 | # 当字符串中包含 '}' 时,移除 '}' 和后面的字符。 119 | index = token.find('}') # 找到 '}' 的索引 120 | if index != -1: 121 | self.final_answer = False 122 | token = token[:index] 123 | 124 | sys.stdout.write(token) 125 | if index == -1: 126 | sys.stdout.write('^') 127 | sys.stdout.flush() 128 | 129 | def chat_agent_3(quesion): 130 | """与智能体聊天,它会把所有内容都流式输出""" 131 | agent.agent.llm_chain.llm.callbacks =[CallbackHandler()] 132 | agent.invoke(quesion) 133 | 134 | 135 | if __name__ == '__main__': 136 | 137 | question = "中国有多少个地级市?" 138 | chat("qwen2.5",question) 139 | chat_stream("qwen2.5",question) 140 | chat_stream_2("qwen2.5",question) 141 | chat_stream_3("qwen2.5",question) 142 | 143 | chat_agent(question) 144 | chat_agent("9的平方是多少?") 145 | chat_agent_2("9的平方是多少?") 146 | chat_agent_3("9的平方是多少?") 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /server/services/practice/28.qa_graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-21 5 | # @function: 使用图形数据库进行问答 6 | # @version : V0.5 7 | # @Description :使用图形数据库进行问答。 8 | 9 | # 安装neo4j 10 | # 下载地址:https://neo4j.com/deployment-center/ 11 | # 安装:https://neo4j.com/docs/operations-manual/current/installation/windows/ 12 | # 安装APOC插件:https://github.com/neo4j/apoc/releases/tag/5.26.1 13 | 14 | import os 15 | os.environ["NEO4J_URI"] = "bolt://localhost:7687" 16 | os.environ["NEO4J_USERNAME"] = "neo4j" 17 | os.environ["NEO4J_PASSWORD"] = "neo4j" 18 | 19 | from langchain_neo4j import Neo4jGraph 20 | 21 | graph = Neo4jGraph() 22 | 23 | def create_graph(): 24 | """导入数据,创建图形数据库""" 25 | 26 | # 把movies_small.csv拷贝到neo4j的import文件夹内 27 | db_file_path = 'file:///movies_small.csv' 28 | 29 | movies_query = """ 30 | LOAD CSV WITH HEADERS FROM 31 | '%s' 32 | AS row 33 | MERGE (m:Movie {id:row.movieId}) 34 | SET m.released = date(row.released), 35 | m.title = row.title, 36 | m.imdbRating = toFloat(row.imdbRating) 37 | FOREACH (director in split(row.director, '|') | 38 | MERGE (p:Person {name:trim(director)}) 39 | MERGE (p)-[:DIRECTED]->(m)) 40 | FOREACH (actor in split(row.actors, '|') | 41 | MERGE (p:Person {name:trim(actor)}) 42 | MERGE (p)-[:ACTED_IN]->(m)) 43 | FOREACH (genre in split(row.genres, '|') | 44 | MERGE (g:Genre {name:trim(genre)}) 45 | MERGE (m)-[:IN_GENRE]->(g)) 46 | """ % (db_file_path) 47 | 48 | graph.query(movies_query) 49 | 50 | graph.refresh_schema() 51 | print(graph.schema) 52 | 53 | enhanced_graph = Neo4jGraph(enhanced_schema=True) 54 | print(enhanced_graph.schema) 55 | 56 | from langchain_ollama import ChatOllama 57 | llm = ChatOllama(model="qwen2.5",temperature=0, verbose=True) #llama3.1查不出内容;EntropyYue/chatglm3生成的查询有问题报错 58 | 59 | # GraphQACypherChain 60 | from langchain_neo4j import GraphCypherQAChain 61 | 62 | chain = GraphCypherQAChain.from_llm( 63 | graph=enhanced_graph, llm=llm, verbose=True, allow_dangerous_requests=True 64 | ) 65 | 66 | def ask(question:str): 67 | """询问图数据库内容""" 68 | 69 | response = chain.invoke({"query": question}) 70 | print(f'response:\n{response}') 71 | 72 | if __name__ == '__main__': 73 | 74 | #create_graph() 75 | ask("What was the cast of the Casino?") 76 | 77 | 78 | -------------------------------------------------------------------------------- /server/services/practice/30.summarize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-20 5 | # @function: 总结文本 6 | # @version : V0.5 7 | # @Description :总结文本。 8 | 9 | # https://python.langchain.com/docs/tutorials/summarization/ 10 | 11 | import os 12 | os.environ['USER_AGENT'] = 'summarize' 13 | 14 | from langchain_community.document_loaders import WebBaseLoader 15 | 16 | """ 17 | 1. 加载文档 18 | """ 19 | 20 | loader = WebBaseLoader("http://wfcoding.com/articles/practice/03%E6%9C%AC%E5%9C%B0%E5%A4%A7%E6%A8%A1%E5%9E%8B%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98/",encoding='utf-8') 21 | docs = loader.load() 22 | 23 | from langchain_ollama import ChatOllama 24 | llm = ChatOllama(model="qwen2.5",temperature=0.3, verbose=True) 25 | # llama3.1 不能执行此任务 26 | #llm = ChatOllama(model="llama3.1",temperature=0.3, verbose=True) 27 | 28 | """ 29 | 2. 用一次调用提取摘要 30 | """ 31 | 32 | from langchain.chains.combine_documents import create_stuff_documents_chain 33 | from langchain_core.prompts import ChatPromptTemplate 34 | 35 | # 定义提示词 36 | prompt = ChatPromptTemplate.from_messages( 37 | [ 38 | ("system", "请简明扼要地概括以下内容:\\n\\n{context}") 39 | ] 40 | ) 41 | 42 | # 初始化 chain 43 | chain = create_stuff_documents_chain(llm, prompt) 44 | 45 | def sum_single_llm_call() : 46 | 47 | # 调用 chain 48 | result = chain.invoke({"context": docs}) 49 | print(result) 50 | 51 | """ 52 | 3. 流式输出 53 | """ 54 | 55 | def sum_single_llm_call_stream() : 56 | 57 | for token in chain.stream({"context": docs}): 58 | print(token, end="|") 59 | 60 | if __name__ == '__main__': 61 | #sum_single_llm_call() 62 | sum_single_llm_call_stream() 63 | -------------------------------------------------------------------------------- /server/services/practice/31.summarize_map_reduce.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-04-28 5 | # @function: 使用Map-Reduce对大量文本提取摘要 6 | # @version : V0.5 7 | 8 | # https://python.langchain.com/docs/tutorials/summarization/ 9 | 10 | import os 11 | os.environ['USER_AGENT'] = 'summarize' 12 | 13 | llm_model_name = "qwen2.5" 14 | 15 | from langchain_ollama import ChatOllama 16 | llm = ChatOllama(model=llm_model_name,temperature=0.3, verbose=True) 17 | 18 | """ 19 | 1. 准备文档 20 | """ 21 | 22 | def load_document(url): 23 | """加载文档""" 24 | 25 | from langchain_community.document_loaders import WebBaseLoader 26 | loader = WebBaseLoader(url,encoding='utf-8') 27 | doc = loader.load() 28 | 29 | return doc 30 | 31 | def split_document(url): 32 | """分割文档,为Map做准备""" 33 | 34 | doc = load_document(url) 35 | 36 | from langchain_text_splitters import CharacterTextSplitter 37 | text_splitter = CharacterTextSplitter.from_tiktoken_encoder( 38 | chunk_size=1000, chunk_overlap=0 39 | ) 40 | split_docs = text_splitter.split_documents(doc) 41 | print(f"Generated {len(split_docs)} documents.") 42 | 43 | return split_docs 44 | 45 | split_docs = split_document("http://www.wfcoding.com/articles/practice/0325/") 46 | 47 | """ 48 | 2. Map-Reduce提示词 49 | """ 50 | # Map时使用 51 | from langchain_core.prompts import ChatPromptTemplate 52 | map_prompt = ChatPromptTemplate.from_messages( 53 | [ 54 | ("system", "请简明扼要地概括以下内容:\\n\\n{context}") 55 | ] 56 | ) 57 | 58 | # Reduce时使用 59 | reduce_template = """ 60 | 以下是一组摘要: 61 | {docs} 62 | 请将这些内容提炼成最终的、综合的主题摘要。 63 | """ 64 | reduce_prompt = ChatPromptTemplate([("human", reduce_template)]) 65 | 66 | """ 67 | 3. 确定token数量 68 | """ 69 | 70 | from typing import List 71 | from langchain_core.documents import Document 72 | import jieba 73 | def count_tokens(text): 74 | """返回token数量""" 75 | 76 | tokens = jieba.lcut(text) 77 | return len(tokens) 78 | 79 | def length_function(documents: List[Document]) -> int: 80 | """获取输入内容的token数量。""" 81 | 82 | return sum(count_tokens(doc.page_content) for doc in documents) 83 | 84 | """ 85 | 4. 定义状态 86 | """ 87 | import operator 88 | from typing import Annotated, Literal, TypedDict 89 | 90 | class OverallState(TypedDict): 91 | """主体状态 92 | 93 | 这里我们使用了operator.add,这是因为我们想将所有从各个节点生成的摘要合并到一个列表中。 94 | """ 95 | 96 | contents: List[str] # 分割后的原始文档列表 97 | summaries: Annotated[list, operator.add] # 由原始文档列表生成的摘要列表 98 | collapsed_summaries: List[Document] # 折叠/压缩的文档列表 99 | final_summary: str # 最终提取的摘要 100 | 101 | 102 | class SummaryState(TypedDict): 103 | """将所有文档“映射”到的节点的状态,以便生成摘要""" 104 | 105 | content: str 106 | 107 | """ 108 | 5. 定义节点/步骤 109 | """ 110 | 111 | from langchain.chains.combine_documents.reduce import ( 112 | acollapse_docs, 113 | split_list_of_docs, 114 | ) 115 | from langgraph.graph import END, START, StateGraph 116 | from langgraph.constants import Send 117 | 118 | token_max = 1000 119 | 120 | async def generate_summary(state: SummaryState): 121 | """提取一个文档的摘要""" 122 | 123 | prompt = map_prompt.invoke(state["content"]) 124 | response = await llm.ainvoke(prompt) 125 | return {"summaries": [response.content]} 126 | 127 | def map_summaries(state: OverallState): 128 | """ 129 | 【边】把文档列表中的每一个文档map出去 130 | 131 | 返回一个 `Send` 对象列表。每个 `Send` 对象包含图中一个节点的名称以及要发送到该节点的状态。 132 | """ 133 | 134 | return [ 135 | Send("generate_summary", {"content": content}) for content in state["contents"] 136 | ] 137 | 138 | 139 | def collect_summaries(state: OverallState): 140 | """收集从map出去的文档提取的摘要 141 | 142 | 所有摘要放在 collapsed_summaries 中,后面可以对它折叠/压缩,直到摘要小于token_max。 143 | """ 144 | 145 | return { 146 | "collapsed_summaries": [Document(summary) for summary in state["summaries"]] 147 | } 148 | 149 | 150 | async def _reduce(input: dict) -> str: 151 | prompt = reduce_prompt.invoke(input) 152 | response = await llm.ainvoke(prompt) 153 | return response.content 154 | 155 | 156 | async def collapse_summaries(state: OverallState): 157 | """折叠/压缩摘要""" 158 | 159 | doc_lists = split_list_of_docs( 160 | state["collapsed_summaries"], length_function, token_max 161 | ) 162 | 163 | # 使用reduce提示词折叠/压缩摘要列表 164 | results = [] 165 | for doc_list in doc_lists: 166 | results.append(await acollapse_docs(doc_list, _reduce)) 167 | 168 | return {"collapsed_summaries": results} 169 | 170 | 171 | def should_collapse( 172 | state: OverallState, 173 | ) -> Literal["collapse_summaries", "generate_final_summary"]: 174 | """【边】确定我们是否应该折叠/压缩摘要""" 175 | 176 | num_tokens = length_function(state["collapsed_summaries"]) 177 | if num_tokens > token_max: 178 | return "collapse_summaries" 179 | else: 180 | return "generate_final_summary" 181 | 182 | 183 | async def generate_final_summary(state: OverallState): 184 | """生成最终摘要""" 185 | 186 | response = await _reduce(state["collapsed_summaries"]) 187 | return {"final_summary": response} 188 | 189 | 190 | def create_graph(): 191 | """构建langgraph图""" 192 | 193 | graph = StateGraph(OverallState) 194 | graph.add_node("generate_summary", generate_summary) 195 | graph.add_node("collect_summaries", collect_summaries) 196 | graph.add_node("collapse_summaries", collapse_summaries) 197 | graph.add_node("generate_final_summary", generate_final_summary) 198 | 199 | # Edges: 200 | graph.add_conditional_edges(START, map_summaries, ["generate_summary"]) 201 | graph.add_edge("generate_summary", "collect_summaries") 202 | graph.add_conditional_edges("collect_summaries", should_collapse) 203 | graph.add_conditional_edges("collapse_summaries", should_collapse) 204 | graph.add_edge("generate_final_summary", END) 205 | 206 | app = graph.compile() 207 | return app 208 | 209 | async def summarize(): 210 | """提取摘要""" 211 | 212 | app = create_graph() 213 | 214 | async for step in app.astream( 215 | {"contents": [doc.page_content for doc in split_docs]}, 216 | {"recursion_limit": 10}, 217 | ): 218 | step_keys = list(step.keys()) 219 | print(step_keys) 220 | if 'generate_final_summary' in step_keys: 221 | print(step['generate_final_summary']) 222 | 223 | 224 | if __name__ == '__main__': 225 | 226 | """ 227 | from utils import show_graph 228 | app = create_graph() 229 | show_graph(app) 230 | """ 231 | 232 | import asyncio 233 | asyncio.run(summarize()) 234 | 235 | 236 | -------------------------------------------------------------------------------- /server/services/practice/32.websocket.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-04-28 5 | # @function: 用websocket显示LLM的输出流 6 | # @version : V0.5 7 | 8 | from langchain_ollama import ChatOllama 9 | from langchain_core.messages import HumanMessage,AIMessage 10 | 11 | model_name = "qwen3" 12 | 13 | llm = ChatOllama(model=model_name,temperature=0.3,verbose=True) 14 | 15 | def ask(question): 16 | 17 | result = llm.invoke([HumanMessage(content=question)]) 18 | return result.content 19 | 20 | import asyncio 21 | async def ask_stream(question,websocket=None): 22 | """与大模型聊天,流式输出""" 23 | 24 | for chunk in llm.stream([HumanMessage(content=question)]): 25 | if isinstance(chunk, AIMessage) and chunk.content !='': 26 | print(chunk.content,end="^") 27 | if websocket is not None: 28 | await websocket.send_json({"reply": chunk.content}) 29 | await asyncio.sleep(0.1) # sleep一下后,前端就可以一点一点显示内容。 30 | 31 | 32 | import os 33 | from fastapi import FastAPI, WebSocket 34 | from fastapi.responses import HTMLResponse 35 | 36 | app = FastAPI() 37 | 38 | @app.get("/") 39 | async def get(): 40 | """返回聊天页面""" 41 | 42 | file_path = os.path.join(os.path.dirname(__file__), "chat.html") 43 | with open(file_path, "r", encoding="utf-8") as f: 44 | html_content = f.read() 45 | return HTMLResponse(content=html_content) 46 | 47 | @app.websocket("/ws") 48 | async def websocket_endpoint(websocket: WebSocket): 49 | await websocket.accept() 50 | try: 51 | while True: 52 | data = await websocket.receive_json() 53 | user_message = data.get("message", "") 54 | print(f"收到用户消息: {user_message}") 55 | await ask_stream(user_message,websocket=websocket) 56 | """ 57 | reply_message = ask(user_message) 58 | await websocket.send_json({"reply": reply_message}) 59 | """ 60 | except Exception as e: 61 | print(f"连接关闭: {e}") 62 | 63 | import uvicorn 64 | 65 | if __name__ == '__main__': 66 | 67 | # 交互式API文档地址: 68 | # http://127.0.0.1:8000/docs/ 69 | # http://127.0.0.1:8000/redoc/ 70 | 71 | uvicorn.run(app, host="0.0.0.0", port=8000) -------------------------------------------------------------------------------- /server/services/practice/assert/Chinook.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/Chinook.db -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/CoH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/CoH.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/agent-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/agent-overview.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/algorithm-distillation-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/algorithm-distillation-results.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/algorithm-distillation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/algorithm-distillation.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/api-bank-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/api-bank-process.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/generative-agents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/generative-agents.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/hugging-gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/hugging-gpt.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/memory.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/mips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/mips.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/react.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/reflexion-exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/reflexion-exp.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/reflexion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/reflexion.png -------------------------------------------------------------------------------- /server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/sea-otter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/LLM Powered Autonomous Agents _ Lil'Log_files/sea-otter.png -------------------------------------------------------------------------------- /server/services/practice/assert/animals.csv: -------------------------------------------------------------------------------- 1 | 名称,学名,特点,作用 2 | 狗,Canis lupus familiaris,忠诚、聪明、社交性强,看家护院、导盲、搜救、警务、情感陪伴 3 | 猫,Felis catus,独立、高冷、善于捕鼠,消灭害鼠、陪伴、缓解压力 4 | 马,Equus ferus caballus,速度快、耐力强、与人类有深厚的合作关系,交通、战马、农业、体育竞技 5 | 牛,Bos taurus,力大耐劳,反刍动物,对人类的经济影响巨大,提供肉、奶、皮革,农业耕作,宗教象征 6 | 羊,Ovis aries,温顺,易于饲养,羊毛和羊奶对人类贡献巨大,羊毛(服装)、羊奶(奶制品)、羊肉(食物来源) 7 | 猪,Sus scrofa domesticus,智商高,好奇心强,适应能力强,主要肉类来源,生物医学研究(猪心脏瓣膜移植) 8 | 鸡,Gallus gallus domesticus,适应性强、繁殖快、能为人类提供大量食物,鸡蛋、鸡肉是全球最主要的蛋白质来源之一 9 | 大象,Elephas spp.,极高的智商,善于记忆,与人类有复杂的关系,文化象征(亚洲神圣动物)、古代战象、运输、旅游 10 | 蜜蜂,Apis mellifera,勤劳、复杂的社会结构,对生态系统至关重要,授粉(维持全球农业)、蜂蜜生产、人类健康(蜂胶、蜂毒疗法) 11 | 老鼠,Rattus spp.,繁殖快,适应力极强,人类生活的“影子伙伴”,害虫(传播疾病)、科学研究的重要实验动物(医学、心理学) 12 | -------------------------------------------------------------------------------- /server/services/practice/assert/animals_all-minilm-33m/02b95cf8-ada8-4129-9d33-297c0bd3860e/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_all-minilm-33m/02b95cf8-ada8-4129-9d33-297c0bd3860e/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_all-minilm-33m/02b95cf8-ada8-4129-9d33-297c0bd3860e/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_all-minilm-33m/02b95cf8-ada8-4129-9d33-297c0bd3860e/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_all-minilm-33m/02b95cf8-ada8-4129-9d33-297c0bd3860e/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_all-minilm-33m/02b95cf8-ada8-4129-9d33-297c0bd3860e/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_all-minilm-33m/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_all-minilm-33m/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/animals_llama3.1/c0bfe162-fc8e-4749-9675-268e4491b1db/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_llama3.1/c0bfe162-fc8e-4749-9675-268e4491b1db/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_llama3.1/c0bfe162-fc8e-4749-9675-268e4491b1db/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_llama3.1/c0bfe162-fc8e-4749-9675-268e4491b1db/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_llama3.1/c0bfe162-fc8e-4749-9675-268e4491b1db/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_llama3.1/c0bfe162-fc8e-4749-9675-268e4491b1db/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_llama3.1/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_llama3.1/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/animals_milkey/m3e/84ad69fd-743e-4966-a092-feaeb3ccb3e2/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_milkey/m3e/84ad69fd-743e-4966-a092-feaeb3ccb3e2/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_milkey/m3e/84ad69fd-743e-4966-a092-feaeb3ccb3e2/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_milkey/m3e/84ad69fd-743e-4966-a092-feaeb3ccb3e2/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_milkey/m3e/84ad69fd-743e-4966-a092-feaeb3ccb3e2/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_milkey/m3e/84ad69fd-743e-4966-a092-feaeb3ccb3e2/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_milkey/m3e/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_milkey/m3e/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/animals_mxbai-embed-large/74493a2e-f195-413c-8d82-237020813700/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_mxbai-embed-large/74493a2e-f195-413c-8d82-237020813700/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_mxbai-embed-large/74493a2e-f195-413c-8d82-237020813700/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_mxbai-embed-large/74493a2e-f195-413c-8d82-237020813700/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_mxbai-embed-large/74493a2e-f195-413c-8d82-237020813700/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_mxbai-embed-large/74493a2e-f195-413c-8d82-237020813700/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_mxbai-embed-large/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_mxbai-embed-large/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/animals_nomic-embed-text/3136354c-7083-4ed7-a434-b15215d7390b/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_nomic-embed-text/3136354c-7083-4ed7-a434-b15215d7390b/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_nomic-embed-text/3136354c-7083-4ed7-a434-b15215d7390b/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_nomic-embed-text/3136354c-7083-4ed7-a434-b15215d7390b/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_nomic-embed-text/3136354c-7083-4ed7-a434-b15215d7390b/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_nomic-embed-text/3136354c-7083-4ed7-a434-b15215d7390b/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_nomic-embed-text/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_nomic-embed-text/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/animals_qwen2.5/129f6664-0acf-4322-81b0-8561abb32505/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_qwen2.5/129f6664-0acf-4322-81b0-8561abb32505/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_qwen2.5/129f6664-0acf-4322-81b0-8561abb32505/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_qwen2.5/129f6664-0acf-4322-81b0-8561abb32505/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_qwen2.5/129f6664-0acf-4322-81b0-8561abb32505/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_qwen2.5/129f6664-0acf-4322-81b0-8561abb32505/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_qwen2.5/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_qwen2.5/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/animals_shaw/dmeta-embedding-zh/7d285bf7-8f9a-4f7d-a328-a9a7757d7bd8/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_shaw/dmeta-embedding-zh/7d285bf7-8f9a-4f7d-a328-a9a7757d7bd8/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_shaw/dmeta-embedding-zh/7d285bf7-8f9a-4f7d-a328-a9a7757d7bd8/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_shaw/dmeta-embedding-zh/7d285bf7-8f9a-4f7d-a328-a9a7757d7bd8/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_shaw/dmeta-embedding-zh/7d285bf7-8f9a-4f7d-a328-a9a7757d7bd8/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_shaw/dmeta-embedding-zh/7d285bf7-8f9a-4f7d-a328-a9a7757d7bd8/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/animals_shaw/dmeta-embedding-zh/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/animals_shaw/dmeta-embedding-zh/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/db_artists_albums/c6e6a8fa-e3e4-4d20-9de1-107800b02132/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_artists_albums/c6e6a8fa-e3e4-4d20-9de1-107800b02132/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/db_artists_albums/c6e6a8fa-e3e4-4d20-9de1-107800b02132/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_artists_albums/c6e6a8fa-e3e4-4d20-9de1-107800b02132/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/db_artists_albums/c6e6a8fa-e3e4-4d20-9de1-107800b02132/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_artists_albums/c6e6a8fa-e3e4-4d20-9de1-107800b02132/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/db_artists_albums/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_artists_albums/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/db_law/4c898430-74c7-47dc-a6a0-f414c514c990/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_law/4c898430-74c7-47dc-a6a0-f414c514c990/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/db_law/4c898430-74c7-47dc-a6a0-f414c514c990/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_law/4c898430-74c7-47dc-a6a0-f414c514c990/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/db_law/4c898430-74c7-47dc-a6a0-f414c514c990/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_law/4c898430-74c7-47dc-a6a0-f414c514c990/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/db_law/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/db_law/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/es_shaw/dmeta-embedding-zh/13a83a34-0f64-44d0-bf44-37007d68f247/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/es_shaw/dmeta-embedding-zh/13a83a34-0f64-44d0-bf44-37007d68f247/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/es_shaw/dmeta-embedding-zh/13a83a34-0f64-44d0-bf44-37007d68f247/length.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/services/practice/assert/es_shaw/dmeta-embedding-zh/13a83a34-0f64-44d0-bf44-37007d68f247/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/es_shaw/dmeta-embedding-zh/13a83a34-0f64-44d0-bf44-37007d68f247/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/es_shaw/dmeta-embedding-zh/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/es_shaw/dmeta-embedding-zh/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/assert/nke-10k-2023.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/nke-10k-2023.pdf -------------------------------------------------------------------------------- /server/services/practice/assert/rag_shaw/dmeta-embedding-zh/427c1a6a-4f48-4648-adc8-9784f8769401/header.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/rag_shaw/dmeta-embedding-zh/427c1a6a-4f48-4648-adc8-9784f8769401/header.bin -------------------------------------------------------------------------------- /server/services/practice/assert/rag_shaw/dmeta-embedding-zh/427c1a6a-4f48-4648-adc8-9784f8769401/length.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/rag_shaw/dmeta-embedding-zh/427c1a6a-4f48-4648-adc8-9784f8769401/length.bin -------------------------------------------------------------------------------- /server/services/practice/assert/rag_shaw/dmeta-embedding-zh/427c1a6a-4f48-4648-adc8-9784f8769401/link_lists.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/rag_shaw/dmeta-embedding-zh/427c1a6a-4f48-4648-adc8-9784f8769401/link_lists.bin -------------------------------------------------------------------------------- /server/services/practice/assert/rag_shaw/dmeta-embedding-zh/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupras/Practical-local-LLM-programming/5c3580e572048d40149d439670d165bbe870bb24/server/services/practice/assert/rag_shaw/dmeta-embedding-zh/chroma.sqlite3 -------------------------------------------------------------------------------- /server/services/practice/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 用WebSocket与大模型聊天 6 | 33 | 34 | 35 | 36 |

WebSocket 聊天测试

37 |
38 | 39 | 40 | 41 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /server/services/practice/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | # @author : 刘立军 4 | # @time : 2025-01-20 5 | # @function: 工具集 6 | # @version : V0.5 7 | # @Description :工具集。 8 | 9 | def show_graph(graph): 10 | from PIL import Image as PILImage 11 | from io import BytesIO 12 | png_data = graph.get_graph().draw_mermaid_png() 13 | img = PILImage.open(BytesIO(png_data)) 14 | img.show() -------------------------------------------------------------------------------- /server/services/practice/vector_store.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | #!/usr/bin/python 4 | # -*- coding:utf-8 -*- 5 | # @author : 刘立军 6 | # @time : 2025-01-07 7 | # @Description: 利用本地大模型测试矢量数据库 8 | # @version : V0.5 9 | 10 | from langchain_core.documents import Document 11 | 12 | # 使用 nomic-embed-text 做嵌入检索很好,使用 llama3.1 效果一般 13 | documents = [ 14 | Document( 15 | page_content="Dogs are great companions, known for their loyalty and friendliness.", 16 | metadata={"source": "mammal-pets-doc"}, 17 | ), 18 | Document( 19 | page_content="Cats are independent pets that often enjoy their own space.", 20 | metadata={"source": "mammal-pets-doc"}, 21 | ), 22 | Document( 23 | page_content="Goldfish are popular pets for beginners, requiring relatively simple care.", 24 | metadata={"source": "fish-pets-doc"}, 25 | ), 26 | Document( 27 | page_content="Parrots are intelligent birds capable of mimicking human speech.", 28 | metadata={"source": "bird-pets-doc"}, 29 | ), 30 | Document( 31 | page_content="Rabbits are social animals that need plenty of space to hop around.", 32 | metadata={"source": "mammal-pets-doc"}, 33 | ), 34 | ] 35 | 36 | # 使用 nomic-embed-text 做嵌入不行,使用 llama3.1 效果还行。 37 | documents_zh = [ 38 | Document( 39 | page_content="狗是极好的伙伴,以忠诚和友好而闻名。", 40 | metadata={"source": "mammal-pets-doc"}, 41 | ), 42 | Document( 43 | page_content="猫是独立的宠物,它们通常喜欢拥有自己的空间。", 44 | metadata={"source": "mammal-pets-doc"}, 45 | ), 46 | Document( 47 | page_content="金鱼是适合初学者饲养的宠物,需要的护理相对简单。", 48 | metadata={"source": "fish-pets-doc"}, 49 | ), 50 | Document( 51 | page_content="鹦鹉是聪明的鸟类,能够模仿人类的语言。", 52 | metadata={"source": "bird-pets-doc"}, 53 | ), 54 | Document( 55 | page_content="兔子是群居动物,需要足够的空间来跳跃。", 56 | metadata={"source": "mammal-pets-doc"}, 57 | ), 58 | ] 59 | 60 | from langchain_chroma import Chroma 61 | from langchain_community.embeddings import OllamaEmbeddings 62 | 63 | # nomic-embed-text llama3.1 EntropyYue/chatglm3 64 | embeddings_model = OllamaEmbeddings(model="nomic-embed-text") 65 | """ 66 | nomic-embed-text: 一个高性能开放嵌入模型,只有27M,具有较大的标记上下文窗口。 67 | 在做英文的嵌入和检索时,明显比llama3.1要好,可惜做中文不行。 68 | """ 69 | 70 | from langchain_ollama import ChatOllama 71 | llm = ChatOllama(model="llama3.1",temperature=0.3,verbose=True) 72 | """ 73 | temperature:用于控制生成语言模型中生成文本的随机性和创造性。 74 | 当temperature值较低时,模型倾向于选择概率较高的词,生成的文本更加保守和可预测,但可能缺乏多样性和创造性。 75 | 当temperature值较高时,模型选择的词更加多样化,可能会生成更加创新和意想不到的文本,但也可能引入语法错误或不相关的内容。 76 | 当需要模型生成明确、唯一的答案时,例如解释某个概念,较低的temperature值更为合适;如果目标是为了产生创意或完成故事,较高的temperature值可能更有助于生成多样化和有趣的文本。 77 | """ 78 | 79 | vectorstore = Chroma.from_documents( 80 | documents_zh, 81 | embedding=embeddings_model, 82 | ) 83 | 84 | def search(): 85 | """ 86 | 矢量检索 87 | """ 88 | 89 | print("search test...") 90 | 91 | # Return documents based on similarity to a string query 92 | r = vectorstore.similarity_search("cat") 93 | print(f'similarity_search:{r}') 94 | 95 | # Return scores 96 | r = vectorstore.similarity_search_with_score("cat") 97 | print(f'similarity_search_with_score:{r}') 98 | 99 | # Return documents based on similarity to an embedded query 100 | embedding = embeddings_model.embed_query("cat") 101 | vectorstore.similarity_search_by_vector(embedding) 102 | 103 | 104 | from langchain_core.runnables import RunnableLambda 105 | 106 | def retriever(): 107 | """ 108 | retriever测试 109 | vectorstore不是Runnable对象,所以不能集成到LangChain中,只能单独调用;Retrievers就是Runnable对象了,可以集成到LangChain。 110 | """ 111 | 112 | print("retriever test...") 113 | 114 | retriever = RunnableLambda(vectorstore.similarity_search).bind(k=1) # select top result 115 | r = retriever.batch(["cat", "shark"]) 116 | print(f'retriever.batch:{r}') 117 | 118 | # as_retriever 119 | retriever = vectorstore.as_retriever( 120 | search_type="similarity", 121 | search_kwargs={"k": 1}, 122 | ) 123 | 124 | r= retriever.batch(["cat", "shark"]) 125 | print(f'as_retriever.batch:{r}') 126 | 127 | from langchain_core.prompts import ChatPromptTemplate 128 | from langchain_core.runnables import RunnablePassthrough 129 | 130 | def RAG(question): 131 | """ 132 | RAG测试 133 | """ 134 | message = """ 135 | Answer this question using the provided context only. 136 | 137 | {question} 138 | 139 | Context: 140 | {context} 141 | """ 142 | prompt = ChatPromptTemplate.from_messages([("human", message)]) 143 | 144 | retriever = vectorstore.as_retriever( 145 | search_type="similarity", 146 | search_kwargs={"k": 1}, 147 | ) 148 | 149 | rag_chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm 150 | response = rag_chain.invoke({"question": question}) 151 | 152 | # print(response.content) 153 | return response.content 154 | 155 | if __name__ == '__main__': 156 | 157 | #search() 158 | #retriever() 159 | 160 | # 使用 nomic-embed-text 可以正确处理,使用llama3.1 失败。 161 | print(RAG("tell me about cats")) 162 | 163 | -------------------------------------------------------------------------------- /server/services/response.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | #!/usr/bin/python 4 | # -*- coding:utf-8 -*- 5 | # @author : 刘立军 6 | # @time : 2024-12-27 7 | # @Description: 定义API的标准响应 8 | # @version : V0.5 9 | 10 | from pydantic import BaseModel, Field 11 | 12 | from enum import Enum 13 | 14 | # 操作结果枚举 15 | class code_enum(str,Enum): 16 | OK = 'ok' 17 | ERR = 'error' 18 | 19 | # API返回的消息体 20 | class response_model(BaseModel): 21 | code: code_enum = Field(description="操作结果" ) 22 | desc: str = Field(description="具体内容" ) -------------------------------------------------------------------------------- /server/services/start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo start translation service 3 | 4 | REM 修改代码页,防止在windows中执行时出现中文乱码 5 | chcp 65001 6 | 7 | REM 切换到当前批处理文件所在的目录 8 | cd /d %~dp0 9 | 10 | echo 激活虚拟环境... 11 | call ..\..\.venv\Scripts\activate 12 | 13 | echo 启动大语言模型服务... 14 | python api.py 15 | 16 | pause -------------------------------------------------------------------------------- /server/services/translation.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | #!/usr/bin/python 4 | # -*- coding:utf-8 -*- 5 | # @author : 刘立军 6 | # @time : 2025-01-06 7 | # @Description: 使用FastAPI做langchain的API 8 | # @version : V0.5 9 | 10 | from langchain_core.prompts import ChatPromptTemplate 11 | from langchain_core.output_parsers import StrOutputParser 12 | from langchain_ollama.llms import OllamaLLM 13 | 14 | def translate(language,text): 15 | # 1. 创建提示词模板 16 | system_template ='''You are an experienced translator,please use concise words to complete the translation. 17 | Please only give the translation results. 18 | ### 19 | Translate the following into {language}:''' 20 | prompt_template = ChatPromptTemplate.from_messages([ 21 | ('system', system_template), 22 | ('user', '{text}') 23 | ]) 24 | 25 | # 2. 创建本地大模型 26 | model = OllamaLLM(model="llama3.1") 27 | 28 | # 3. 创建解析器 29 | parser = StrOutputParser() 30 | 31 | # 4. 创建链 32 | chain = prompt_template | model | parser 33 | 34 | #5. 调用链 35 | result = chain.invoke({"language": language,"text":text}) 36 | 37 | return result 38 | 39 | if __name__ == '__main__': 40 | print(translate("简体中文","You are an experienced translator, please use concise words to complete the translation.")) --------------------------------------------------------------------------------