├── .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."))
--------------------------------------------------------------------------------