环境搭建
首先要在电脑上安装 Python,直接在官网下载安装包进行安装即可:https://www.python.org/downloads/
1 2 3 4 5 6 7 8 9 |
# 创建文件夹,可随意命名 mkdir chatgpt-pdf cd chatgpt-pdf # 创建虚拟环境(第二个venv为虚拟环境的名称,之所以使用venv是因为一般通用的.gitignore会默认忽略该文件夹,完全可以选择使用其它名称) python3 -m venv venv # 激活虚拟环境 source venv/bin/activate # 安装依赖包 pip install langchain pdfplumber python-dotenv streamlit faiss-cpu openai tiktoken |
langchain
官网位于https://python.langchain.com/en/latest/index.html,它可以简化对各种大语言模型的使用,比如内置有OpenAI等。
pdfplumber
顾名思义,是用于读取和处理PDF文件的,选择这库是因为今年还在更新,并且对中文的支持还不错。本次项目demo会导入自己的PDF文件,并将其作为知识库,回答你的提问。
python-dotenv
用于读取.env
文件,本例在该文件中放入Open AI平台的key。
streamlit
用于绘制 UI界面,当前大多数ChatGPT应用都使用它,如大名鼎鼎的gpt4free,streamlit
默认会收集信息进行分析,可通过配置文件关闭,macOS和Linux位于~/.streamlit/config.toml
,Windows位于%userprofile%/.streamlit/config.toml
,添加如下内容即可:
1 2 |
[browser] gatherUsageStats = false |
其它配置项可通过streamlit config show
可查看。运行过程中如果界面出现意料之外的报错,可先运行streamlit cache clear
清除缓存后再试试。
faiss-cpu
是facebook开源用于相似搜索的库,https://github.com/facebookresearch/faiss,GPU版本请使用faiss-gpu
。
openai
和tiktoken
都是调用ChatGPT接口时使用的。
保留当前使用版本请使用pip freeze > requirements.txt
。
OpenAI的注册方式这里就不介绍了,最常用的是在https://sms-activate.org/上购买服务获取短信验证码完成注册。
代码开发
知识库搭建和使用流程图如下:
我们在根目录下创建.env
文件:
1 |
OPENAI_API_KEY=你自己的key |
这里的OPENAI_API_KEY
名称固定,请不要修改。
然后创建app.py
,先进行环境变量的读取(使用编辑器的小伙伴请注意选择刚刚创建的环境,PyCharm应该能自动识别,VScode 请按下快捷键 ctrl/cmd+shift+p进行选择),先测试读取环境变量是否正常
1 2 3 4 5 |
from dotenv import load_dotenv import os load_dotenv() print(os.getenv('OPENAI_API_KEY')) |
接着搭建页面框架:
1 2 3 4 5 6 |
import streamlit as st st.set_page_config(page_title="专属PDF知识库") st.header("专属PDF知识库💬") # 上传文件 pdf = st.file_uploader("上传PDF文件", type="pdf") |
运行streamlit run app.py
效果如下:
提取文本:
1 2 3 4 5 6 7 8 |
import pdfplumber # 提取文本 if pdf is not None: text = "" with pdfplumber.open(pdf) as pdf_reader: for page in pdf_reader.pages: text += page.extract_text() |
接下对文本进行分片,这里每个分片长充为1000字符,为保留上下文选择了重叠200字符:
1 2 3 4 5 6 7 8 9 |
from langchain.text_splitter import CharacterTextSplitter text_splitter = CharacterTextSplitter( separator="\n", chunk_size=1000, chunk_overlap=200, length_function=len ) chunks = text_splitter.split_text(text) |
接下来配置embedding(词嵌入),也即将离散值转化为连续向量:
1 2 |
embeddings = OpenAIEmbeddings() knowledge_base = FAISS.from_texts(chunks, embeddings) |
为界面添加一个输入框:
1 |
user_question = st.text_input("来向我提问吧:") |
最后完成回复的逻辑:
1 2 3 4 5 6 7 8 9 10 |
from langchain.chains.question_answering import load_qa_chain from langchain.llms import OpenAI if user_question: docs = knowledge_base.similarity_search(user_question) llm = OpenAI() chain = load_qa_chain(llm, chain_type="stuff") response = chain.run(input_documents=docs, question=user_question) st.write(response) |
如需追踪花了多少钱,可增加:
1 2 3 4 5 |
from langchain.callbacks import get_openai_callback with get_openai_callback() as cb: response = chain.run(input_documents=docs, question=user_question) print(cb) st.write(response) |
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
from dotenv import load_dotenv import streamlit as st from langchain.text_splitter import CharacterTextSplitter from langchain.embeddings.openai import OpenAIEmbeddings from langchain.vectorstores import FAISS from langchain.chains.question_answering import load_qa_chain from langchain.llms import OpenAI from langchain.callbacks import get_openai_callback import pdfplumber def main(): load_dotenv() # locale.setlocale(locale.LC_ALL, 'zh_CN') st.set_page_config(page_title="专属PDF知识库") st.header("专属PDF知识库💬") # 上传文件 pdf = st.file_uploader("上传PDF文件", type="pdf") # 提取文本 if pdf is not None: text = "" with pdfplumber.open(pdf) as pdf_reader: for page in pdf_reader.pages: text += page.extract_text() # 文本分片 text_splitter = CharacterTextSplitter( separator="\n", chunk_size=1000, chunk_overlap=50, length_function=len ) chunks = text_splitter.split_text(text) # 创建embeddings embeddings = OpenAIEmbeddings() knowledge_base = FAISS.from_texts(chunks, embeddings) user_question = st.text_input("来向我提问吧:") if user_question: docs = knowledge_base.similarity_search(user_question) llm = OpenAI() chain = load_qa_chain(llm, chain_type="stuff") with get_openai_callback() as cb: response = chain.run(input_documents=docs, question=user_question) print(cb) st.write(response) if __name__ == '__main__': main() |
国内直连接口超时问题
如果有海外服务器可以直接做代理
网上现在比较通用的方案是使用 Cloudflare Workers,比如下面是 Alan随便找到的一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
const TELEGRAPH_URL = 'https://api.openai.com'; addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) async function handleRequest(request) { const url = new URL(request.url); url.host = TELEGRAPH_URL.replace(/^https?:\/\//, ''); const modifiedRequest = new Request(url.toString(), { headers: request.headers, method: request.method, body: request.body, redirect: 'follow' }); const response = await fetch(modifiedRequest); const modifiedResponse = new Response(response.body, response); // 添加允许跨域访问的响应头 modifiedResponse.headers.set('Access-Control-Allow-Origin', '*'); return modifiedResponse; } |
但现在xxx.workers.dev在国内也无法访问,所以也是有门槛的,那就是要绑定一个自定义域名。自定义域可在.env
文件中添加OPENAI_API_BASE=xxx.xxx.xxx/v1
。
Nginx反代配置(GitHub):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
events { worker_connections 16; } http { proxy_ssl_server_name on; proxy_cache_path /server_cache levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=4d use_temp_path=off; log_format cache_log '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' 'Cache: $upstream_cache_status'; server { listen 80; proxy_set_header Host api.openai.com; proxy_http_version 1.1; proxy_set_header Host $host; proxy_busy_buffers_size 512k; proxy_buffers 4 512k; proxy_buffer_size 256k; location ~* ^\/v1\/((engines\/.+\/)?(?:chat\/completions|completions|edits|moderations|answers|embeddings))$ { proxy_pass https://api.openai.com; proxy_set_header Connection ''; proxy_cache my_cache; proxy_cache_methods POST; proxy_cache_key "$request_method|$request_uri|$request_body"; proxy_cache_valid 200 4d; proxy_cache_valid 404 1m; proxy_read_timeout 8m; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_background_update on; proxy_cache_lock on; access_log /dev/stdout cache_log; proxy_ignore_headers Cache-Control; add_header X-Cache-Status $upstream_cache_status; client_body_buffer_size 4m; } location /v1 { proxy_pass https://api.openai.com; } } } |
Vercel反向代理: https://github.com/gaboolic/vercel-reverse-proxy
- Unsupported OpenAI-Version header provided: 2022-12-01. (HINT: you can provide any of the following supported versions: 2020-10-01, 2020-11-07. Alternatively, you can simply omit this header to use the default version associated with your account.)
这个问题待探讨,快速解决方法是
1embeddings = OpenAIEmbeddings(openai_api_version='2020-11-07')
https://github.com/hwchase17/langchain/commit/8c28ad6daca3420d4428a464cd35f00df8b84f01
参考资料:
https://python.langchain.com/en/latest/index.html
https://bennycheung.github.io/ask-a-book-questions-with-langchain-openai
https://github.com/x-dr/chatgptProxyAPI
https://www.youtube.com/watch?v=wUAUdEw5oxM