光谷做網(wǎng)站推廣論壇推廣的步驟
簡介
本質(zhì)上是在Langchain基礎(chǔ)上封裝的一層聊天服務(wù),可以對接底層多種離線LLM和在線的LLM(也可以對接自定義的在線LLM)。提供基于知識庫聊天功能相關(guān)的一系列API。
下載源碼
源碼地址:
https://github.com/chatchat-space/Langchain-Chatchat
實(shí)踐版本:
注:
1. 因?yàn)閞equirements.txt里一些依賴沒有標(biāo)注版本號,所以,在安裝使用中可能存在版本號不匹配問題,本文可用版本號參考
openai 0.28.1 langchain 0.0.330 |
2. 顯卡卡住問題https://leiblog.wang/%E8%B8%A9%E5%9D%91nvidia-driver/
執(zhí)行:nvidia-smi -pm 1
下載模型
修改配置
修改configs/model_config.py中MODEL_PATH,你要使用的模型為本地模型路徑
啟動服務(wù)
python startup.py --all-webui
webui界面:
API 體驗(yàn)界面
Docker運(yùn)行服務(wù)
安裝docker:
apt update apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" apt update apt install docker-ce docker-ce-cli containerd.io systemctl status docker |
Ubuntu 23.04 Support · Issue #72 · NVIDIA/nvidia-container-toolkit · GitHub
安裝nvidia-container-toolkit
distribution=ubuntu18.04 \ ????? && curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ ????? && curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \ ??????????? sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ ??????????? sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list apt-get update && apt-get install -y nvidia-container-toolkit service docker restart |
設(shè)置docker鏡像源:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
??? "registry-mirrors": [
??????? "http://hub-mirror.c.163.com",
??????? "https://z2ycya8q.mirror.aliyuncs.com"
??? ]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
生成鏡像:
cd /home
git clone https://code.dobest.com/research-nlp/Langchain-Chatchat.git
初始化數(shù)據(jù)庫:
cp -r /home/Langchain-Chatchat/knowledge_base/* /home/data/Langchain-Chatchat/knowledge_base/
cd Langchain-Chatchat
docker build -f ./Dockerfile -t langchain-chatchat .
docker run -it -d -p 8501:8501 -p 7861:7861 -p 20000:20000 -p 20001:20001 -p 20002:20002 -p 21007:21007 --gpus all -e ZMENV="online" --restart=always -v "/home/models:/home/models" -v "/home/data/Langchain-Chatchat/knowledge_base:/usr/src/Langchain-Chatchat/knowledge_base" -v "/home/data/Langchain-Chatchat/logs:/usr/src/Langchain-Chatchat/logs" -v /etc/localtime:/etc/localtime:ro langchain-chatchat
遠(yuǎn)程調(diào)試代碼
pycharm遠(yuǎn)程調(diào)試
PyCharm遠(yuǎn)程調(diào)試代碼 - 知乎
【已解決】Pycharm:Can't get remote credentials for deployment server-CSDN博客
設(shè)置個ssh interpreter就可以遠(yuǎn)程調(diào)試了。
代碼解析
start_main_server
???????? -> run_controller
???????? -> run_model_worker
???????? -> run_openai_api
???????? -> run_api_server
???????? -> run_webui
主進(jìn)程fork出7個子進(jìn)程
每個模型起一個子進(jìn)程
服務(wù)端運(yùn)行FastChat
GitHub - lm-sys/FastChat: An open platform for training, serving, and evaluating large language models. Release repo for Vicuna and Chatbot Arena.
HTTP API Source | Segment Documentation
socket,長連接通信
run_api_server(startup.py)
run_api_server ?-> create_app
start_main_server ?-> run_model_worker
FastAPI拉起模型進(jìn)程
document
??? app.get("/",
??????????? response_model=BaseResponse,
??????????? summary="swagger 文檔")(document)
openai_chat
??? # Tag: Chat
??? app.post("/chat/fastchat",
???????????? tags=["Chat"],
???????????? summary="與llm模型對話(直接與fastchat api對話)")(openai_chat)
openai_chat -> ChatCompletion : acreate -> EngineAPIResource : acreate -> api_requestor : request
不帶歷史條件的單問題對話
{ ? "model": "chatglm2-6b", ? "messages": [ ??? { ????? "role": "user", ????? "content": "hello" ??? } ? ], ? "temperature": 0.7, ? "n": 1, ? "max_tokens": 1024, ? "stop": [], ? "stream": false, ? "presence_penalty": 0, ? "frequency_penalty": 0 } |
chat
??? app.post("/chat/chat",
???????????? tags=["Chat"],
???????????? summary="與llm模型對話(通過LLMChain)")(chat)
chat每個入?yún)?yīng)post里面的一個json字段
def chat(query: str = Body(..., description="用戶輸入", examples=["惱羞成怒"]), |
chat ->chat_iterator
把所有歷史記錄作為prompt輸入
[[ChatMessage(content='我們來玩成語接龍,我先來,生龍活虎', additional_kwargs={}, role='user'), ChatMessage(content='虎頭虎腦', additional_kwargs={}, role='assistant'), ChatMessage(content='惱羞成怒', additional_kwargs={}, role='user')]] |
chat ->chat_iterator ->acall ->llm : _acall -> agenerate -> agenerate_prompt ->agenerate ->_agenerate_with_cache ->openai:_agenerate ->openai: _astream -> openai: acompletion_with_retry -> chat_completion : acreate -> engine_api_resource : acreate -> api_requestor : arequest -> api_requestor : arequest_raw -> client : request
貌似大模型都按照openai_api的定義生成了一套標(biāo)準(zhǔn)http接口
langchain是封裝了一層,對接所有大模型的openap_api
knowledge_base_chat
??? app.post("/chat/knowledge_base_chat",
???????????? tags=["Chat"],
???????????? summary="與知識庫對話")(knowledge_base_chat)
長連接通信:
pages = {"對話": {"icon": "chat","func": dialogue_page,},"知識庫管理": {"icon": "hdd-stack","func": knowledge_base_page,},} |
dialogue_page ?-> utils: knowledge_base_chat -> chat. knowledge_base_chat
http 接口:
?create_app -> chat. knowledge_base_chat
所有接口都有兩條路
knowledge_base_chat -> knowledge_base_chat_iterator -> kb_doc_api: search_docs -> base : search_docs -> faiss_kb_service : do_search -> langchain : similarity_search_with_score -> faiss : replacement_search
langchain 對接 faiss
docs返回相似度top5的答案
[ChatMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], output_parser=None, partial_variables={}, template='{% raw %}我們來玩成語接龍,我先來,生龍活虎{% endraw %}', template_format='jinja2', validate_template=True), additional_kwargs={}, role='user'), ChatMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], output_parser=None, partial_variables={}, template='{% raw %}虎頭虎腦{% endraw %}', template_format='jinja2', validate_template=True), additional_kwargs={}, role='assistant'), ChatMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], output_parser=None, partial_variables={}, template='<指令>根據(jù)已知信息,簡潔和專業(yè)的來回答問題。如果無法從中得到答案,請說 “根據(jù)已知信息無法回答該問題”,不允許在答案中添加編造成分,答案請使用中文。 </指令>\n\n<已知信息>{{ context }}</已知信息>\n\n<問題>{{ question }}</問題>', template_format='jinja2', validate_template=True), additional_kwargs={}, role='user')] |
chat_prompt = ChatPromptTemplate.from_messages(
??? [i.to_msg_template() for i in history] + [input_msg])
歷史和prompt模版拼接成chat_prompt
knowledge_base_chat -> Chain : acall
會將文本作為已知信息,提出問題
所有的歷史和搜索文本、問題拼裝成提示語
Chain : agenerate -> Chain : aprep_prompts
將params一些數(shù)據(jù)轉(zhuǎn)成bytes的data發(fā)給模型服務(wù)
knowledge_base_chat -> Chain : acall -> Chain : agenerate -> Chain : aprep_prompts -> BaseChatModel : agenerate -> BaseChatModel : _agenerate_with_cache -> ChatOpenAI : _agenerate -> ChatOpenAI : _astream -> chat_models : acompletion_with_retry -> _completion_with_retry -> ChatCompletion : acreate -> EngineAPIResource : acreate -> APIRequestor : arequest -> APIRequestor : arequest_raw
最后Chain : acall的response里獲得模型答復(fù)
可以根據(jù)閾值返回TOPK作為先驗(yàn)知識
# 知識庫匹配向量數(shù)量VECTOR_SEARCH_TOP_K = 5# 知識庫匹配相關(guān)度閾值,取值范圍在0-1之間,SCORE越小,相關(guān)度越高,取到1相當(dāng)于不篩選,建議設(shè)置在0.5左右SCORE_THRESHOLD = 1 |
search_engine_chat
??? app.post("/chat/search_engine_chat",
???????????? tags=["Chat"],
???????????? summary="與搜索引擎對話")(search_engine_chat)
search_engine_chat -> lookup_search_engine -> duckduckgo_search
調(diào)用第三方搜索API,我們沒有對應(yīng)的庫,就會拋異常報(bào)錯。
agent_chat
??? app.post("/chat/agent_chat",
???????????? tags=["Chat"],
???????????? summary="與agent對話")(agent_chat)
LangChain Agent入門教程 - LangChain教程(Python版本) - 梯子教程網(wǎng)
是通過llm進(jìn)行決策,然后,程序去調(diào)用對應(yīng)的工具。目前不需要。對應(yīng)工具可能沒有。
都是調(diào)用langchain的agent接口。查天氣不可用。翻譯可用。
本質(zhì)上還是語言模型輸入輸出,在大模型外層設(shè)計(jì)出對應(yīng)字符串的action回調(diào),回調(diào)中自定義功能。
天氣這個是chatchat這邊實(shí)現(xiàn)的,應(yīng)該還沒有調(diào)通,解決了一個參數(shù)問題,大模型返回格式還不太對。
大模型回復(fù)了,格式不對
修改了明確的提示語后,可以調(diào)通天氣,只需要和風(fēng)天氣API的key就可以查詢了。
明確的prompt很重要,問題后面就是接答案,不要亂搞。
_PROMPT_TEMPLATE = """用戶將會向您咨詢天氣問題,您不需要自己回答天氣問題,而是將用戶提問的信息提取出來區(qū),市和時(shí)間三個元素后使用我為你編寫好的工具進(jìn)行查詢并返回結(jié)果,格式為 區(qū)+市+時(shí)間 每個元素用空格隔開。如果缺少信息,則用 None 代替。問題: ${{用戶的問題}}答案:
```text${{拆分的區(qū),市和時(shí)間}}```... weather(提取后的關(guān)鍵字,用空格隔開)...這是兩個例子:問題: 上海浦東未來1小時(shí)天氣情況?答案:
```text浦東 上海 1```...weather(浦東 上海 1)...問題: 北京市朝陽區(qū)未來24小時(shí)天氣如何?答案:
```text朝陽 北京 24```...weather(朝陽 北京 24)...現(xiàn)在,這是我的問題:問題: {question}""" |
利用ChatGPT的函數(shù)調(diào)用功能實(shí)現(xiàn):實(shí)時(shí)查詢天氣 - FooFish
agent_chat -> agent_chat_iterator -> Chain : acall -> BaseSingleActionAgent : _acall
generations=[ChatGenerationChunk(text=' 我需要查詢上海浦東的天氣情況,可以使用天氣查詢工具幫助我。\nAction: 天氣查詢工具\(yùn)nAction Input: 上海 浦東 未來1小時(shí)\nObservation', message=AIMessageChunk(content=' 我需要查詢上海浦東的天氣情況,可以使用天氣查詢工具幫助我。\nAction: 天氣查詢工具\nAction Input: 上海 浦東 未來1小時(shí)\nObservation'))] llm_output=None |
通過tool定義,能找到對應(yīng)的func
然后再根據(jù)對應(yīng)天氣的特定的prompt調(diào)用大模型,從問題中提取出對應(yīng)的參數(shù),然后,提取校驗(yàn)返回值,并最為入?yún)⒔o到獲取天氣的函數(shù),拿到天氣信息。
list_kbs
??? # Tag: Knowledge Base Management
??? app.get("/knowledge_base/list_knowledge_bases",
??????????? tags=["Knowledge Base Management"],
??????????? response_model=ListResponse,
??????????? summary="獲取知識庫列表")(list_kbs)
SQlALchemy session詳解 - 知乎
list_kbs -> list_kbs_from_db
使用SQLAlchemy來操作數(shù)據(jù)庫
create_kb
??? app.post("/knowledge_base/create_knowledge_base",
???????????? tags=["Knowledge Base Management"],
???????????? response_model=BaseResponse,
???????????? summary="創(chuàng)建知識庫"
???????????? )(create_kb)
封裝了向量庫服務(wù) kb_service
再去調(diào)用langchain.vectorstores中封裝好的方法
create_kb -> get_service_by_name ->load_kb_from_db
???????????? get_service_by_name -> KBServiceFactory : get_service
// 先創(chuàng)建向量庫
create_kb -> KBService : create_kb -> do_create_kb -> load_vector_store -> load_faiss_vector_store
// 再創(chuàng)建對應(yīng)數(shù)據(jù)庫信息
????????? -> KBService : create_kb -> add_kb_to_db
delete_kb
??? app.post("/knowledge_base/delete_knowledge_base",
???????????? tags=["Knowledge Base Management"],
???????????? response_model=BaseResponse,
???????????? summary="刪除知識庫"
???????????? )(delete_kb)
// 刪除向量庫內(nèi)容
delete_kb ->clear_vs -> FaissKBService : do_clear_vs
// 刪除對應(yīng)數(shù)據(jù)庫信息
delete_kb ->clear_vs -> delete_files_from_db
// 刪除知識庫
delete_kb -> drop_kb -> FaissKBService : do_drop_kb
delete_kb -> drop_kb -> delete_kb_from_db
list_files
??? app.get("/knowledge_base/list_files",
??????????? tags=["Knowledge Base Management"],
??????????? response_model=ListResponse,
??????????? summary="獲取知識庫內(nèi)的文件列表"
??????????? )(list_files)
// 查數(shù)據(jù)庫
list_files -> KBService : list_files -> list_files_from_db
search_docs
??? app.post("/knowledge_base/search_docs",
???????????? tags=["Knowledge Base Management"],
???????????? response_model=List[DocumentWithScore],
???????????? summary="搜索知識庫"
???????????? )(search_docs)
search_docs -> KBService : search_docs -> FaissKBService : do_search -> FAISS : similarity_search_with_score
FaissKBService 屬于Langchain基礎(chǔ)上再封裝
FAISS 是Langchain封裝好的向量庫接口
upload_doc
??? app.post("/knowledge_base/upload_doc",
???????????? tags=["Knowledge Base Management"],
???????????? response_model=BaseResponse,
???????????? summary="上傳文件到知識庫"
???????????? )(upload_doc)
保存知識文件到對應(yīng)知識庫路徑
會將文件內(nèi)容以行為單位截?cái)?#xff0c;那么相鄰兩部分的首尾有一部分內(nèi)容重合。
// 清理向量庫和數(shù)據(jù)庫
upload_doc -> KBService : add_doc -> KBService : delete_doc
// 將文檔內(nèi)容加入向量庫
upload_doc -> KBService : add_doc -> FaissKBService : do_add_doc -> VectorStore : add_documents
VectorStore:langchain封裝的向量庫接口
// 將生成的向量id和對應(yīng)文檔信息,存入數(shù)據(jù)庫
upload_doc -> KBService : add_doc -> add_file_to_db
delete_doc
??? app.post("/knowledge_base/delete_doc",
???????????? tags=["Knowledge Base Management"],
???????????? response_model=BaseResponse,
???????????? summary="刪除知識庫內(nèi)指定文件"
???????????? )(delete_doc)
刪除找對應(yīng)id是遍歷整個知識庫比對文件路徑
// 從向量庫中刪除數(shù)據(jù)
delete_doc -> KBService : delete_doc -> FaissKBService : do_delete_doc
// 從數(shù)據(jù)庫中刪除文件信息(可以選擇是否刪除內(nèi)容)
delete_doc -> KBService : delete_doc -> delete_file_from_db
update_doc
??? app.post("/knowledge_base/update_doc",
???????????? tags=["Knowledge Base Management"],
???????????? response_model=BaseResponse,
???????????? summary="更新現(xiàn)有文件到知識庫"
???????????? )(update_doc)
update_doc -> KBService : update_doc
所謂的更新就是先刪除再重新添加
download_doc
??? app.get("/knowledge_base/download_doc",
??????????? tags=["Knowledge Base Management"],
??????????? summary="下載對應(yīng)的知識文件")(download_doc)
http://10.225.20.233:7861/knowledge_base/download_doc?knowledge_base_name=samples&file_name=1.docx
postman選擇 send and download可以下載文件
download_doc -> FileResponse
recreate_vector_store
??? app.post("/knowledge_base/recreate_vector_store",
???????????? tags=["Knowledge Base Management"],
???????????? summary="根據(jù)content中文檔重建向量庫,流式輸出處理進(jìn)度。"
???????????? )(recreate_vector_store)
// 最后刷新緩存
recreate_vector_store -> output -> KBService : add_doc -> FaissKBService : do_add_doc -> vector_store.save_local
list_models
# LLM模型相關(guān)接口@app.post("/llm_model/list_models",tags=["LLM Model Management"],summary="列出當(dāng)前已加載的模型")
list_running_models
應(yīng)該是LLM Model + FSCHAT_MODEL_WORKERS
本地模型和在線接口
list_running_models -> Controller : list_models
list_config_models
app.post("/llm_model/list_config_models",tags=["LLM Model Management"],summary="列出configs已配置的模型",)(list_config_models)
list_config_models -> list_llm_models
stop_llm_model
@app.post("/llm_model/stop",tags=["LLM Model Management"],summary="停止指定的LLM模型(Model Worker)",)
stop_llm_model -> release_worker -> release_model
網(wǎng)絡(luò)中轉(zhuǎn)了幾次,最后,模型被釋放了,顯存釋放了
change_llm_model
@app.post("/llm_model/change",tags=["LLM Model Management"],summary="切換指定的LLM模型(Model Worker)",)
change_llm_model -> release_worker -> release_model
模型停掉,就不能切換了,看來不能動態(tài)的更換大模型
webui.py
UI框架Streamlit
SQLAlchemy
SQLAlchemy入門和進(jìn)階 - 知乎
# 數(shù)據(jù)庫默認(rèn)存儲路徑。# 如果使用sqlite,可以直接修改DB_ROOT_PATH;如果使用其它數(shù)據(jù)庫,請直接修改SQLALCHEMY_DATABASE_URI。DB_ROOT_PATH = os.path.join(KB_ROOT_PATH, "info.db")SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_ROOT_PATH}"
|
默認(rèn)使用了sqlite
class KnowledgeFileModel(Base):"""知識文件模型
"""__tablename__ = 'knowledge_file'id = Column(Integer, primary_key=True, autoincrement=True, comment='知識文件ID')file_name = Column(String(255), comment='文件名')file_ext = Column(String(10), comment='文件擴(kuò)展名')kb_name = Column(String(50), comment='所屬知識庫名稱')document_loader_name = Column(String(50), comment='文檔加載器名稱')text_splitter_name = Column(String(50), comment='文本分割器名稱')file_version = Column(Integer, default=1, comment='文件版本')file_mtime = Column(Float, default=0.0, comment="文件修改時(shí)間")file_size = Column(Integer, default=0, comment="文件大小")custom_docs = Column(Boolean, default=False, comment="是否自定義docs")docs_count = Column(Integer, default=0, comment="切分文檔數(shù)量")create_time = Column(DateTime, default=func.now(), comment='創(chuàng)建時(shí)間')def __repr__(self):return f"<KnowledgeFile(id='{self.id}', file_name='{self.file_name}', file_ext='{self.file_ext}', kb_name='{self.kb_name}', document_loader_name='{self.document_loader_name}', text_splitter_name='{self.text_splitter_name}', file_version='{self.file_version}', create_time='{self.create_time}')>"class FileDocModel(Base):"""文件-向量庫文檔模型
"""__tablename__ = 'file_doc'id = Column(Integer, primary_key=True, autoincrement=True, comment='ID')kb_name = Column(String(50), comment='知識庫名稱')file_name = Column(String(255), comment='文件名稱')doc_id = Column(String(50), comment="向量庫文檔ID")meta_data = Column(JSON, default={})def __repr__(self):return f"<FileDoc(id='{self.id}', kb_name='{self.kb_name}', file_name='{self.file_name}', doc_id='{self.doc_id}', metadata='{self.metadata}')>" |
兩張表KnowledgeFileModel 和 FileDocModel
用的是SessionLocal,本地?cái)?shù)據(jù)庫
自定義線上API
server/model_workers下面添加線上API的具體定義,可以參考目錄下其它線上API定義。
generate_stream_gate里面定義具體的接口對接代碼。
server_config.py中 FSCHAT_MODEL_WORKERS下面新增api綁定端口。
model_config.py 中ONLINE_LLM_MODEL 新增 api配置