ARTS 打卡第 4 天

今天打卡晚了二十几分钟。因为加班加上和老弟聊天聊得比较久，然后选读了一篇关于用 LLM 进行 TDD 开发的文章，内容太长了，为了实现当天打卡又换了一篇，没想到还是超时了，而且还换了一篇质量不够高的文章，得不偿失。不管怎样，先用这篇文章完成打卡，用 LLM 进行 TDD 的文章作为紧接着的打卡文章的内容好了。

A：263. 丑数

丑数 就是只包含质因数 2、3 和 5 的正整数。
给你一个整数 n ，请你判断 n 是否为 丑数 。如果是，返回 true ；否则，返回 false 。
示例 1：
输入：n = 6
输出：true
解释：6 = 2 × 3
示例 2：
输入：n = 1
输出：true
解释：1 没有质因数，因此它的全部质因数是 {2, 3, 5} 的空集。习惯上将其视作第一个丑数。
示例 3：
输入：n = 14
输出：false
解释：14 不是丑数，因为它包含了另外一个质因数 7 。

也是一道比较简单的题目，没有想到什么理论化的方法，一开始忽略了 0 不是丑数，改了下后就提交了：

Copy
function isUgly(n: number): boolean {
if (n === 0) {
return false
}
if (n === 1) {
return true
}
if (n % 2 === 0) {
return isUgly(n / 2)
}
if (n % 3 === 0) {
return isUgly(n / 3)
}
if (n % 5 === 0) {
return isUgly(n / 5)
}
return false
}

提交结果为：

1013/1013 cases passed (56 ms)
Your runtime beats 100 % of typescript submissions
Your memory usage beats 38.09 % of typescript submissions (44 MB)

值得注意的是我这里是用递归实现的，换成 while (true) 的循环也是可以的。

R：LangChain + Streamlit + Llama: Bringing Conversational AI to Your Local Machine

由于大家对大语言模型还有 LangChain 应该都很熟悉了，这里我只再简单描述一下。

大语言模型引起了巨大的关注，许多开发者也在用大语言模型做聊天机器人、个人助手或内容创造，大语言模型的可能性让开发者、AI、NLP 的社区里都涌现出了巨大的热情。

可以向大语言模型注入领域特定的数据来高效地解决查询问题，尤其在公司内部的文档知识库方面有用武之地。用于实现这种目的的架构叫作 “检索增强生成” 或者 “生成式问答”。

LangChain 是什么？LangChain 一个将大语言 AI 应用的组成部分方便地串联在一起的研发框架，可以方便开发者快速实现聊天机器人等应用。

这篇文章主要讲怎么用 LangChain 和 LLaMA 7B 模型创建一个文档助手 （个人感觉有点过时了，现在已经是 LLaMA2 了）。

文章信息结构：

创建虚拟环境和文件结构

在本地拉取大语言模型

集成大语言模型到 LangChain 中并自定义 Prompt 模板

文档检索和回答生成

使用 Streamlit 创建应用

1. 创建虚拟环境和文件结构

创建基本的文件结构和 Python 虚拟环境，主要是模型文件、Notebook 文件、app.py 应用程序入口文件，可以 clone 作者的仓库：DocQA。

2. 在本地拉取大语言模型

LLaMA 是 Meta 发布的大语言模型，LLaMA2 可以免费商用，这篇文章用的是 LLaMA1。去 HuggingFace 找到 LLaMA 模型下载 bin 文件到 models 目录内即可。

GGML 是一个开源的 C++ 写的机器学习张量库，可以通过量化的方式运行我们在消费级的硬件上运行 LLM。

那什么是量化呢？LLM 的权重是浮点型的数值，相比于整型数值更占用空间和算力。量化就是减少权重的精度来减少资源占用。GGML 支持 4-bit、5-bit 和 8-bit 的量化。

需要权衡考虑内存、硬盘以及模型效果来选择模型参数大小和量化方式。大小越大、量化精度越高效果越好，资源占用也越多。

如果 GGML 是 C++ 库，怎么在 Python 里面用起来呢？这就需要用到 llma-cpp-python 项目了，这是一个 llama.cpp 的 Python binding，可以让我们用 Python 去运行 LLaMA 模型。

介绍了这么多，其实运行起来非常简单，几行 Python 代码就搞定了：

3. 集成大语言模型到 LangChain 中并自定义 Prompt 模板

对 LLM 来说，简化理解它的运行就是输入文本、输出文本，因此对 LangChain 来说大部分工作也是以文本为中心的。

Prompt 的细微差别就会对 LLM 的运行效果产生巨大的差距，因此专门产生了 Prompt Engineering 的概念，来考虑怎么生成质量更好的 Prompt。为了无缝于 LLM 交互，LangChain 提供了 Prompt 模板开发的功能，它通常包含两部分：文本模板以及动态参数：

简单的应用把 Prompt 和输入参数传给 LLM 生成结果就好了，但复杂的应用一般需要串接 LLM 以及其它组件。LangChain 提供了串接的开发方式，可以串行调用一系列组件。

4. 文档检索和回答生成

在大量 LLM 应用中，用户需要的数据都不在模型的训练数据集中，需要外挂到 Prompt 里。LangChain 提供了加载、转换、存储和查询这些数据的必要组件：

这五个过程是：文档加载 - 文档转换 - Embedding - 向量存储 - 向量检索，以下是文章检索的全流程：

这个过程比较长就不在这里展开了，值得注意的是因为这是一个本地部署的方案，因此 embedding 模型也不是用的远程服务，而是用 LlamaCppEmbeddings 这个 LangChain 组件来用 LLaMA 模型做 Embedding。

5. 使用 Streamlit 创建应用

作者没有详细展开 Streamlit，因为这对主链路来说是一个相对可选的步骤。不过使用 Streamlit 实现文件上传的时候作者强调为了防止内存不够用，他是在一个临时目录内保存上传的文件为 raw.txt 的，当前只支持 txt 类型文件，也可以改造成支持 PDF、CSV 的文件。最后通过调用 Streamlit 库，就把这个基于 LangChain 的 LLM 应用变成了一个网页应用：

Copy
# Bring in deps
import streamlit as st
from langchain.llms import LlamaCpp
from langchain.embeddings import LlamaCppEmbeddings
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma

# Customize the layout
st.set_page_config(page_title="DOCAI", page_icon="🤖", layout="wide", )
st.markdown(f"""

.stApp {{background-image: url("https://images.unsplash.com/photo-1509537257950-20f875b03669?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1469&q=80");
background-attachment: fixed;
background-size: cover}}

""", unsafe_allow_html=True)

# function for writing uploaded file in temp
def write_text_file(content, file_path):
try:
with open(file_path, 'w') as file:
file.write(content)
return True
except Exception as e:
print(f"Error occurred while writing the file: {e}")
return False

# set prompt template
prompt_template = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
Answer:"""
prompt = PromptTemplate(template=prompt_template, input_variables=["context", "question"])

# initialize hte LLM & Embeddings
llm = LlamaCpp(model_path="./models/llama-7b.ggmlv3.q4_0.bin")
embeddings = LlamaCppEmbeddings(model_path="models/llama-7b.ggmlv3.q4_0.bin")
llm_chain = LLMChain(llm=llm, prompt=prompt)

st.title("📄 Document Conversation 🤖")
uploaded_file = st.file_uploader("Upload an article", type="txt")

if uploaded_file is not None:
content = uploaded_file.read().decode('utf-8')
# st.write(content)
file_path = "temp/file.txt"
write_text_file(content, file_path)

loader = TextLoader(file_path)
docs = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
texts = text_splitter.split_documents(docs)
db = Chroma.from_documents(texts, embeddings)
st.success("File Loaded Successfully!!")

# Query through LLM
question = st.text_input("Ask something from the file", placeholder="Find something similar to: ....this.... in the text?", disabled=not uploaded_file,)
if question:
similar_doc = db.similarity_search(question, k=1)
context = similar_doc[0].page_content
query_llm = LLMChain(llm=llm, prompt=prompt)
response = query_llm.run({"context": context, "question": question})
st.write(response)

我个人比较关注 Streamlit 的应用，感觉继 Gradio 实现原型开发后，更复杂的应用开发很有可能是 Streamlit 的机会，可惜这篇文章没有深入展开，我最想看的内容草草略过了。

T：CoDeF

一个视频转视频的 LLM，输出稳定，质量也不错。

S：SQ3R 阅读法

SQ3R 代表着五个单词：Survey（浏览），Question（提问），Read（阅读），Recite（复述），Review（复习） 在学习之前，先把内容大概浏览一遍，然后在这个浏览的基础上提出自己的问题，这是讲什么的，解决什么问题。 然后带着这个问题去深入阅读。通过阅读找出答案。
最后关上书，自己复述一遍，这个书是讲什么的，然后我们先有什么问题，这个书是怎么解决。 最后是进行复习，巩固学习成果。 这五步下来，这个书的内容就能够被真正的吸收。

Reference:

ARTS 打卡活动