【一】大模型调用及相关准备
1.使用阿里云百炼的模型
2.使用Ollama进行本地模型部署
简单说:在自己电脑上部署和运行大模型,由自己电脑的硬件提供算力支撑模型的运行,所以不需要像阿里云百炼那样担心自己Token不够用,消耗的只是自己的电力。
(可能会有点慢,要根据自己电脑的显卡性能选择合适的参数;不是标准的大模型【蒸馏模型】)
3.OpenAI大致使用流程
4.OpenAI的流式输出
目的:使用户获得更好的使用体验
5.OpenAI附带历史消息调用模型
【二】提示词优化工程
from openai import OpenAI
import json
client = OpenAI(
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
schema = ['日期', '股票名称', '开盘价', '收盘价', '成交量']
examples_data = [ # 示例数据
{
"content": "2023-01-10,股市震荡。股票强大科技A股今日开盘价100人民币,一度飙升至105人民币,随后回落至98人民币,最终以102人民币收盘,成交量达到520000。",
"answers": {
"日期": "2023-01-10",
"股票名称": "强大科技A股",
"开盘价": "100人民币",
"收盘价": "102人民币",
"成交量": "520000"
}
},
{
"content": "2024-05-16,股市利好。股票英伟达美股今日开盘价105美元,一度飙升至109美元,随后回落至100美元,最终以116美元收盘,成交量达到3560000。",
"answers": {
"日期": "2024-05-16",
"股票名称": "英伟达美股",
"开盘价": "105美元",
"收盘价": "116美元",
"成交量": "3560000"
}
}
]
questions = [ # 提问问题
"2025-06-16,股市利好。股票传智教育A股今日开盘价66人民币,一度飙升至70人民币,随后回落至65人民币,最终以68人民币收盘,成交量达到123000。",
"2025-06-06,股市利好。股票黑马程序员A股今日开盘价200人民币,一度飙升至211人民币,随后回落至201人民币,最终以206人民币收盘。"
]
messages = [
{"role": "system", "content": f"你帮我完成信息抽取,我给你句子,你抽取{schema}信息,按JSON字符串输出,如果某些信息不存在,用'原文未提及'表示,请参考如下示例:"}
]
for example in examples_data:
messages.append(
{"role": "user", "content": example["content"]}
)
messages.append(
{"role": "assistant", "content": json.dumps(example["answers"], ensure_ascii=False)}
)
for q in questions:
response = client.chat.completions.create(
model="deepseek-v3",
messages=messages + [{"role": "user", "content": f"按照上述的示例,现在抽取这个句子的信息:{q}"}]
)
print(response.choices[0].message.content)
37行-43行是整理提示词数据,只输出整理结果就为:
[
{"role": "system", "content": f"你帮我完成信息抽取,我给你句子,你抽取{schema}信息,按JSON字符串输出,如果某些信息不存在,用'原文未提及'表示,请参考如下示例:"},
{"role": "user", "content": "2023-01-10,股市震荡。股票强大科技A股今日开盘价100人民币,一度飙升至105人民币,随后回落至98人民币,最终以102人民币收盘,成交量达到520000。"},
{"role": "assistant", "content": '{"日期":"2023-01-10","股票名称":"强大科技A股","开盘价":"100人民币","收盘价":"102人民币","成交量":"520000"}'},
{"role": "user", "content": "2024-05-16,股市利好。股票英伟达美股今日开盘价105美元,一度飙升至109美元,随后回落至100美元,最终以116美元收盘,成交量达到3560000。"},
{"role": "assistant", "content": '{"日期":"2024-05-16","股票名称":"英伟达美股","开盘价":"105美元","收盘价":"116美元","成交量":"3560000"}'},
{"role": "user", "content": f"按照上述示例,现在抽取这个句子的信息:{要抽取的句子文本}"}]}
]
【三】RAG开发-LangChain
1.LangChain
2.RAG介绍
RAG核心工作的的两个流程:
向量的基础概念:
扩展:余弦相似度计算:
代码:
import numpy as np
"""
计算两个向量的余弦相似度(衡量方向相似性,剔除长度影响)
参数:
vec_a (np.array): 向量A
vec_b (np.array): 向量B
返回:
float: 余弦相似度结果(范围[-1,1],越接近1方向越一致)
公式:
cos_sim = (vec_a · vec_b) / (||vec_a|| × ||vec_b||)
拆解:
1. 点积:vec_a · vec_b = vec_a[0]×vec_b[0] + vec_a[1]×vec_b[1] + ... + vec_a[n]×vec_b[n]
2. 模长:||vec_a|| = √(vec_a[0]² + vec_a[1]² + ... + vec_a[n]²)
3. 模长:||vec_b|| = √(vec_b[0]² + vec_b[1]² + ... + vec_b[n]²)
A: [0.5, 0.5]
B: [0.7, 0.7]
C: [0.7, 0.5]
D: [-0.6, -0.5]
"""
def get_dot(vec_a, vec_b):
"""计算2个向量的点积,2个向量同维度数字乘积之和"""
if len(vec_a) != len(vec_b):
raise ValueError("2个向量必须维度数量相同")
dot_sum = 0
for a, b in zip(vec_a, vec_b):
dot_sum += a * b
return dot_sum
def get_norm(vec):
"""计算单个向量的模长:对向量的每个数字求平方在求和在开根号"""
sum_square = 0
for v in vec:
sum_square += v * v
# numpy sqrt函数完成开根号
return np.sqrt(sum_square)
def cosine_similarity(vec_a, vec_b):
"""余弦相似度:2个向量的点积 除以 2个向量模长的乘积"""
result = get_dot(vec_a, vec_b) / (get_norm(vec_a) * get_norm(vec_b))
return result
if __name__ == '__main__':
vec_a = [0.5, 0.5]
vec_b = [0.7, 0.7]
vec_c = [0.7, 0.5]
vec_d = [-0.6, -0.5]
print("ab:", cosine_similarity(vec_a, vec_b))
print("ac:", cosine_similarity(vec_a, vec_c))
print("ad:", cosine_similarity(vec_a, vec_d))
3.LangChain调用大语言模型
# langchain_community
from langchain_community.llms.tongyi import Tongyi
# 不用qwen3-max,因为qwen3-max是聊天模型,qwen-max是大语言模型
model = Tongyi(model="qwen-max")
# 调用invoke向模型提问
res = model.invoke(input="你是谁呀能做什么?")
print(res)
4.LangChain模型流式输出(用Ollama版)
from langchain_ollama import OllamaLLM
model = OllamaLLM(model="qwen3:4b")
res = model.stream(input="你是谁呀能做什么?")
for chunk in res:
print(chunk, end="", flush=True)
5.LangChain调用聊天模型
6.LangChain消息的简化升级
下面的内容被替换成了元组形式
# 准备消息列表
messages = [
# (角色,内容) 角色:system/human/ai
("system", "你是一个边塞诗人。"),
("human", "写一首唐诗。"),
("ai", "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦。"),
("human", "按照你上一个回复的格式,在写一首唐诗。")
]
前一种调用方式是静态的,这一种方式是动态的
所以此方式有一个好处:支持内部填充{变量}占位
7.LangChain调用文本嵌入模型(文本向量化)
8.zero-shot提示词模板
from langchain_core.prompts import PromptTemplate
from langchain_community.llms.tongyi import Tongyi
prompt_template = PromptTemplate.from_template(
"我的邻居姓{lastname}, 刚生了{gender}, 你帮我起个名字,简单回答。"
)
model = Tongyi(model="qwen-max")
chain = prompt_template | model
res = chain.invoke(input={"lastname": "张", "gender": "女儿"})
print(res)
9.FewShot提示词模板
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_community.llms.tongyi import Tongyi
# 示例的模板
example_template = PromptTemplate.from_template("单词:{word}, 反义词:{antonym}")
# 示例的动态数据注入 要求是list内部套字典
examples_data = [
{"word": "大", "antonym": "小"},
{"word": "上", "antonym": "下"},
]
few_shot_template = FewShotPromptTemplate(
example_prompt=example_template, # 示例数据的模板
examples=examples_data, # 示例的数据(用来注入动态数据的),list内套字典
prefix="告知我单词的反义词,我提供如下的示例:", # 示例之前的提示词
suffix="基于前面的示例告知我,{input_word}的反义词是?", # 示例之后的提示词
input_variables=['input_word'] # 声明在前缀或后缀中所需要注入的变量名
)
prompt_text = few_shot_template.invoke(input={"input_word": "左"}).to_string()
print(prompt_text)
model = Tongyi(model="qwen-max")
print(model.invoke(input=prompt_text))
10.format和invoke区别
代码示例
template = PromptTemplate.from_template("我的邻居是:{lastname},最喜欢:{hobby}")
res = template.format(lastname="张大明", hobby="钓鱼")
print(res, type(res))
res2 = template.invoke({"lastname": "周杰轮", "hobby": "唱歌"})
print(res2, type(res2))
输出结果:
11.ChatPromptTemplate的使用
代码示例:
12.Chain的基础使用
代码实例:
model = ChatTongyi(model="qwen3-max")
# 组成链,要求每一个组件都是Runnable接口的子类
chain = chat_prompt_template | model
# 通过stream流式输出
for chunk in chain.stream({"history": history_data}):
print(chunk.content, end="", flush=True)
13.StrOutputParse字符串输出解析器
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi
parser = StrOutputParser()
model = ChatTongyi(model="qwen3-max")
prompt = PromptTemplate.from_template(
"我邻居姓:{lastname},刚生了{gender},请起名,仅告知我名字无需其它内容。"
)
chain = prompt | model | parser | model
res: str = chain.invoke({"lastname": "张", "gender": "女儿"})
print(res)
14.JsonOutputParser和多模型执行链
15.自定义函数加入此多模型执行链
16.Memory临时会话记忆
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import InMemoryChatMessageHistory
model = ChatTongyi(model="qwen3-max")
prompt = PromptTemplate.from_template(
"你需要根据会话历史回应用户问题。对话历史:{chat_history},用户提问:{input},请回答"
)
str_parser = StrOutputParser()
base_chain = prompt | model | str_parser
store = {} # key就是session,value就是InMemoryChatMessageHistory类对象
# 实现通过会话id获取InMemoryChatMessageHistory类对象
def get_history(session_id):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 创建一个新的链,对原有链增强功能:自动附加历史消息
conversation_chain = RunnableWithMessageHistory(
base_chain, # 被增强的原有chain
get_history, # 通过会话id获取InMemoryChatMessageHistory类对象
input_messages_key="input", # 表示用户输入在模板中的占位符
history_messages_key="chat_history" # 表示用户输入在模板中的占位符
)
if __name__ == '__main__':
# 固定格式,添加LangChain的配置,为当前程序配置所属的session_id
session_config = {
"configurable": {
"session_id": "user_001"
}
}
res = conversation_chain.invoke({"input": "小明有2个猫"}, session_config)
print("第1次执行:", res)
#
res = conversation_chain.invoke({"input": "小刚有1只狗"}, session_config)
print("第2次执行:", res)
res = conversation_chain.invoke({"input": "总共有几个宠物"}, session_config)
print("第3次执行:", res)
17.CSVloader
18.JSONLoader
from langchain_community.document_loaders import JSONLoader
loader = JSONLoader(
file_path="./data/stu_json_lines.json",
jq_schema=".name",
text_content=False, # 告知JSONLoader 我抽取的内容不是字符串
json_lines=True # 告知JSONLoader 这是一个JSONLines文件(每一行都是一个独立的标准JSON)
)
document = loader.load()
print(document)
19.PyPDFLoader
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader(
file_path="./data/pdf2.pdf",
mode="single", # 默认是page模式,每个页面形成一个Document文档对象,
# single模式,不管有多少页,只返回1个Document对象
password="itheima"
)
i = 0
for doc in loader.lazy_load():
i += 1
print(doc)
print("="*20, i)
20.TextLoader和文档分割器
一:TextLoader:
二:文档分割器:
21.VectorStores向量存储
22.基于向量检索构建提示词
"""
提示词:用户的提问 + 向量库中检索到的参考资料
"""
from langchain_community.chat_models import ChatTongyi
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
def print_prompt(prompt):
print(prompt.to_string())
print("=" * 20)
return prompt
model = ChatTongyi(model="qwen3-max")
prompt = ChatPromptTemplate.from_messages(
[
("system", "以我提供的已知参考资料为主,简洁和专业的回答用户问题。参考资料:{context}。"),
("user", "用户提问:{input}")
]
)
vector_store = InMemoryVectorStore(embedding=DashScopeEmbeddings(model="text-embedding-v4"))
# 准备一下资料(向量库的数据)
# add_texts 传入一个 list[str]
vector_store.add_texts(
["减肥就是要少吃多练", "在减脂期间吃东西很重要,清淡少油控制卡路里摄入并运动起来", "跑步是很好的运动哦"])
input_text = "怎么减肥?"
# 检索向量库
result = vector_store.similarity_search(input_text, 2)
reference_text = "["
for doc in result:
reference_text += doc.page_content
reference_text += "]"
chain = prompt | print_prompt | model | StrOutputParser()
res = chain.invoke({"input": input_text, "context": reference_text})
print(res)
23.RunnablePassThrough的使用
为什么要用它:
(一)原因:
保留原始数据(“我要带着旧数据去下一站”)
假设你的链需要同时使用 用户原始输入 和 中间处理结果。
如果不加它,数据经过一步处理后,原始输入就丢了。
例子:
你想做一个链:先让用户输入一个问题,然后让模型回答,最后把 “用户的问题” 和 “模型的回答” 一起打印出来。
❌ 错误写法(丢失了原始问题):
# 输入: {"question": "你好"}
# prompt | model --> 输出变成了字符串 "你好!有什么可以帮你?"
# 此时原始的 {"question": "你好"} 已经丢了!
chain = prompt | model
✅ 正确写法(使用 RunnablePassthrough 保留上下文):
from langchain_core.runnables import RunnablePassthrough
# 结构:{ "question": 原始问题, "answer": 模型回答 }
chain = (
RunnablePassthrough.assign(answer=prompt | model)
)
# 输入: {"question": "你好"}
# 输出: {"question": "你好", "answer": "你好!有什么可以帮你?"}
(二)原因:
修复数据结构不匹配(“我要把字典原样传给下一个需要字典的步骤”)
first_prompt | model输出的是一个 字符串 (名字)。my_func把它变成了 字典{"name": "顾婉儿"}。second_prompt需要这个字典。
如果你的链中间某一步不需要处理,只是想把数据“搬运”到下一步,或者为了调试想看看中间数据长什么样,就可以插一个 RunnablePassthrough()。
更典型的用法:并行处理
假设你要同时做两件事:
- 把用户输入翻译成英文。
- 把用户输入翻译成法文。
最后把两个结果合并。
from langchain_core.runnables import RunnablePassthrough
# 输入: "Hello"
chain = (
RunnablePassthrough() # 1. 原样传递 "Hello"
| {
"english": translate_en_prompt | model, # 2. 分支1:翻译英文
"french": translate_fr_prompt | model, # 3. 分支2:翻译法文
# 注意:这里字典的值是其他的 Runnable,LangChain 会自动并行执行它们
# 而键 "original" 需要原始输入,所以通常配合 assign 使用
}
)
【四】RAG项目
1.项目总述
最终效果: