langchain教程-11.RAG管道/多轮对话RAG
前言
该系列教程的代码: https://github.com/shar-pen/Langchain-MiniTutorial
我主要参考 langchain 官方教程, 有选择性的记录了一下学习内容
这是教程清单
- 1.初试langchain
- 2.prompt
- 3.OutputParser/输出解析
- 4.model/vllm模型部署和langchain调用
- 5.DocumentLoader/多种文档加载器
- 6.TextSplitter/文档切分
- 7.Embedding/文本向量化
- 8.VectorStore/向量数据库存储和检索
- 9.Retriever/检索器
- 10.Reranker/文档重排序
- 11.RAG管道/多轮对话RAG
- 12.Agent/工具定义/Agent调用工具/Agentic RAG
理解 RAG 的基本结构
RAG(检索增强生成)流程中的预处理阶段涉及四个步骤,用于加载、拆分、嵌入和存储文档到向量数据库(Vector DB)。
- 预处理 ## 步骤 1 到 4
- 步骤 1:文档加载:加载文档内容。
- 步骤 2:文本拆分:根据特定标准将文档拆分成多个块。
- 步骤 3:嵌入:为拆分后的文档块生成嵌入,并为存储做准备。
- 步骤 4:向量数据库存储:将生成的嵌入存储到向量数据库中。
以上可以称为 Indexing。一个收集数据并对其进行索引的管道。这个过程通常是在离线进行的。
- RAG 执行(运行时) - 步骤 5 到 8
- 步骤 5:检索器:定义一个检索器,根据输入查询从数据库中获取结果。检索器使用搜索算法,通常分为密集型和稀疏型:
- 密集型:基于相似度的检索。
- 稀疏型:基于关键词的检索。
- 步骤 6:提示生成:为执行 RAG 创建一个提示。提示中的
context
包含从文档中检索到的内容。通过提示工程,可以指定回答的格式。 - 步骤 7:大语言模型(LLM):定义使用的大语言模型(例如 GPT-3.5、GPT-4 或 Claude)。
- 步骤 8:链式连接:创建一个链,将提示、大语言模型和输出连接起来。
以上可称为 Retrieval and Generation 。实际的RAG链实时处理用户查询,从索引中检索相关数据,并将其传递给模型。
RAG 基本 pipeline
以下是理解 RAG(检索增强生成)基本结构的框架代码。
每个模块的内容可以根据具体场景进行调整,从而允许逐步改进结构以适应文档。
(在每个步骤中,可以应用不同的选项或新技术。)
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
步骤 1:文档加载:加载文档内容。
# Step 1: Load Documents
loader = TextLoader("data/appendix-keywords.txt", encoding="utf-8")
docs = loader.load()
print(f"Number of pages in the document: {len(docs)}")
Number of pages in the document: 1
步骤 2:文本拆分:根据特定标准将文档拆分成多个块。
# Step 2: Split Documents
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_documents = text_splitter.split_documents(docs)
print(f"Number of split chunks: {len(split_documents)}")
Number of split chunks: 30
步骤 3:嵌入:为拆分后的文档块生成嵌入,并为存储做准备。
# Step 3: Generate Embeddings
embeddings = OpenAIEmbeddings(model="bge-m3",base_url='http://localhost:9997/v1',api_key='cannot be empty',# dimensions=1024,
)
步骤 4:向量数据库存储:将生成的嵌入存储到向量数据库中。
# Step 4: Create and Save the Database
# Create a vector store.
vectorstore = FAISS.from_documents(documents=split_documents, embedding=embeddings)
# test similarity_search
for doc in vectorstore.similarity_search("URBAN MOBILITY", k=1):print(doc.page_content)
步骤 5:检索器:定义一个检索器,根据输入查询从数据库中获取结果。检索器使用搜索算法,通常分为密集型和稀疏型:
# Step 5: Create Retriever
# Search and retrieve information contained in the documents.
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={'k': 1})
retriever.invoke("What is the phased implementation timeline for the EU AI Act?")
步骤 6:提示生成:为执行 RAG 创建一个提示。提示中的context
包含从文档中检索到的内容。通过提示工程,可以指定回答的格式。
# Step 6: Create Prompt
prompt = PromptTemplate.from_template("""You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know. #Context:
{context}#Question:
{question}#Answer:"""
)
prompt
PromptTemplate(input_variables=['context', 'question'], input_types={}, partial_variables={}, template="You are an assistant for question-answering tasks. \nUse the following pieces of retrieved context to answer the question. \nIf you don't know the answer, just say that you don't know. \n\n#Context: \n{context}\n\n#Question:\n{question}\n\n#Answer:")
步骤 7:大语言模型(LLM):定义使用的大语言模型(例如 GPT-3.5、GPT-4 或 Claude)。
# Step 7: Load LLM
llm = ChatOpenAI(base_url='http://localhost:5551/v1',api_key='EMPTY',model_name='Qwen2.5-7B-Instruct',temperature=0.2,
)
步骤 8:链式连接:创建一个链,将提示、大语言模型和输出连接起来。
# Step 8: Create Chain
chain = ({"context": retriever, "question": RunnablePassthrough()}| prompt| llm| StrOutputParser()
)
# Run Chain
# Input a query about the document and print the response.
question = "Where has the application of AI in healthcare been confined to so far?"
response = chain.invoke(question)
print(response)
完整版本
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI, OpenAIEmbeddings# Step 1: Load Documents
loader = TextLoader("data/appendix-keywords.txt", encoding="utf-8")
docs = loader.load()
print(f"Number of pages in the document: {len(docs)}")# Step 2: Split Documents
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_documents = text_splitter.split_documents(docs)
print(f"Number of split chunks: {len(split_documents)}")# Step 3: Generate Embeddings
embeddings = OpenAIEmbeddings(model="bge-m3",base_url='http://localhost:9997/v1',api_key='cannot be empty',# dimensions=1024,
)# Step 4: Create and Save the Database
# Create a vector store.
vectorstore = FAISS.from_documents(documents=split_documents, embedding=embeddings)# Step 5: Create Retriever
# Search and retrieve information contained in the documents.
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={'k': 1})# Step 6: Create Prompt
prompt = PromptTemplate.from_template("""You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know. #Context:
{context}#Question:
{question}#Answer:"""
)# Step 7: Load LLM
llm = ChatOpenAI(base_url='http://localhost:5551/v1',api_key='EMPTY',model_name='Qwen2.5-7B-Instruct',temperature=0.2,
)# Step 8: Create Chain
chain = ({"context": retriever, "question": RunnablePassthrough()}| prompt| llm| StrOutputParser()
)# Run Chain
# Input a query about the document and print the response.
question = "Where has the application of AI in healthcare been confined to so far?"
response = chain.invoke(question)
print(response)
多轮对话
创建一个记住之前对话的链条, 将对话历史添加到 chain
- 使用
MessagesPlaceholder
来包含对话历史。 - 定义一个提示语,接收用户输入的问题。
- 创建一个
ChatOpenAI
实例,使用 OpenAI 的ChatGPT
模型。 - 通过将提示语、语言模型和输出解析器连接起来,构建链条。
- 使用
StrOutputParser
将模型的输出转换为字符串。
基础问答框架
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser# Defining the prompt
prompt = ChatPromptTemplate.from_messages([("system","You are a Question-Answering chatbot. Please provide an answer to the given question.",),# Please use the key 'chat_history' for conversation history without changing it if possible!MessagesPlaceholder(variable_name="chat_history"),("human", "#Question:\n{question}"), # Use user input as a variable]
)# Generating an LLM
llm = ChatOpenAI(base_url='http://localhost:5551/v1',api_key='EMPTY',model_name='Qwen2.5-7B-Instruct',temperature=0.2,
)# Creating a regular Chain
chain = prompt | llm | StrOutputParser()
创建可管理对话历史的 chain
创建一个记录对话的链条 (chain_with_history)
- 创建一个字典来存储会话记录。
- 定义一个函数,根据会话 ID 检索会话记录。如果会话 ID 不在存储中,创建一个新的
ChatMessageHistory
对象。 - 创建一个
RunnableWithMessageHistory
对象来管理对话历史。
# Dictionary to store session records
store = {}# Function to retrieve session records based on session ID
def get_session_history(session_ids):print(f"[Conversation Session ID]: {session_ids}")if session_ids not in store: # If the session ID is not in the store# Create a new ChatMessageHistory object and save it to the storestore[session_ids] = ChatMessageHistory()return store[session_ids] # Return the session history for the corresponding session IDchain_with_history = RunnableWithMessageHistory(chain,get_session_history, # Function to retrieve session historyinput_messages_key="question", # Key for the template variable that will contain the user's questionhistory_messages_key="chat_history", # Key for the history messages
)
多轮 QA 测试
chain_with_history.invoke(# Input question{"question": "My name is Jack."},# Record the conversation based on the session ID.config={"configurable": {"session_id": "abc123"}},
)
[Conversation Session ID]: abc123'Hello Jack! Nice to meet you. How can I assist you today?'
chain_with_history.invoke(# Input question{"question": "What is my name?"},# Record the conversation based on the session ID.config={"configurable": {"session_id": "abc123"}},
)
[Conversation Session ID]: abc123'Your name is Jack.'
print(store['abc123'])
Human: My name is Jack.
AI: Hello Jack! Nice to meet you. How can I assist you today?
Human: What is my name?
AI: Your name is Jack.
带多轮对话的 RAG 框架
基础问答框架
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PDFPlumberLoader
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from operator import itemgetter# Step 1: Load Documents
loader = PDFPlumberLoader("data/A European Approach to Artificial Intelligence - A Policy Perspective.pdf")
docs = loader.load()# Step 2: Split Documents
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_documents = text_splitter.split_documents(docs)
print(f"Number of split chunks: {len(split_documents)}")# Step 3: Generate Embeddings
embeddings = OpenAIEmbeddings(model="bge-m3",base_url='http://localhost:9997/v1',api_key='cannot be empty',# dimensions=1024,
)# Step 4: Create and Save the Database
# Create a vector store.
vectorstore = FAISS.from_documents(documents=split_documents, embedding=embeddings)# Step 5: Create Retriever
# Search and retrieve information contained in the documents.
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={'k': 1})# Step 6: Create Prompt
prompt = PromptTemplate.from_template("""You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know.#Previous Chat History:
{chat_history}#Question:
{question} #Context:
{context} #Answer:"""
)# Step 7: Load LLM
llm = ChatOpenAI(base_url='http://localhost:5551/v1',api_key='EMPTY',model_name='Qwen2.5-7B-Instruct',temperature=0.2,
)# Step 8: Create Chain
chain = ({"context": itemgetter("question") | retriever,"question": itemgetter("question"),"chat_history": itemgetter("chat_history"),}| prompt| llm| StrOutputParser()
)
Number of split chunks: 171
创建可管理对话历史的 chain
# Dictionary to store session records
store = {}# Function to retrieve session records based on session ID
def get_session_history(session_ids):print(f"[Conversation Session ID]: {session_ids}")if session_ids not in store: # If the session ID is not in the store# Create a new ChatMessageHistory object and save it to the storestore[session_ids] = ChatMessageHistory()return store[session_ids] # Create a RAG chain that records conversations
rag_with_history = RunnableWithMessageHistory(chain,get_session_history, # Function to retrieve session historyinput_messages_key="question", # Key for the template variable that will contain the user's questionhistory_messages_key="chat_history", # Key for the history messages
)
多轮 QA 测试
rag_with_history.invoke(# Input question{"question": "What are the three key components necessary to achieve 'trustworthy AI' in the European approach to AI policy?"},# Record the conversation based on the session ID.config={"configurable": {"session_id": "rag123"}},
)
[Conversation Session ID]: rag123"Based on the provided context, the three key components necessary to achieve 'trustworthy AI' in the European approach to AI policy are not explicitly stated. The context provided seems to be about improving public services and creating a secure, trusted data space, but it does not directly address the components of trustworthy AI. To provide an accurate answer, I would need more specific information from the document."
rag_with_history.invoke(# Input question{"question": "Please translate the previous answer into Spanish."},# Record the conversation based on the session ID.config={"configurable": {"session_id": "rag123"}},
)
[Conversation Session ID]: rag123'No se especifican los tres componentes clave necesarios para lograr la "IA confiable" en la aproximación europea a la política de IA en el contexto proporcionado. El contexto parece estar sobre mejorar los servicios públicos y crear un espacio de datos seguro y confiable, pero no aborda directamente los componentes de la IA confiable. Para proporcionar una respuesta precisa, necesitaría más información específica del documento.'
相关文章:
langchain教程-11.RAG管道/多轮对话RAG
前言 该系列教程的代码: https://github.com/shar-pen/Langchain-MiniTutorial 我主要参考 langchain 官方教程, 有选择性的记录了一下学习内容 这是教程清单 1.初试langchain2.prompt3.OutputParser/输出解析4.model/vllm模型部署和langchain调用5.DocumentLoader/多种文档…...

Postgresql的三种备份方式_postgresql备份
这种方式可以在数据库正在使用的时候进行完整一致的备份,并不阻塞其它用户对数据库的访问。它会产生一个脚本文件,里面包含备份开始时,已创建的各种数据库对象的SQL语句和每个表中的数据。可以使用数据库提供的工具pg_dumpall和pg_dump来进行…...
WebAssembly:前后端开发的未来利器
引言 在互联网的世界里,前端和后端开发一直是两块重要的领域。而 JavaScript 长期以来是前端的霸主,后端则有各种语言诸如 Java、Python、Node.js、Go 等等。然而,近年来一个名为 WebAssembly (Wasm) 的技术正在逐渐改变这一格局。它的高性能…...

Mac下使用brew安装go 以及遇到的问题
首先按照网上找到的命令进行安装 brew install go 打开终端输入go version,查看安装的go版本 go version 配置环境变量 查看go的环境变量配置: go env 事实上安装好后的go已经可以使用了。 在home/go下新建src/hello目录,在该目录中新建…...
【Leetcode 每日一题】47. 全排列 II
问题背景 给定一个可包含重复数字的序列 n u m s nums nums,按任意顺序 返回所有不重复的全排列。 数据约束 1 ≤ n u m s . l e n g t h ≤ 8 1 \le nums.length \le 8 1≤nums.length≤8 − 10 ≤ n u m s [ i ] ≤ 10 -10 \le nums[i] \le 10 −10≤nums[i]≤…...

车型检测7种YOLOV8
车型检测7种YOLOV8,采用YOLOV8NANO训练,得到PT模型,转换成ONNX,然后OPENCV的DNN调用,支持C,python,android开发 车型检测7种YOLOV8...

C语言按位取反【~】详解,含原码反码补码的0基础讲解【原码反码补码严格意义上来说属于计算机组成原理的范畴,不过这也是学好编程初级阶段的必修课】
目录 概述【适合0基础看的简要描述】: 上述加粗下划线的内容提取版: 从上述概述中提取的核心知识点,需背诵: 整数【包含整数,负整数和0】的原码反码补码相互转换的过程图示: 过程详细刨析:…...

面对全球化的泼天流量,出海企业如何观测多地域网络质量?
作者:俞嵩、白玙 泼天富贵背后,技术挑战接踵而至 随着全球化进程,出海、全球化成为很多 Toc 产品的必经之路,保障不同地域、不同网络环境的一致的用户体验成为全球化应用的不得不面对的问题。在跨运营商、跨地域的网络环境中&am…...
『python爬虫』获取免费IP代理 搭建自己的ip代理池(保姆级图文)
目录 1. 环境搭建2. 获取爬虫ip3. 启动本地flask api接口服务4. 封装方法例子代码5. 自定义抓取免费ip的代理站规则6. 自定义规则示例总结欢迎关注 『python爬虫』 专栏,持续更新中 欢迎关注 『python爬虫』 专栏,持续更新中 1. 环境搭建 这边建议python3.7-3.11版本,redis …...
21.命令模式(Command Pattern)
定义 命令模式(Command Pattern) 是一种行为型设计模式,它将请求封装成一个对象,从而使您可以使用不同的请求、队列、日志请求以及支持撤销操作等功能。命令模式通过将请求(命令)封装成对象,使…...

深入探索 C++17 特征变量模板 (xxx_v)
文章目录 一、C++类型特征的前世今生二、C++17特征变量模板闪亮登场三、常见特征变量模板的实际应用(一)基本类型判断(二)指针与引用判断四、在模板元编程中的关键作用五、总结与展望在C++的持续演进中,C++17带来了许多令人眼前一亮的特性,其中特征变量模板(xxx_v)以其…...

【Day32 LeetCode】动态规划DP Ⅴ 完全背包
一、动态规划DP Ⅴ 完全背包 1、完全背包理论 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和…...

景区如何打造高质量游览观光车,提高人流量?
景区如何打造高质量游览观光车,提高人流量? 在旅游业蓬勃发展的今天,各大景区迎来了前所未有的游客热潮。尤其是在春节、五一、国庆等节假日期间,游客数量更是激增。作为景区交通的重要组成部分,游览观光车不仅承载着…...

【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题
【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题 零、起因 最近在使用Ubuntu虚拟机编译ARM程序,解压ARM的GCC后想要启动,报“没有那个文件或目录”,但是文件确实存在,环境配置也检查过了没问题,本文记…...

蓝桥杯之c++入门(六)【string(practice)】
目录 练习1:标题统计方法1:一次性读取整行字符,然后统计方法2:按照单词读取小提示: 练习2:石头剪子布练习3:密码翻译练习4:文字处理软件练习5:单词的长度练习6࿱…...

go的sync包学习
包含了sync.Mutex,sync.RWMutex,sync.Cond,sync.Map,sync.Once等demo sync.Mutex //讲解mutex import ("fmt""math/rand""sync""time" )type Toilet struct {m sync.Mutex } type Person struct {Name string }var DateTime "2…...
互联网上常见的,ip地址泛播什么意思
互联网上常见的,ip地址泛播什么意思! 泛播通过将IP地址广播发送到网络中的所有设备,使得这些设备能够接收到相关信息。例如,DHCP服务器在局域网中广播提供IP地址的请求,以便新设备能够获取一个可用的IP地址。此外&…...

Linux/C高级(精讲)----shell结构语句、shell数组
shell脚本 功能性语句 test 可测试对象三种:字符串 整数 文件属性 每种测试对象都有若干测试操作符 1)字符串的测试: s1 s2 测试两个字符串的内容是否完全一样 s1 ! s2 测试两个字符串的内容是否有差异 -z s1 测试s1 字符串的长度是…...
14.kafka开机自启动配置
要在Linux(RHEL7.7)系统中设置kafka开机自启动,可以创建一个系统服务单元文件。以下是为详细配置部署,假设你已经安装了kafka并且可以通过kafka-server-start.sh命令启动它。 1.进入/lib/systemd/system目录 命令: cd /lib/systemd/system…...

11 享元(Flyweight)模式
享元模式 1.1 分类 (对象)结构型 1.2 提出问题 做一个车管所系统,将会产生大量的车辆实体,如果每一个实例都保存自己的所有信息,将会需要大量内存,甚至导致程序崩溃。 1.3 解决方案 运用共享技术有效…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...