├── README.assets └── 640.png ├── README.md ├── data └── test_corpus.txt ├── prompt工程.assets ├── v2-15b0f2e85c19c728a790e2e18e2ab51a_r.jpg └── v2-7b3d72c23ca12e980564897f16923a9e_1440w.webp ├── prompt工程.md ├── 中文例子 ├── README.md ├── chatglm实现agent控制.assets │ └── v2-3e8e500321140396dfe4a4ae6b120fd4_720w.webp ├── chatglm实现agent控制.md ├── mini-langchain-ChatGLM.md ├── 使用中文splitter.md ├── 定制中文模型.md ├── 定制中文聊天模型.md ├── 打造简易版类小爱同学助手.md └── 根据query查询docs.md ├── 文章 ├── langchain-chatglm │ ├── 上传文档时发生了什么.md │ ├── 使用api部署langchain-chatglm的基本原理.md.md │ ├── 关于CharacterTextSplitter.md │ ├── 关于HuggingFaceEmbeddings.md │ ├── 关于InMemoryDocstore.md │ ├── 关于TextLoader.md │ ├── 关于怎么调用bing搜索接口.md │ ├── 根据prompt用模型生成结果.md │ ├── 根据query得到相关的doc的原理.md │ └── 根据查询出的docs和query生成prompt.md ├── langchain.load.serializable.py.md ├── langchain中ChatOpenAI背后做了什么.md ├── langchain中LLMChain原理.md ├── langchain中initialize_agent的原理.md ├── langchain中memory的工作原理.md ├── langchain中是怎么调用chatgpt的接口的.md ├── langchain中的EmbeddingRouterChain原理.md ├── langchain中的StreamingStdOutCallbackHandler原理.md ├── langchain中的一些schema.assets │ └── image-20230706174323824.png ├── langchain中的一些schema.md ├── langchain中路由链LLMRouterChain的原理.md ├── langchain怎么确保输出符合道德期望.assets │ ├── v2-0f651841755c0ce57095ee1c92a61180_720w.png │ ├── v2-284fd837e5c016ddd36faee89a37d4bb_720w.webp │ ├── v2-725a02272610a6350432470a454603e1_720w.webp │ ├── v2-81e9dab51a9ff43f053757f487ce58bf_720w.webp │ └── v2-e5d301aa4517b10cb59e295d9cce466a_720w.png ├── langchain怎么确保输出符合道德期望.md ├── langchain结构化输出背后的原理.md ├── langchain集成GPTCache.md ├── langchain集成Mivus向量数据库.md ├── langchian中的ChatPromptTemplate原理.md ├── pydantic中config的一些配置.md ├── pydantic中的Serializable和root_validator.md ├── python中args和kwargs.md ├── python中functools的partial的用法.md ├── python中inspect的signature用法.md ├── python中常用的一些魔术方法.md ├── python的typing常用的类型.md └── 官方文档学习 │ ├── 初步认识.md │ ├── 初步认识.pdf │ ├── 组件-代理.md │ ├── 组件-代理.pdf │ ├── 组件-内存.md │ ├── 组件-内存.pdf │ ├── 组件-回调.md │ ├── 组件-回调.pdf │ ├── 组件-数据连接.assets │ ├── dfe7b878-3c1d-4123-ae5c-cd17758a29b1.png │ ├── f03e9b67-b107-4225-8920-71f8b7e0ac76.png │ └── f221f652-1c2f-45f7-9a15-222c16106770.png │ ├── 组件-数据连接.md │ ├── 组件-数据连接.pdf │ ├── 组件-模型IO.assets │ └── 9ed90e22-7497-4460-81ce-d1f703e4b9e7.png │ ├── 组件-模型IO.md │ ├── 组件-模型IO.pdf │ ├── 组件-链.assets │ ├── 36e9dd56-7066-4d54-897e-f38c756f8e5e.png │ ├── 7de280fc-389b-4081-af01-ad5d0685d7b1.png │ ├── db684c44-a7cf-452e-978d-14c119b8bbe7.png │ └── e9dbda41-d4d7-474d-8fbd-84c0e50808ad.png │ ├── 组件-链.md │ └── 组件-链.pdf ├── 智能体-llama_generative_agent源码解读.md └── 英文例子 ├── langchain中使用不同的链.md ├── langchain中使用链.assets ├── 257f9fc8ce224d1784c0a5886696026btplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp ├── 287263118276474996f72c503eddb984tplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp └── eae7d59e0da7494e899175a500a8990etplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp ├── langchain使用openai例子.md ├── langchain基于文档的问答.md ├── langchain带有记忆的对话.md ├── langchain解析结果并格式化输出.md └── openai调用chatgpt例子.md /README.assets/640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/README.assets/640.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # langchain-learning 2 | langchain的学习笔记。依赖: 3 | 4 | ```python 5 | openai==0.27.8 6 | langchian==0.0.225 7 | ``` 8 | 9 | 和langchain相类似的一些工具: 10 | 11 | - [danswer-ai/danswer: Ask Questions in natural language and get Answers backed by private sources. Connects to tools like Slack, GitHub, Confluence, etc.](https://github.com/danswer-ai/danswer) 12 | 13 | ## **文章** 14 | 15 | **注意:由于langchain或langchain-ChatGLM的更新,可能导致部分源码和讲解的有所差异。** 16 | 17 | 有的一些文章直接放的是一些链接,从网上收集整理而来。 18 | 19 | **** 20 | 21 | - langchain组件-数据连接(data connection) 22 | - langchain组件-模型IO(model IO) 23 | - langchain组件-链(chains) 24 | - langchain组件-代理(agents) 25 | - langchain组件-内存(memory) 26 | - langchain组件-回调(callbacks) 27 | - langchain中ChatOpenAI背后做了什么.md 28 | - langchain.load.serializable.py.md 29 | - langchain中的一些schema.md 30 | - langchain中是怎么调用chatgpt的接口的.md 31 | - langchain结构化输出背后的原理,md 32 | - langchain中memory的工作原理.md 33 | - langchain怎么确保输出符合道德期望.md 34 | - langchain中路由链LLMRouterChain的原理.md 35 | - langchain中的EmbeddingRouterChain原理.md 36 | - langchain集成GPTCache.md 37 | - langchain集成Mivus向量数据库.md 38 | - langchain中的StreamingStdOutCallbackHandler原理.md 39 | - pydantic中config的一些配置.md 40 | - pydantic中的Serializable和root_validator.md 41 | - python中常用的一些魔术方法.md 42 | - python的typing常用的类型.md 43 | - python中functools的partial的用法.md 44 | - python中inspect的signature用法.md 45 | - python中args和kwargs.md 46 | - [我为什么放弃了 LangChain? - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/645358531) 47 | 48 | 目前基于langchain的中文项目有两个: 49 | 50 | - https://github.com/yanqiangmiffy/Chinese-LangChain 51 | - https://github.com/imClumsyPanda/langchain-ChatGLM 52 | 53 | 我们从中可以学到不少。 54 | 55 | #### langchain-ChatGLM 56 | 57 | - 使用api部署langchain-chatglm的基本原理.md 58 | - 上传文档时发生了什么.md 59 | - 关于HuggingFaceEmbeddings.md 60 | - 关于InMemoryDocstore.md 61 | - 关于CharacterTextSplitter.md 62 | - 关于TextLoader.md 63 | - 关于怎么调用bing的搜索接口.md 64 | - 根据query得到相关的doc的原理.md 65 | - 根据查询出的docs和query生成prompt.md 66 | - 根据prompt用模型生成结果.md 67 | - [ChatGPT小型平替之ChatGLM-6B本地化部署、接入本地知识库体验 | 京东云技术团队](https://juejin.cn/post/7246408135015415868) 68 | 69 | ## **中文例子** 70 | 71 | - 定制中文LLM模型 72 | - 定制中文聊天模型 73 | - 使用中文splitter.md 74 | - 根据query查询docs.md 75 | - mini-langchain-ChatGLM.md 76 | - 打造简易版类小爱同学助手.md 77 | - chatglm实现agent控制.md 78 | - [向量检索增强chatglm生成-结合ES](https://zhuanlan.zhihu.com/p/644619003) 79 | - [知识图谱抽取LLM - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/645509983) 80 | 81 | ## **英文例子** 82 | 83 | - langchain使用openai例子.md(文本翻译) 84 | - openai调用chatgpt例子.md 85 | - langchain解析结果并格式化输出.md 86 | - langchain带有记忆的对话.md 87 | - langchain中使用不同链.md 88 | - langchain基于文档的问答md 89 | - [使用GGML和LangChain在CPU上运行量化的llama2](https://zhuanlan.zhihu.com/p/644701608) 90 | - [本地部署开源大模型的完整教程:LangChain + Streamlit+ Llama - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/639565332) 91 | 92 | ## prompt工程.md 93 | 94 | 一个优化的prompt对结果至关重要,感兴趣的可以去看看这个。 95 | 96 | [yzfly/LangGPT: LangGPT: Empowering everyone to become a prompt expert!🚀 Structured Prompt,结构化提示词。 (github.com)](https://github.com/yzfly/LangGPT):构建结构化的高质量prompt 97 | 98 | ## **langchain可能存在一些问题** 99 | 100 | 虽然langchain给我们提供了一些便利,但是也存在一些问题: 101 | 102 | - **无法解决大模型基础技术问题,主要是prompt重用问题**:首先很多大模型应用的问题都是大模型基础技术的缺陷,并不是LangChain能够解决的。其中核心的问题是大模型的开发主要工作是prompt工程。而这一点的重用性很低。但是,这些功能都**需要非常定制的手写prompt**。链中的每一步都需要手写prompt。输入数据必须以非常特定的方式格式化,以生成该功能/链步骤的良好输出。设置DAG编排来运行这些链的部分只占工作的5%,95%的工作实际上只是在提示调整和数据序列化格式上。这些东西都是**不可重用**的。 103 | 104 | - **LangChain糟糕的抽象与隐藏的垃圾prompt造成开发的困难**:简单说,就是LangChain的抽象工作不够好,所以很多步骤需要自己构建。而且LangChain内置的很多prompt都很差,不如自己构造,但是它们又隐藏了这些默认prompt。 105 | - **LangChain框架很难debug**:**尽管LangChain很多方法提供打印详细信息的参数,但是实际上它们并没有很多有价值的信息**。例如,如果你想看到实际的prompt或者LLM查询等,都是十分困难的。原因和刚才一样,LangChain大多数时候都是隐藏了自己内部的prompt。所以如果你使用LangChain开发效果不好,你想去调试代码看看哪些prompt有问题,那就很难。 106 | - **LangChain鼓励工具锁定**:LangChain鼓励用户在其平台上进行开发和操作,但是如果用户需要进行一些LangChain文档中没有涵盖的工作流程,即使有自定义代理,也很难进行修改。这就意味着,一旦用户开始使用LangChain,他们可能会发现自己被限制在LangChain的特定工具和功能中,而无法轻易地切换到其他可能更适合他们需求的工具或平台。 107 | 108 | 以上内容来自: 109 | 110 | - [Langchain Is Pointless | Hacker News (ycombinator.com)](https://news.ycombinator.com/item?id=36645575) 111 | - [使用LangChain做大模型开发的一些问题:来自Hacker News的激烈讨论~](https://zhuanlan.zhihu.com/p/642498874) 112 | 113 | 有时候一些简单的任务,我们完全可以自己去实现相关的流程,这样**每一部分都由我们自己把控**,更易于修改。 114 | 115 | # 使用langchain解决复杂任务 116 | 117 | ## 方法一:领域微调LLM 118 | 119 | 使用领域数据对LLM进行微调,受限于计算资源和模型参数的大小,而且模型会存在胡言乱语的情况。这里面涉及到一系列的问题: 120 | 121 | - 数据怎么获取,怎么进行数据清理。 122 | - 分词使用什么方式。 123 | - 模型采用什么架构,怎么训练,怎么评估模型。 124 | - 模型怎么进行有效推理,怎么进行部署。 125 | - 领域预训练、领域指令微调、奖励模型、结果对齐。 126 | 127 | ## 方法二:langchain + LLM + tools 128 | 129 | 基本思路: 130 | 131 | 1、用户提问:请对比下商品雅诗兰黛特润修护肌活精华露和SK-II护肤精华? 132 | 133 | 2、RouterChain问题路由,即使用哪种方式回答问题:(调用一次LLM) 134 | 135 | - RouterChain可以是一个LLM,也可以是一个embedding,去匹配到合适的解决方案,如果没有匹配到任何解决方案,则使用模型内部知识进行回答。 136 | - 这里匹配到**商品对比**这一问题,得到解决方案:(1)调用商品搜索工具得到每一个商品的介绍。(2)通过搜索结果对比这些商品。 137 | 138 | 3、使用Planner生成step:(调用一次LLM) 139 | 140 | - 根据解决方案生成合适的steps,比如:(1)搜索雅诗兰黛特润修护肌活精华露。(2)搜索SK-II护肤精华。(3)对比上述商品。 141 | 142 | 4、执行者Executer执行上述步骤:(调用steps次LLM,n是超参数表明调用的最大次数) 143 | 144 | - 需要提供工具,每个step的问题,需要调用llm生成每个工具的调用参数。 145 | - 调用工具获取结果。 146 | 147 | 5、对所有的结果进行汇总。(调用一次LLM) 148 | 149 | ## 方法三:langchain + LLM + 检索 150 | 151 | 相比于方案1,不使用工具,直接根据问题进行对数据库进行检索,然后对检索到的结果进行回答。 152 | 153 | 检索的方式可以是基于给定问题的关键字,使用ES工具从海量数据库中检索到可能存在答案的topk段落。把这topk个段落连同问题一起发送给LLM,进行回答。 154 | 155 | 检索的方式改成向量的形式,先对所有已知资料按照300个字切分成小的段落,然后对这些段落进行编码成向量,当用户提问时,把用户问题同样编码成向量,然后对这些段落进行检索,得到topk最相关的段落,把这topk个段落连同问题一起发送给LLM,进行回答。 156 | 157 | ![图片](README.assets/640.png) 158 | 159 | **上述方法的优缺点:** 160 | 161 | **领域微调LLM**:需要耗费很多的人力收集领域内数据和问答对,需要耗费很多算力进行微调。 162 | 163 | **langchain + LLM + tools**:是把LLM作为一个子的服务,LangChain作为计划者和执行者的大脑,合适的时机调用LLM,优点是解决复杂问题,缺点是不可靠。LLM生成根据问题和工具调用工具获取数据时不可靠。可以不能很好的利用工具。可能不能按照指令调用合适的工具,还可能设定计划差,难以控制。优点是:用于解决复杂的问题。 164 | 165 | **langchain + LLM + 检索**:优点是现在的领域内主流问答结构,缺点:是根据问题对可能包含答案的段落检索时可能检索不准。不适用于复杂问答 166 | 167 | **总结:最大的问题还是LLM本身:** 168 | 169 | - LLM输出的不可控性,会导致后续步骤出现偏差。 170 | - LLM的输入的context的长度问题:目前已经可以把长度推广到10亿以上了。 171 | - 训练一个LLM需要的成本:对于数据而言,除了人工收集整理外,也可以使用大模型进行生成;对于训练而言,目前也有不少基于参数有效微调的例子。 172 | - LLM的部署问题:也已经有不少加速推理的方法,比如量化、压缩、使用分布式进行部署、使用C++进行部署等。 173 | 174 | LLM是整个系统的基座,目前还是有不少选择的余地的,网上开源了不少中文大语言模型,但大多都是6B/7B/13B的,要想有一个聪明的大脑,模型的参数量还是需要有保证的。 175 | 176 | 以上参考:[https://mp.weixin.qq.com/s/FvRchiT0c0xHYscO_D-sdA](https://python.langchain.com.cn/docs/modules/agents/how_to/custom_llm_chat_agent) 177 | 178 | # 扩展 179 | 180 | 留出一些问题以待思考:可能和langchain相关,也可能和大模型相关 181 | 182 | - **怎么根据垂直领域的数据选择中文大模型?**1、是否可以商用。2、根据各评测的排行版。3、在自己领域数据上进行评测。4、借鉴现有的垂直领域模型的选择,比如金融大模型、法律大模型、医疗大模型等。 183 | 184 | - **数据的一个答案由一系列相连的句子构成,怎么对文本进行切分以获得完整的答案?**比如: 185 | 186 | ```python 187 | 怎么能够解决失眠? 188 | 1、保持良好的心情; 189 | 2、进行适当的训练。 190 | 3、可适当使用药物。 191 | ``` 192 | 193 | 1、尽量将划分的文本的长度设置大一些。2、为了避免答案被分割,可以设置不同段之间可以重复一定的文本。3、检索时可返回前top_k个文档。4、融合查询出的多个文本,利用LLM进行总结。 194 | 195 | - **怎么构建垂直领域的embedding?** 196 | - **怎么存储获得的embedding?** 197 | - **如何引导LLM更好的思考?** 可使用:**chain of thoughts、self ask、ReAct**,具体介绍可以看这一篇文章:https://zhuanlan.zhihu.com/p/622617292 实际上,langchain中就使用了ReAct这一策略。 198 | 199 | # 参考 200 | 201 | > [Introduction | 🦜️🔗 Langchain](https://python.langchain.com/docs/get_started/introduction.html) 202 | > 203 | > [API Reference — 🦜🔗 LangChain 0.0.229](https://api.python.langchain.com/en/latest/api_reference.html) 204 | > 205 | > [https://mp.weixin.qq.com/s/FvRchiT0c0xHYscO_D-sdA](https://python.langchain.com.cn/docs/modules/agents/how_to/custom_llm_chat_agent) 206 | > 207 | > https://python.langchain.com.cn/docs/modules/agents/how_to/custom_llm_chat_agent 208 | -------------------------------------------------------------------------------- /prompt工程.assets/v2-15b0f2e85c19c728a790e2e18e2ab51a_r.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/prompt工程.assets/v2-15b0f2e85c19c728a790e2e18e2ab51a_r.jpg -------------------------------------------------------------------------------- /prompt工程.assets/v2-7b3d72c23ca12e980564897f16923a9e_1440w.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/prompt工程.assets/v2-7b3d72c23ca12e980564897f16923a9e_1440w.webp -------------------------------------------------------------------------------- /prompt工程.md: -------------------------------------------------------------------------------- 1 | 在实际开发的过程中,除了模型自身的能力外,一个好的提示工程对结果有很大的影响,以下整理了一些使用提示工程中可能用到的方法: 2 | 3 | ### langchain中openai的基本使用 4 | 5 | ```python 6 | import openai 7 | 8 | openai_api_key = "" 9 | 10 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 11 | from langchain.chat_models import ChatOpenAI 12 | from langchain.llms import OpenAI 13 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 14 | # 用一个比较高的temperature值以获得一些更有意思的结果 15 | llm = OpenAI(model_name="gpt-3.5-turbo", 16 | openai_api_key=openai_api_key, 17 | temperature=0.0) 18 | 19 | prompt = "" 20 | print(llm(prompt)) 21 | ``` 22 | 23 | ### prompt的原则 24 | 25 | - 要描述清楚自己所需要做的任务。 26 | - 给模型思考的时间。 27 | 28 | ### 基本模板 29 | 30 | 一个指令应该有三部分构成: 31 | 32 | - 针对该任务的描述。 33 | - 需要处理的文本。 34 | - 完成后应该输出的结果格式。 35 | 36 | 比如: 37 | 38 | ```python 39 | 你现在是一个翻译专家,你需要把以下内容翻译为中文:i like china。请你只输出翻译后的文本。 40 | ``` 41 | 42 | ### 灵活使用一些标点符号或数据结构 43 | 44 | 比如我们可以在每一部分用'\n'分开: 45 | 46 | ```python 47 | 你现在是一个翻译专家,你需要把以下内容翻译为中文:\ni like china。\n请你只输出翻译后的文本。 48 | 49 | 你现在是一个翻译专家,你需要把以下内容翻译为中文:```i like china```。请你只输出翻译后的文本。 50 | ``` 51 | 52 | 还可以在提示里面加入字典或列表等提供结构化的知识。 53 | 54 | ### 指定输出格式 55 | 56 | ```python 57 | 你现在是一个翻译专家,你需要把以下内容翻译为中文:我喜欢中国。请你输出一个字典,字典格式为{"source":"要翻译的文本","target":"翻译后的文本"}。 58 | 59 | 要求生成三个虚构书名及其作者、流派的列表,以 JSON 格式输出,包括以下字段:图书的ID、书名、作者和流派。 60 | ``` 61 | 62 | ### 要求模型检查是否满足条件 63 | 64 | 有时模型不能按我们的要求回答,这是我们要提供一个其他的选择。 65 | 66 | ### few-shot 67 | 68 | 提供一个或几个样例给模型。 69 | 70 | ```python 71 | Pompt示例:你的任务是按照下面的风格回答问题。 72 | 73 | <学生>:什么叫做耐心 74 | 75 | <老师>:河水从泉眼流向森林深处,最热烈的奔流来自长期的凝聚力。 76 | 77 | <学生>:什么叫做坚韧 78 | 79 | Response示例:大树在狂风中摇曳却并不会折断,每次暴风中愈加强壮。 80 | ``` 81 | 82 | ### 把一些复杂的任务分解为小任务 83 | 84 | 如果给模型复杂的任务,应该把任务拆解成若干个小的任务(一般是推理任务)+最后的任务(一般是执行) 85 | 86 | Prompt示例:请完成以下的任务:1-总结 2-翻译 3-输出JSON 87 | 88 | **指示ChatGPT拆解问题并逐步推理得到答案,而不是直接输出答案(这个技巧会有意想不到的神奇~)** 89 | 90 | 这个例子非常有意思,示例中让模型去判断学生做数学题的答案(包括推导和结论)是否是对的。如果直接问ChatGPT,学生的答案是否正确,ChatGPT会说学生的答案是对的:( 91 | 92 | 但是!如果修改Prompt为:请判断答案是否是对的。请按照下面的步骤判断 1-理解题目并得出自己的推导过程 2-比较你的推导过程和学生的,你需要自己按照学生的推导过程计算并比较结果。 93 | 94 | 然后ChatGPT就会发现学生的过程是错的!只是结果蒙对了!(此处需要一个捂脸的表情包) 95 | 96 | 再比如: 97 | 98 | 我们描述了 Jack 和 Jill 的故事。然后,我将复制一份提示。在这个提示中,说明执行以下操作: 99 | 100 | - 首先,用一句话总结由三个反引号```分隔的以下文本。 101 | - 其次,将摘要翻译成法语。 102 | - 第三,在法语摘要中列出每个名字。 103 | - 第四,输出一个 JSON 对象,包括以下字段:法语摘要和名字的数量。 104 | 105 | #### 在模型得出结果之前,我们自己解决任务 106 | 107 | 有时候我们的任务过程没有那么详细的步骤,只有一个最终结果。这时我们可以思考,**我们自己是怎么解决这个任务的**。比如,上述的数学题中,我们也是通过先进行计算,再和题目结果进行比较,最终得到结果**。因此,让模型思考它是怎么得出这个结果的,可以有效的避免其没有事实依据而随便作答。** 108 | 109 | 再举个例子:比如放一首孤勇者。想一下我们是怎么操作的: 110 | 111 | - 打开音乐播放类app,也可以是其他的一些平台。 112 | - 搜索得到孤勇者歌曲。 113 | - 播放该歌曲。 114 | 115 | 因此我们要按照上述的操作让模型一步步进行。 116 | 117 | ### 处理模型幻觉 118 | 119 | 将预先得到的知识添加到prompt里面。 120 | 121 | ### 迭代式的开发流程 122 | 123 | 从椅子的详细的技术规格说明书,来生成电商网站的描述信息。 124 | 125 | 1. 指示模型生产电商网站的描述的草稿。 126 | 2. 发现字数太多。修改Prompt,尝试添加字数(句子的个数/字符的个数)限制。 127 | 3. 电商网站的客户主要是零售商户,而不是大众消费者。修改Prompt,告诉模型这个描述的受众是谁,倾向是啥。 128 | 4. 添加细节。修改Prompt,要求模型输出在结尾处,包含产品ID等。 129 | 5. 添加细节。修改Prompt,要求生成表格,指定表格标题。 130 | 6. 要求输出结果为HTML。 131 | 132 | 总而言之,可以分为以下步骤: 133 | 134 | - 确定自己要进行的任务。 135 | - 制定初步的草稿。 136 | - 不断加入细节。 137 | - 指定输出格式。 138 | - 把它们组装起来。 139 | 140 | ### 文字总结式任务 141 | 142 | **摘要任务**:首先要告诉模型摘要的任务是什么,即摘要的意图和关注点,比如对于一个新闻的文章,我们关注的是其讲述的一个具体的事件。 143 | 144 | **抽取任务**:明确是“抽取/extract”。 145 | 146 | ### 推断式任务 147 | 148 | 直接看一些例子: 149 | 150 | #### 情感分析 151 | 152 | Prompt示例:判断这段用户评价的情感是什么,情感从['正面的', '负面的', '中性的']里选择。 153 | 154 | Prompt示例:在这段评价中识别不超过五种不同类型的情感。 155 | 156 | Prompt示例:这段评价是表达愤怒吗? 157 | 158 | ### 实体抽取 159 | 160 | Prompt示例:识别以下信息:评论人买了什么产品,产品制造商,产品品牌 161 | 162 | #### 话题抽取 163 | 164 | Prompt示例:识别文中讨论的五个话题 165 | 166 | Prompt示例:topic_list = [‘NASA’,‘工程优化’,‘员工满意度’], 识别文中包含列表中的哪几个话题? 167 | 168 | ### 转换 169 | 170 | #### 翻译、重写 171 | 172 | 识别是哪种语言、翻译重写**带风格**(文言文、模仿海盗的英语风格、模仿商务语气、按照APA格式等) 173 | 174 | #### 格式转换 175 | 176 | JSON转HTML等 177 | 178 | #### 拼写检查、语法检查 179 | 180 | ### 扩写 181 | 182 | 这里注意一个参数:Temperature 183 | 184 | Tempeature越低(等于或接近0),ChatGPT越保守、更靠谱 185 | Tempeature越高(等于或接近2),ChatGPT越具有创造力、更天马行空 186 | 187 | ### 聊天机器人 188 | 189 | 区分不同消息源,比如: 190 | 191 | - 系统:最开始对任务的介绍。 192 | - 人类:我们输入的消息。 193 | - AI:模型返回给我们的结果。 194 | 195 | 一般的,我们每次都会将人类和AI的消息不断的加入的整个消息列表中,从而进行多轮的对话。 196 | 197 | ### 问题拆分 198 | 199 | 如果你想要用向量数据库实现GPT机器人,你可能会遇到这样的问题:如果用户在一句话里提出了几个问题,比如:`上一届世界杯是在哪年进行的?中国足球夺冠了吗?` 200 | 201 | 如果你直接用向量搜索这个问题,数据库会优先匹配**既包含世界杯举办年,又包含中国足球夺冠信息**的内容。这时候很容易就会得到匹配度较低的参考资料,GPT也无法回答出正确的答案。 202 | 203 | 但实际上,我们的预期其实是将它作为两个独立的问题进行。先匹配世界杯举办年,回答第一个问题,再匹配国足夺冠,回答第二个问题。这种需求,我将其称之为**问题拆分**,就很适合通过自己编写的Agent完成。 204 | 205 | 我们需要编写三个Tool,第一个,用于问题的拆分。 206 | 207 | ![img](prompt工程.assets/v2-15b0f2e85c19c728a790e2e18e2ab51a_r.jpg) 208 | 209 | 第二个Tool,则是负责向量搜索(向量搜索的代码简化了) 210 | 211 | ![img](prompt工程.assets/v2-7b3d72c23ca12e980564897f16923a9e_1440w.webp) 212 | 213 | 再按照类似方式写一个Tool,负责得出最后的答案就可以了 214 | 215 | 将这几个Tool结合起来,创建一个Agent,就得到了一个有问题拆分能力的聊天机器人。 216 | 217 | 注意上述示例的代码不是python版本的,可以参考我们讲解的其它的一些示例进行修改。 218 | 219 | > [实现你自己的AutoGPT —— Langchain Agent - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/634649618) 220 | 221 | ### 实例 222 | 223 | 以下收集整理一些prompt相关的一些经验文章: 224 | 225 | - [如何写一个小红书的爆款标题和短评](https://zhuanlan.zhihu.com/p/644419485) 226 | 227 | ### 参考 228 | 229 | > https://zhuanlan.zhihu.com/p/625973388:上述部分内容摘自这 230 | > 231 | > **【开发者的提示工程】PDF版本**:https://github.com/youcans/GPT-Prompt-Tutorial 232 | > 233 | > [【ChatGPT Prompt Engineering for Developers】https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/](https://link.zhihu.com/?target=https%3A//www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/) 234 | > 235 | > https://zhuanlan.zhihu.com/p/626966526 -------------------------------------------------------------------------------- /中文例子/README.md: -------------------------------------------------------------------------------- 1 | 主要是怎么让langchain支持加载中文大语言模型,然后利用一些组件完成相关的任务。 2 | 3 | 可以参考的资源有: 4 | 5 | - [imClumsyPanda/langchain-ChatGLM: langchain-ChatGLM, local knowledge based ChatGLM with langchain | 基于本地知识库的 ChatGLM 问答 (github.com)](https://github.com/imClumsyPanda/langchain-ChatGLM) 6 | 7 | - [yanqiangmiffy/Chinese-LangChain: 中文langchain项目|小必应,Q.Talk,强聊,QiangTalk (github.com)](https://github.com/yanqiangmiffy/Chinese-LangChain) 8 | 9 | - [HaxyMoly/Vicuna-LangChain: A simple LangChain-like implementation based on Sentence Embedding+local knowledge base, with Vicuna (FastChat) serving as the LLM. Supports both Chinese and English, and can process PDF, HTML, and DOCX formats of documents as knowledge base. (github.com)](https://github.com/HaxyMoly/Vicuna-LangChain) 10 | - [FredGoo/langchain-chinese-chat-models: 为langchain添加chatGLM-130B,星火大模型的chat models (github.com)](https://github.com/FredGoo/langchain-chinese-chat-models) 11 | - [API Reference — 🦜🔗 LangChain 0.0.221](https://api.python.langchain.com/en/latest/api_reference.html) 12 | - https://python.langchain.com/docs/ 13 | 14 | - [langchain中文文档](https://python.langchain.com.cn/docs/) 15 | -------------------------------------------------------------------------------- /中文例子/chatglm实现agent控制.assets/v2-3e8e500321140396dfe4a4ae6b120fd4_720w.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/中文例子/chatglm实现agent控制.assets/v2-3e8e500321140396dfe4a4ae6b120fd4_720w.webp -------------------------------------------------------------------------------- /中文例子/mini-langchain-ChatGLM.md: -------------------------------------------------------------------------------- 1 | 看了这么多源码以及实例,接下来我们将做一个最小限度的langchain-chatglm: 2 | 3 | ## 从文本中加载数据 4 | 5 | ```python 6 | text = """ 7 | ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。 ChatGLM-6B 使用了和 ChatGPT 相似的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答,更多信息请参考我们的博客。 8 | 9 | 为了方便下游开发者针对自己的应用场景定制模型,我们同时实现了基于 P-Tuning v2 的高效参数微调方法 (使用指南) ,INT4 量化级别下最低只需 7GB 显存即可启动微调。 10 | 11 | 想让 ChatGLM-6B 更符合你的应用场景?欢迎参与 Badcase 反馈计划。 12 | 13 | ChatGLM-6B 开源模型旨在与开源社区一起推动大模型技术发展,恳请开发者和大家遵守开源协议,勿将开源模型和代码及基于开源项目产生的衍生物用于任何可能给国家和社会带来危害的用途以及用于任何未经过安全评估和备案的服务。目前,本项目团队未基于 ChatGLM-6B 开发任何应用,包括网页端、安卓、苹果 iOS 及 Windows App 等应用。 14 | 15 | 尽管模型在训练的各个阶段都尽力确保数据的合规性和准确性,但由于 ChatGLM-6B 模型规模较小,且模型受概率随机性因素影响,无法保证输出内容的准确性,且模型易被误导(详见局限性)。本项目不承担开源模型和代码导致的数据安全、舆情风险或发生任何模型被误导、滥用、传播、不当利用而产生的风险和责任。 16 | 17 | 更新信息 18 | [2023/06/25] 发布 ChatGLM2-6B,ChatGLM-6B 的升级版本,在保留了了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上,ChatGLM2-6B 引入了如下新特性: 19 | 20 | 更强大的性能:基于 ChatGLM 初代模型的开发经验,我们全面升级了 ChatGLM2-6B 的基座模型。ChatGLM2-6B 使用了 GLM 的混合目标函数,经过了 1.4T 中英标识符的预训练与人类偏好对齐训练,评测结果显示,相比于初代模型,ChatGLM2-6B 在 MMLU(+23%)、CEval(+33%)、GSM8K(+571%) 、BBH(+60%)等数据集上的性能取得了大幅度的提升,在同尺寸开源模型中具有较强的竞争力。 21 | 更长的上下文:基于 FlashAttention 技术,我们将基座模型的上下文长度(Context Length)由 ChatGLM-6B 的 2K 扩展到了 32K,并在对话阶段使用 8K 的上下文长度训练,允许更多轮次的对话。但当前版本的 ChatGLM2-6B 对单轮超长文档的理解能力有限,我们会在后续迭代升级中着重进行优化。 22 | 更高效的推理:基于 Multi-Query Attention 技术,ChatGLM2-6B 有更高效的推理速度和更低的显存占用:在官方的模型实现下,推理速度相比初代提升了 42%,INT4 量化下,6G 显存支持的对话长度由 1K 提升到了 8K。 23 | 更多信息参见 ChatGLM2-6B。 24 | 25 | [2023/06/14] 发布 WebGLM,一项被接受于KDD 2023的研究工作,支持利用网络信息生成带有准确引用的长回答。 26 | """ 27 | 28 | import logging 29 | from typing import List, Optional, Any 30 | 31 | from langchain.docstore.document import Document 32 | from langchain.document_loaders.base import BaseLoader 33 | from langchain.document_loaders.helpers import detect_file_encodings 34 | 35 | class StrLoader(BaseLoader): 36 | """Load text files. 37 | 38 | 39 | Args: 40 | file_path: Path to the file to load. 41 | 42 | encoding: File encoding to use. If `None`, the file will be loaded 43 | with the default system encoding. 44 | 45 | autodetect_encoding: Whether to try to autodetect the file encoding 46 | if the specified encoding fails. 47 | """ 48 | 49 | def __init__( 50 | self, 51 | text: str, 52 | encoding: Optional[str] = None, 53 | autodetect_encoding: bool = False, 54 | ): 55 | """Initialize with file path.""" 56 | self.text = text 57 | self.encoding = encoding 58 | self.autodetect_encoding = autodetect_encoding 59 | 60 | def load(self) -> List[Document]: 61 | # 从文本加载 62 | metadata = {"source": "chatglm.md"} 63 | return [Document(page_content=text, metadata=metadata)] 64 | 65 | print("文本长度:", len(text)) 66 | 67 | loader = StrLoader(text) 68 | ``` 69 | 70 | 继承BaseLoader,重写load方法,返回一个Document类,其中page_content是我们的文本,metadata是一些额外的元信息,比如文本的路径、文本的描述等。 71 | 72 | ## 将文本进行分割 73 | 74 | ```python 75 | 76 | from langchain.text_splitter import CharacterTextSplitter 77 | 78 | class CharacterStrSplitter(CharacterTextSplitter): 79 | """Implementation of splitting text that looks at characters.""" 80 | 81 | def __init__(self, separator: str = "\n\n", **kwargs: Any) -> None: 82 | """Create a new TextSplitter.""" 83 | super().__init__(**kwargs) 84 | self._separator = separator 85 | 86 | def split_text(self, text: str) -> List[str]: 87 | """Split incoming text and return chunks.""" 88 | # First we naively split the large input into a bunch of smaller ones. 89 | splits = text.split(self._separator) 90 | _separator = "" if self._keep_separator else self._separator 91 | return self._merge_splits(splits, _separator) 92 | 93 | text_splitter = CharacterStrSplitter( 94 | separator = "\n\n", 95 | chunk_size = 512, 96 | chunk_overlap = 100, 97 | length_function = len, 98 | ) 99 | docs = loader.load_and_split(text_splitter) 100 | from pprint import pprint 101 | pprint(docs) 102 | 103 | """ 104 | WARNING:langchain.text_splitter:Created a chunk of size 535, which is longer than the specified 512 105 | 文本长度: 1442 106 | [Document(page_content='ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。 ChatGLM-6B 使用了和 ChatGPT 相似的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答,更多信息请参考我们的博客。\n\n为了方便下游开发者针对自己的应用场景定制模型,我们同时实现了基于 P-Tuning v2 的高效参数微调方法 (使用指南) ,INT4 量化级别下最低只需 7GB 显存即可启动微调。\n\n想让 ChatGLM-6B 更符合你的应用场景?欢迎参与 Badcase 反馈计划。', metadata={'source': 'chatglm.md'}), 107 | Document(page_content='想让 ChatGLM-6B 更符合你的应用场景?欢迎参与 Badcase 反馈计划。\n\nChatGLM-6B 开源模型旨在与开源社区一起推动大模型技术发展,恳请开发者和大家遵守开源协议,勿将开源模型和代码及基于开源项目产生的衍生物用于任何可能给国家和社会带来危害的用途以及用于任何未经过安全评估和备案的服务。目前,本项目团队未基于 ChatGLM-6B 开发任何应用,包括网页端、安卓、苹果 iOS 及 Windows App 等应用。\n\n尽管模型在训练的各个阶段都尽力确保数据的合规性和准确性,但由于 ChatGLM-6B 模型规模较小,且模型受概率随机性因素影响,无法保证输出内容的准确性,且模型易被误导(详见局限性)。本项目不承担开源模型和代码导致的数据安全、舆情风险或发生任何模型被误导、滥用、传播、不当利用而产生的风险和责任。\n\n更新信息\n[2023/06/25] 发布 ChatGLM2-6B,ChatGLM-6B 的升级版本,在保留了了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上,ChatGLM2-6B 引入了如下新特性:', metadata={'source': 'chatglm.md'}), 108 | Document(page_content='更强大的性能:基于 ChatGLM 初代模型的开发经验,我们全面升级了 ChatGLM2-6B 的基座模型。ChatGLM2-6B 使用了 GLM 的混合目标函数,经过了 1.4T 中英标识符的预训练与人类偏好对齐训练,评测结果显示,相比于初代模型,ChatGLM2-6B 在 MMLU(+23%)、CEval(+33%)、GSM8K(+571%) 、BBH(+60%)等数据集上的性能取得了大幅度的提升,在同尺寸开源模型中具有较强的竞争力。\n更长的上下文:基于 FlashAttention 技术,我们将基座模型的上下文长度(Context Length)由 ChatGLM-6B 的 2K 扩展到了 32K,并在对话阶段使用 8K 的上下文长度训练,允许更多轮次的对话。但当前版本的 ChatGLM2-6B 对单轮超长文档的理解能力有限,我们会在后续迭代升级中着重进行优化。\n更高效的推理:基于 Multi-Query Attention 技术,ChatGLM2-6B 有更高效的推理速度和更低的显存占用:在官方的模型实现下,推理速度相比初代提升了 42%,INT4 量化下,6G 显存支持的对话长度由 1K 提升到了 8K。\n更多信息参见 ChatGLM2-6B。', metadata={'source': 'chatglm.md'}), 109 | Document(page_content='[2023/06/14] 发布 WebGLM,一项被接受于KDD 2023的研究工作,支持利用网络信息生成带有准确引用的长回答。', metadata={'source': 'chatglm.md'})] 110 | """ 111 | ``` 112 | 113 | 继承CharacterTextSplitter,重写split_text方法,这里我只简单的进行\n\n分割文本,可根据自己的需求定义。 114 | 115 | 初始化的参数: 116 | 117 | - separator:分割符 118 | - chunk_size:文本块的长度 119 | - chunk_overlap:文本块之间重叠的长度 120 | - length_function:计算长度的方法,这里也可以先进行tokenizer化,再计算长度 121 | 122 | ## 使用模型转换分割后的文本并进行存储 123 | 124 | ```python 125 | from langchian. 126 | embeddings = HuggingFaceEmbeddings(model_name="GanymedeNil/text2vec-large-chinese", model_kwargs={'device': ""}) 127 | from langchain.vectorstores.faiss import FAISS 128 | vector_store = FAISS.from_documents(docs, embeddings) 129 | 130 | related_docs_with_score = vector_store.similarity_search_with_score(query, k=self.top_k) 131 | 132 | """ 133 | [(Document(page_content='ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。 ChatGLM-6B 使用了和 ChatGPT 相似的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答,更多信息请参考我们的博客。\n\n为了方便下游开发者针对自己的应用场景定制模型,我们同时实现了基于 P-Tuning v2 的高效参数微调方法 (使用指南) ,INT4 量化级别下最低只需 7GB 显存即可启动微调。\n\n想让 ChatGLM-6B 更符合你的应用场景?欢迎参与 Badcase 反馈计划。', metadata={'source': 'chatglm.md'}), 134 | 488.00125), 135 | (Document(page_content='更强大的性能:基于 ChatGLM 初代模型的开发经验,我们全面升级了 ChatGLM2-6B 的基座模型。ChatGLM2-6B 使用了 GLM 的混合目标函数,经过了 1.4T 中英标识符的预训练与人类偏好对齐训练,评测结果显示,相比于初代模型,ChatGLM2-6B 在 MMLU(+23%)、CEval(+33%)、GSM8K(+571%) 、BBH(+60%)等数据集上的性能取得了大幅度的提升,在同尺寸开源模型中具有较强的竞争力。\n更长的上下文:基于 FlashAttention 技术,我们将基座模型的上下文长度(Context Length)由 ChatGLM-6B 的 2K 扩展到了 32K,并在对话阶段使用 8K 的上下文长度训练,允许更多轮次的对话。但当前版本的 ChatGLM2-6B 对单轮超长文档的理解能力有限,我们会在后续迭代升级中着重进行优化。\n更高效的推理:基于 Multi-Query Attention 技术,ChatGLM2-6B 有更高效的推理速度和更低的显存占用:在官方的模型实现下,推理速度相比初代提升了 42%,INT4 量化下,6G 显存支持的对话长度由 1K 提升到了 8K。\n更多信息参见 ChatGLM2-6B。', metadata={'source': 'chatglm.md'}), 136 | 538.00037)] 137 | """ 138 | ``` 139 | 140 | 这里可参考已经讲解的文章。 141 | 142 | ## 构建prompt 143 | 144 | ```python 145 | PROMPT_TEMPLATE = """已知信息: 146 | {context} 147 | 148 | 根据上述已知信息,简洁和专业的来回答用户的问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题” 或 “没有提供足够的相关信息”,不允许在答案中添加编造成分,答案请使用中文。 问题是:{question}""" 149 | 150 | context = "\n".join([doc[0].page_content for doc in related_docs_with_score]) 151 | question = query 152 | 153 | prompt = PROMPT_TEMPLATE.replace("{question}", query).replace("{context}", context) 154 | print(prompt) 155 | 156 | """ 157 | 已知信息: 158 | ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。 ChatGLM-6B 使用了和 ChatGPT 相似的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答,更多信息请参考我们的博客。 159 | 160 | 为了方便下游开发者针对自己的应用场景定制模型,我们同时实现了基于 P-Tuning v2 的高效参数微调方法 (使用指南) ,INT4 量化级别下最低只需 7GB 显存即可启动微调。 161 | 162 | 想让 ChatGLM-6B 更符合你的应用场景?欢迎参与 Badcase 反馈计划。 163 | 更强大的性能:基于 ChatGLM 初代模型的开发经验,我们全面升级了 ChatGLM2-6B 的基座模型。ChatGLM2-6B 使用了 GLM 的混合目标函数,经过了 1.4T 中英标识符的预训练与人类偏好对齐训练,评测结果显示,相比于初代模型,ChatGLM2-6B 在 MMLU(+23%)、CEval(+33%)、GSM8K(+571%) 、BBH(+60%)等数据集上的性能取得了大幅度的提升,在同尺寸开源模型中具有较强的竞争力。 164 | 更长的上下文:基于 FlashAttention 技术,我们将基座模型的上下文长度(Context Length)由 ChatGLM-6B 的 2K 扩展到了 32K,并在对话阶段使用 8K 的上下文长度训练,允许更多轮次的对话。但当前版本的 ChatGLM2-6B 对单轮超长文档的理解能力有限,我们会在后续迭代升级中着重进行优化。 165 | 更高效的推理:基于 Multi-Query Attention 技术,ChatGLM2-6B 有更高效的推理速度和更低的显存占用:在官方的模型实现下,推理速度相比初代提升了 42%,INT4 量化下,6G 显存支持的对话长度由 1K 提升到了 8K。 166 | 更多信息参见 ChatGLM2-6B。 167 | 168 | 根据上述已知信息,简洁和专业的来回答用户的问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题” 或 “没有提供足够的相关信息”,不允许在答案中添加编造成分,答案请使用中文。 问题是:chatglm-6b是什么? 169 | """ 170 | ``` 171 | 172 | 将查询出的内容和query以及基本模板进行整合。 173 | 174 | ## 根据prompt用模型生成结果 175 | 176 | 首先我们使用原始的模型直接对query进行预测: 177 | 178 | ```python 179 | from transformers import AutoTokenizer, AutoModel 180 | tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b-int4", trust_remote_code=True) 181 | model = AutoModel.from_pretrained("THUDM/chatglm-6b-int4", trust_remote_code=True).half().cuda() 182 | response, history = model.chat(tokenizer, query, history=[]) 183 | print(response) 184 | """ 185 | ChatGLM-6B 是一个基于清华大学 KEG 实验室和智谱 AI 公司于 2023 年共同训练的语言模型 GLM-6B 开发的人工智能助手。 186 | """ 187 | ``` 188 | 189 | 然后是使用带有知识的提问: 190 | 191 | ```python 192 | response, history = model.chat(tokenizer, prompt, history=[]) 193 | print(response) 194 | """ 195 | ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。ChatGLM-6B 使用了和 ChatGPT 相似的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答。 196 | """ 197 | ``` 198 | 199 | ## 总结 200 | 201 | 到这里,一个简单的基于知识的问道就全部完成了。 202 | 203 | 当然,prompt的构建和模型的使用我们并没有和langchain进行整合,整合的难度也不是很大,可参考已经写好的文章。 -------------------------------------------------------------------------------- /中文例子/使用中文splitter.md: -------------------------------------------------------------------------------- 1 | 之前已经讲解过了ChineseTextSplitter后面的原理,可以找到相关文章进行查看。 2 | 3 | 这里将langchian-chatglm里面的拿出来讲讲,先看代码: 4 | 5 | ```python 6 | from langchain.text_splitter import CharacterTextSplitter 7 | from langchain.document_loaders import TextLoader 8 | import re 9 | from typing import List 10 | 11 | 12 | 13 | class ChineseTextSplitter(CharacterTextSplitter): 14 | def __init__(self, pdf: bool = False, sentence_size: int = None, **kwargs): 15 | super().__init__(**kwargs) 16 | self.pdf = pdf 17 | self.sentence_size = sentence_size 18 | 19 | def split_text1(self, text: str) -> List[str]: 20 | if self.pdf: 21 | text = re.sub(r"\n{3,}", "\n", text) 22 | text = re.sub('\s', ' ', text) 23 | text = text.replace("\n\n", "") 24 | sent_sep_pattern = re.compile('([﹒﹔﹖﹗.。!?]["’”」』]{0,2}|(?=["‘“「『]{1,2}|$))') # del :; 25 | sent_list = [] 26 | for ele in sent_sep_pattern.split(text): 27 | if sent_sep_pattern.match(ele) and sent_list: 28 | sent_list[-1] += ele 29 | elif ele: 30 | sent_list.append(ele) 31 | return sent_list 32 | 33 | def split_text(self, text: str) -> List[str]: ##此处需要进一步优化逻辑 34 | if self.pdf: 35 | text = re.sub(r"\n{3,}", r"\n", text) 36 | text = re.sub('\s', " ", text) 37 | text = re.sub("\n\n", "", text) 38 | 39 | text = re.sub(r'([;;.!?。!?\?])([^”’])', r"\1\n\2", text) # 单字符断句符 40 | text = re.sub(r'(\.{6})([^"’”」』])', r"\1\n\2", text) # 英文省略号 41 | text = re.sub(r'(\…{2})([^"’”」』])', r"\1\n\2", text) # 中文省略号 42 | text = re.sub(r'([;;!?。!?\?]["’”」』]{0,2})([^;;!?,。!?\?])', r'\1\n\2', text) 43 | # 如果双引号前有终止符,那么双引号才是句子的终点,把分句符\n放到双引号后,注意前面的几句都小心保留了双引号 44 | text = text.rstrip() # 段尾如果有多余的\n就去掉它 45 | # 很多规则中会考虑分号;,但是这里我把它忽略不计,破折号、英文双引号等同样忽略,需要的再做些简单调整即可。 46 | ls = [i for i in text.split("\n") if i] 47 | for ele in ls: 48 | if len(ele) > self.sentence_size: 49 | ele1 = re.sub(r'([,,.]["’”」』]{0,2})([^,,.])', r'\1\n\2', ele) 50 | ele1_ls = ele1.split("\n") 51 | for ele_ele1 in ele1_ls: 52 | if len(ele_ele1) > self.sentence_size: 53 | ele_ele2 = re.sub(r'([\n]{1,}| {2,}["’”」』]{0,2})([^\s])', r'\1\n\2', ele_ele1) 54 | ele2_ls = ele_ele2.split("\n") 55 | for ele_ele2 in ele2_ls: 56 | if len(ele_ele2) > self.sentence_size: 57 | ele_ele3 = re.sub('( ["’”」』]{0,2})([^ ])', r'\1\n\2', ele_ele2) 58 | ele2_id = ele2_ls.index(ele_ele2) 59 | ele2_ls = ele2_ls[:ele2_id] + [i for i in ele_ele3.split("\n") if i] + ele2_ls[ 60 | ele2_id + 1:] 61 | ele_id = ele1_ls.index(ele_ele1) 62 | ele1_ls = ele1_ls[:ele_id] + [i for i in ele2_ls if i] + ele1_ls[ele_id + 1:] 63 | 64 | id = ls.index(ele) 65 | ls = ls[:id] + [i for i in ele1_ls if i] + ls[id + 1:] 66 | return ls 67 | 68 | filepath = "/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt" 69 | sentence_size = 100 70 | loader = TextLoader(filepath, autodetect_encoding=True) 71 | textsplitter = ChineseTextSplitter(pdf=False, sentence_size=sentence_size) 72 | docs = loader.load_and_split(textsplitter) 73 | 74 | from pprint import pprint 75 | 76 | pprint(docs) 77 | 78 | “”“ 79 | Document(page_content='本节开始后不久,奇才两度三分命中,而塞拉芬此后又空中接力扣篮,奇才很快就反超比分。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 80 | Document(page_content='本节还有3分57秒时,杰弗森上篮后,爵士将比分追成36-39,沃尔马上突破上篮还以一球,克劳福德此后也中投命中,奇才又将差距拉开。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 81 | Document(page_content='本节奇才以35-22大胜,上半场以51-43领先。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 82 | Document(page_content='易建联在第三节终于首次得分,本节开始后不久,他抢下进攻篮板,直接补篮得手,奇才取得10分的优势。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 83 | Document(page_content='本节后半段易建联又两度投篮命中,本节还有2分25秒时,易建联中投得手后,奇才以70-61领先,马丁紧接着三分得手,奇才又取得两位数的优势。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 84 | Document(page_content='易建联本节得了6分,三节过后,奇才以73-63领先。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 85 | Document(page_content='第四节易建联只打了3分钟就6次犯规下场,他还没来得及出手投篮,只抢下一个篮板。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 86 | Document(page_content='奇才遭到爵士顽强的反击,在本节还有1分30秒时,埃文斯上篮得手,爵士只以82-83落后。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 87 | Document(page_content='沃尔关键时刻的投篮被盖帽,埃文斯两罚两中后,爵士在本节结束前45.', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 88 | Document(page_content='1秒以84-83反超,这是他们下半场首度领先。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 89 | Document(page_content='第四节还有7分36秒时起,奇才在7分32秒内未能投中一球,在这段时间只得了1分。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 90 | Document(page_content='爵士领先2分后,克劳福德终于在本节还有4.', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 91 | “”“ 92 | ``` 93 | 94 | 当然,我们也可以根据自己的需求修改split_text里面的逻辑。 -------------------------------------------------------------------------------- /中文例子/定制中文模型.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 目前langchain都是基于openai的模型进行的,本文将讲解下**怎么定制化使用中文的模型**。为了方便起见,这里使用的模型为cpm-bee-1b。 3 | 4 | # 定制中文模型 5 | 首先我们得看下cpm-bee-1b是怎么使用的: 6 | ```python 7 | from transformers import AutoModelForCausalLM, AutoTokenizer 8 | tokenizer = AutoTokenizer.from_pretrained("openbmb/cpm-bee-1b", trust_remote_code=True) 9 | model = AutoModelForCausalLM.from_pretrained("openbmb/cpm-bee-1b", trust_remote_code=True).cuda() 10 | result = model.generate({"input": "今天天气真", "": ""}, tokenizer) 11 | print(result) 12 | result = model.generate({"input": "今天天气真不错", "question": "今天天气怎么样?", "": ""}, tokenizer) 13 | print(result) 14 | ``` 15 | 输入是一个字典,而且有两种方式: 16 | - 带有question,根据input的内容进行回答。 17 | - 不带有question,根据input继续生成文本。 18 | 19 | 另外,还有一个键,生成的结果会存到它的值里面。上述结果: 20 | ```python 21 | [{'input': '今天天气真', '': '今天天气真好'}] 22 | [{'input': '今天天气真不错', 'question': '今天天气怎么样?', '': '好'}] 23 | ``` 24 | 25 | 要在langchain使用中文模型,我们要继承langchain中的LLM类,它位于`from langchain.llms.base import LLM`,然后重写_llm_type、_call、_identifying_params方法。 26 | - _llm_type:用于标识模型名称 27 | - _call:里面实现推理逻辑,既可以是原生的模型推理,也可以是接口。 28 | - _identifying_params:用于帮助我们打印类的一些属性。 29 | 30 | 接下来看完整代码: 31 | ```python 32 | # 使用langchain加载中文模型 33 | # 继承LLM,并重写_llm_type、_call、_identifying_params方法 34 | import json 35 | from transformers import AutoModelForCausalLM, AutoTokenizer 36 | 37 | class ModelLoader: 38 | def __init__(self, model_name_or_path): 39 | self.model_name_or_path = model_name_or_path 40 | self.model, self.tokenizer = self.load() 41 | 42 | def load(self): 43 | tokenizer = AutoTokenizer.from_pretrained(self.model_name_or_path, trust_remote_code=True) 44 | model = AutoModelForCausalLM.from_pretrained("openbmb/cpm-bee-1b", trust_remote_code=True).cuda() 45 | return model, tokenizer 46 | 47 | modelLoader = ModelLoader("openbmb/cpm-bee-1b") 48 | 49 | from typing import Any, List, Mapping, Optional 50 | from langchain.llms.base import LLM 51 | class CpmBee(LLM): 52 | @property 53 | def _llm_type(self) -> str: 54 | return "cpm-bee-1B" 55 | 56 | def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str: 57 | print(prompt) 58 | prompt = json.loads(prompt) 59 | tokenizer = modelLoader.tokenizer 60 | model = modelLoader.model 61 | result = model.generate(prompt, tokenizer) 62 | if len(result) == 1: 63 | return result[0][""] 64 | return "对不起,我没有理解你的意思" 65 | 66 | @property 67 | def _identifying_params(self) -> Mapping[str, Any]: 68 | params_dict = { 69 | "test": "这里是参数字典", 70 | } 71 | return params_dict 72 | 73 | prompt = {"input": "今天天气真不错", "question": "今天天气怎么样?", "": ""} 74 | cpmBee = CpmBee() 75 | 76 | print(cpmBee) 77 | 78 | print(cpmBee(json.dumps(prompt, ensure_ascii=False))) 79 | 80 | """ 81 | CpmBee 82 | Params: {'test': '这里是参数字典'} 83 | {"input": "今天天气真不错", "question": "今天天气怎么样?", "": ""} 84 | 好 85 | """ 86 | ``` 87 | 我们需要注意的几点: 88 | - prompt必须为一个字符串,而cpm-bee-1b的输入有点特殊,需要是一个字典,可能内部有对其进行转换,这里不作探讨。因此,我们在_call里面将其转换为字典。 89 | - 输出也要是一个字符串,因此,我们从cpm-bee-1b的结果中提取结果。 90 | 91 | # 总结 92 | 以上虽然只是一个简单的例子,但是也足够我们完成各种传统NLP的任务了。当然,更加复杂的一些任务我们还是需要借助langchain的其它一些特性的。 93 | 94 | -------------------------------------------------------------------------------- /中文例子/定制中文聊天模型.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 之前我们已经讲过在langchain中怎么使用中文模型: 3 | 4 | https://zhuanlan.zhihu.com/p/641631547 5 | 6 | 也讲过langchain中使用基于openai的聊天模型的原理: 7 | 8 | https://zhuanlan.zhihu.com/p/641823532 9 | 10 | 还未了解的可以先去看看。本文将讲解的是如何在langchain中使用中文的聊天模型,它和中文模型不太一样。 11 | 12 | # 定制中文聊天模型 13 | 首先我们得看下cpm-bee-1b是怎么使用的: 14 | ```python 15 | from transformers import AutoModelForCausalLM, AutoTokenizer 16 | tokenizer = AutoTokenizer.from_pretrained("openbmb/cpm-bee-1b", trust_remote_code=True) 17 | model = AutoModelForCausalLM.from_pretrained("openbmb/cpm-bee-1b", trust_remote_code=True).cuda() 18 | result = model.generate({"input": "今天天气真", "": ""}, tokenizer) 19 | print(result) 20 | result = model.generate({"input": "今天天气真不错", "question": "今天天气怎么样?", "": ""}, tokenizer) 21 | print(result) 22 | ``` 23 | 输入是一个字典,而且有两种方式: 24 | - 带有question,根据input的内容进行回答。 25 | - 不带有question,根据input继续生成文本。 26 | 27 | 另外,还有一个键,生成的结果会存到它的值里面。上述结果: 28 | ```python 29 | [{'input': '今天天气真', '': '今天天气真好'}] 30 | [{'input': '今天天气真不错', 'question': '今天天气怎么样?', '': '好'}] 31 | ``` 32 | 33 | 要在langchain使用中文模型,我们要继承langchain.chat_models.base中的SimpleChatModel类,它位于`from langchain.chat_models.base import SimpleChatModel`,然后重写_llm_type、_call、_identifying_params方法。 34 | - _llm_type:用于标识模型名称 35 | - _call:里面实现推理逻辑,既可以是原生的模型推理,也可以是接口。(这个是必须的) 36 | - _identifying_params:用于帮助我们打印类的一些属性。 37 | 38 | 接下来看完整代码: 39 | ```python 40 | import json 41 | from transformers import AutoModelForCausalLM, AutoTokenizer 42 | 43 | class ModelLoader: 44 | def __init__(self, model_name_or_path): 45 | self.model_name_or_path = model_name_or_path 46 | self.model, self.tokenizer = self.load() 47 | 48 | def load(self): 49 | tokenizer = AutoTokenizer.from_pretrained(self.model_name_or_path, trust_remote_code=True) 50 | model = AutoModelForCausalLM.from_pretrained("openbmb/cpm-bee-1b", trust_remote_code=True).cuda() 51 | return model, tokenizer 52 | 53 | modelLoader = ModelLoader("openbmb/cpm-bee-1b") 54 | 55 | from typing import Any, List, Mapping, Optional 56 | from langchain.llms.base import LLM 57 | from langchain.chat_models.base import SimpleChatModel 58 | from langchain.schema import HumanMessage 59 | 60 | import asyncio 61 | import inspect 62 | import warnings 63 | from abc import ABC, abstractmethod 64 | from functools import partial 65 | from typing import Any, Dict, List, Mapping, Optional, Sequence 66 | 67 | from pydantic import Field, root_validator 68 | 69 | import langchain 70 | from langchain.base_language import BaseLanguageModel 71 | from langchain.callbacks.base import BaseCallbackManager 72 | from langchain.callbacks.manager import ( 73 | AsyncCallbackManager, 74 | AsyncCallbackManagerForLLMRun, 75 | CallbackManager, 76 | CallbackManagerForLLMRun, 77 | Callbacks, 78 | ) 79 | from langchain.load.dump import dumpd, dumps 80 | from langchain.schema import ( 81 | ChatGeneration, 82 | ChatResult, 83 | LLMResult, 84 | PromptValue, 85 | RunInfo, 86 | ) 87 | from langchain.schema.messages import AIMessage, BaseMessage, HumanMessage 88 | from langchain.chat_models.base import BaseChatModel 89 | 90 | 91 | class ChatCpmBee(SimpleChatModel): 92 | def _generate( 93 | self, 94 | messages: List[BaseMessage], 95 | stop: Optional[List[str]] = None, 96 | run_manager: Optional[CallbackManagerForLLMRun] = None, 97 | **kwargs: Any, 98 | ) -> ChatResult: 99 | output_str = self._call(messages, stop=stop, run_manager=run_manager, **kwargs) 100 | message = AIMessage(content=output_str) 101 | generation = ChatGeneration(message=message) 102 | return ChatResult(generations=[generation]) 103 | 104 | def _call( 105 | self, 106 | messages: List[BaseMessage], 107 | stop: Optional[List[str]] = None, 108 | run_manager: Optional[CallbackManagerForLLMRun] = None, 109 | **kwargs: Any, 110 | ) -> str: 111 | """Simpler interface.""" 112 | generations = [] 113 | for mes in messages: 114 | prompt = json.loads(mes.content) 115 | tokenizer = modelLoader.tokenizer 116 | model = modelLoader.model 117 | result = model.generate(prompt, tokenizer) 118 | if len(result) == 1: 119 | return result[0][""] 120 | return "对不起,我没有理解你的意思" 121 | 122 | async def _agenerate( 123 | self, 124 | messages: List[BaseMessage], 125 | stop: Optional[List[str]] = None, 126 | run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, 127 | **kwargs: Any, 128 | ) -> ChatResult: 129 | func = partial( 130 | self._generate, messages, stop=stop, run_manager=run_manager, **kwargs 131 | ) 132 | return await asyncio.get_event_loop().run_in_executor(None, func) 133 | 134 | @property 135 | def _identifying_params(self) -> Mapping[str, Any]: 136 | params_dict = { 137 | "test": "这里是参数字典", 138 | } 139 | return params_dict 140 | 141 | @property 142 | def _llm_type(self) -> str: 143 | return "chat-cpm-bee-1B" 144 | 145 | prompt = {"input": "今天天气真不错", "question": "今天天气怎么样?", "": ""} 146 | messages = [ 147 | HumanMessage(content=json.dumps(prompt, ensure_ascii=False)) 148 | ] 149 | chatCpmBee = ChatCpmBee() 150 | print(chatCpmBee) 151 | 152 | chatCpmBee(messages) 153 | """ 154 | cache=None verbose=False callbacks=None callback_manager=None tags=None metadata=None 155 | AIMessage(content='好', additional_kwargs={}, example=False) 156 | """ 157 | ``` 158 | 需要注意,这里我们要传入的是一个message的列表,什么是message呢?message用于标识传过来的角色是什么,有AIMessage, BaseMessage, HumanMessage等,你也可以自定义角色。 159 | - AIMessage:模型返回的结果 160 | - HumanMessage:我们传入的文本消息 161 | - SystemMessage:前提消息,比如:你是一个有用的助手,接下来是你的任务描述。 162 | 163 | 这里我们模拟传入一个消息,在`_call`中最后会返回一个字符串。基于openai的聊天模型比这个简单的示例要复杂,具体可以看之前所讲解的。 164 | 165 | 最后看到,返回给我们的是一个AIMessage,输出的content为好。正好是我们的输出。 166 | 167 | 与普通的中文模型相比。有以下不同: 168 | - 普通的中文模型继承LLM,而聊天模型继承SimpleChatModel。 169 | - 聊天模型传入的是一个消息列表,而不是prompt(提示),输出的也是消息,而不是直接的字符串。 170 | 171 | 相同之处是都要实现`_call`方法。 172 | 173 | 到这里,你已经基本了解在langchain中怎么构建中文聊天模型了。 -------------------------------------------------------------------------------- /中文例子/打造简易版类小爱同学助手.md: -------------------------------------------------------------------------------- 1 | 这是一个简易版的助手,主要是意图识别和槽位填充。直接看代码: 2 | 3 | ```python 4 | # 定义意图及槽位 5 | LANUCH = { 6 | "name": "lanuch", 7 | "description": "打开某个app或者电器", 8 | "slots":{ 9 | "app_name": "app或者电器名", 10 | } 11 | } 12 | 13 | TRANSLATION = { 14 | "name": "translation", 15 | "description": "翻译", 16 | "slots": { 17 | "language": "语言", 18 | } 19 | } 20 | 21 | LOCAITON = { 22 | "name": "location", 23 | "description": "查询两个地点之间的路径", 24 | "slots": { 25 | "from_loation": "起始位置", 26 | "to_location": "终止位置", 27 | } 28 | } 29 | 30 | # from transformers import AutoTokenizer, AutoModel 31 | # tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b-int4", trust_remote_code=True) 32 | # model = AutoModel.from_pretrained("THUDM/chatglm-6b-int4", trust_remote_code=True).half().cuda() 33 | 34 | history = [] 35 | start_prompt = """你现在是一个识别意图的专家,给定一个文本,你需要识别文本里面的意图,请从 36 | `{打开某app或者电器:lanuch,翻译文本:translation,查询路径:location]`进行选择。 37 | 38 | ```文本:{text}``` 39 | 40 | 你需要返回意图对应的英文名,比如如果是翻译文本,则返回translation等。如果没有上述意图,则回答无。 41 | """ 42 | 43 | second_prompt = """ 44 | 给定文本:{text} 45 | 46 | 你已经知道该文本意图为`{intent}`,请识别里面的{slots}。 47 | 48 | 结果返回为一个字典,例如{text: 原始文本, intent:意图名, slots:按照字典里面格式填充值}。 49 | """ 50 | 51 | import openai 52 | 53 | openai_api_key = "" 54 | 55 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 56 | from langchain.chat_models import ChatOpenAI 57 | from langchain.llms import OpenAI 58 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 59 | # 用一个比较高的temperature值以获得一些更有意思的结果 60 | llm = OpenAI(model_name="gpt-3.5-turbo", 61 | openai_api_key=openai_api_key, 62 | temperature=0.0) 63 | 64 | 65 | str2intent = { 66 | "lanuch": LANUCH, 67 | "translation": TRANSLATION, 68 | "location": LOCAITON 69 | } 70 | 71 | zh2eng = { 72 | "打开某app或者电器": "lanuch", 73 | "翻译文本": "translation", 74 | "查询路径": "location", 75 | } 76 | 77 | text = "打开淘宝" 78 | history = [] 79 | while True: 80 | if len(history) == 0: 81 | text = str(input("user:")) 82 | prompt = start_prompt.replace("{text}", text) 83 | # response, history = model.chat(tokenizer, prompt, history=history) 84 | response = llm(prompt) 85 | print("chatgpt", response) 86 | if response.strip() in zh2eng: 87 | response = zh2eng[response] 88 | if response in ["lanuch", "translation", "location"]: 89 | history = second_prompt.replace("{text}", text).replace("{intent}", response) 90 | history = history.replace("{slots}", str(str2intent[response]["slots"])) 91 | print(history) 92 | response = llm(history) 93 | print("chatgpt", response) 94 | history = [] 95 | 96 | ``` 97 | 98 | 结果: 99 | 100 | ```python 101 | user:把我爱你翻译为英文 102 | chatgpt 翻译文本 103 | 104 | 给定文本:把我爱你翻译为英文 105 | 106 | 你已经知道该文本意图为`translation`,请识别里面的{'language': '语言'}。 107 | 108 | 结果返回为一个字典,例如{text: 原始文本, intent:意图名, slots:按照字典里面格式填充值}。 109 | 110 | chatgpt { 111 | "text": "把我爱你翻译为英文", 112 | "intent": "translation", 113 | "slots": { 114 | "language": "英文" 115 | } 116 | } 117 | user:武汉到深圳怎么去 118 | chatgpt 查询路径 119 | 120 | 给定文本:武汉到深圳怎么去 121 | 122 | 你已经知道该文本意图为`location`,请识别里面的{'from_loation': '起始位置', 'to_location': '终止位置'}。 123 | 124 | 结果返回为一个字典,例如{text: 原始文本, intent:意图名, slots:按照字典里面格式填充值}。 125 | 126 | chatgpt { 127 | "text": "武汉到深圳怎么去", 128 | "intent": "location", 129 | "slots": { 130 | "from_location": "武汉", 131 | "to_location": "深圳" 132 | } 133 | } 134 | user:打开天天象棋 135 | chatgpt lanuch 136 | 137 | 给定文本:打开天天象棋 138 | 139 | 你已经知道该文本意图为`lanuch`,请识别里面的{'app_name': 'app或者电器名'}。 140 | 141 | 结果返回为一个字典,例如{text: 原始文本, intent:意图名, slots:按照字典里面格式填充值}。 142 | 143 | chatgpt { 144 | "text": "打开天天象棋", 145 | "intent": "launch", 146 | "slots": { 147 | "app_name": "天天象棋" 148 | } 149 | } 150 | user:打开相机这 151 | 152 | 给定文本:打开相机这 153 | 154 | 你已经知道该文本意图为`lanuch`,请识别里面的{'app_name': 'app或者电器名'}。 155 | 156 | 结果返回为一个字典,例如{text: 原始文本, intent:意图名, slots:按照字典里面格式填充值}。 157 | 158 | chatgpt { 159 | "text": "打开相机这", 160 | "intent": "launch", 161 | "slots": { 162 | "app_name": "相机" 163 | } 164 | } 165 | ``` 166 | 167 | 麻雀虽小,五脏俱全。 168 | 169 | 这只是一个简单的样例,有很多值得改进的地方: 170 | 171 | - 如果不存在上述的意图怎么处理。 172 | - 对于特定的意图,还需要继续问答下去。 173 | - 将公用的部分进一步进行分装。 174 | - 怎么将其整合到langchain里面。 175 | 176 | - 除了上述的利用LLM充当中间角色外,还可以使用embedding来充当。 -------------------------------------------------------------------------------- /中文例子/根据query查询docs.md: -------------------------------------------------------------------------------- 1 | 前面我们已经获得了一些Document: 2 | 3 | ```python 4 | from langchain.text_splitter import CharacterTextSplitter 5 | from langchain.document_loaders import TextLoader 6 | import re 7 | from typing import List 8 | 9 | 10 | 11 | class ChineseTextSplitter(CharacterTextSplitter): 12 | def __init__(self, pdf: bool = False, sentence_size: int = None, **kwargs): 13 | super().__init__(**kwargs) 14 | self.pdf = pdf 15 | self.sentence_size = sentence_size 16 | 17 | def split_text1(self, text: str) -> List[str]: 18 | if self.pdf: 19 | text = re.sub(r"\n{3,}", "\n", text) 20 | text = re.sub('\s', ' ', text) 21 | text = text.replace("\n\n", "") 22 | sent_sep_pattern = re.compile('([﹒﹔﹖﹗.。!?]["’”」』]{0,2}|(?=["‘“「『]{1,2}|$))') # del :; 23 | sent_list = [] 24 | for ele in sent_sep_pattern.split(text): 25 | if sent_sep_pattern.match(ele) and sent_list: 26 | sent_list[-1] += ele 27 | elif ele: 28 | sent_list.append(ele) 29 | return sent_list 30 | 31 | def split_text(self, text: str) -> List[str]: ##此处需要进一步优化逻辑 32 | if self.pdf: 33 | text = re.sub(r"\n{3,}", r"\n", text) 34 | text = re.sub('\s', " ", text) 35 | text = re.sub("\n\n", "", text) 36 | 37 | text = re.sub(r'([;;.!?。!?\?])([^”’])', r"\1\n\2", text) # 单字符断句符 38 | text = re.sub(r'(\.{6})([^"’”」』])', r"\1\n\2", text) # 英文省略号 39 | text = re.sub(r'(\…{2})([^"’”」』])', r"\1\n\2", text) # 中文省略号 40 | text = re.sub(r'([;;!?。!?\?]["’”」』]{0,2})([^;;!?,。!?\?])', r'\1\n\2', text) 41 | # 如果双引号前有终止符,那么双引号才是句子的终点,把分句符\n放到双引号后,注意前面的几句都小心保留了双引号 42 | text = text.rstrip() # 段尾如果有多余的\n就去掉它 43 | # 很多规则中会考虑分号;,但是这里我把它忽略不计,破折号、英文双引号等同样忽略,需要的再做些简单调整即可。 44 | ls = [i for i in text.split("\n") if i] 45 | for ele in ls: 46 | if len(ele) > self.sentence_size: 47 | ele1 = re.sub(r'([,,.]["’”」』]{0,2})([^,,.])', r'\1\n\2', ele) 48 | ele1_ls = ele1.split("\n") 49 | for ele_ele1 in ele1_ls: 50 | if len(ele_ele1) > self.sentence_size: 51 | ele_ele2 = re.sub(r'([\n]{1,}| {2,}["’”」』]{0,2})([^\s])', r'\1\n\2', ele_ele1) 52 | ele2_ls = ele_ele2.split("\n") 53 | for ele_ele2 in ele2_ls: 54 | if len(ele_ele2) > self.sentence_size: 55 | ele_ele3 = re.sub('( ["’”」』]{0,2})([^ ])', r'\1\n\2', ele_ele2) 56 | ele2_id = ele2_ls.index(ele_ele2) 57 | ele2_ls = ele2_ls[:ele2_id] + [i for i in ele_ele3.split("\n") if i] + ele2_ls[ 58 | ele2_id + 1:] 59 | ele_id = ele1_ls.index(ele_ele1) 60 | ele1_ls = ele1_ls[:ele_id] + [i for i in ele2_ls if i] + ele1_ls[ele_id + 1:] 61 | 62 | id = ls.index(ele) 63 | ls = ls[:id] + [i for i in ele1_ls if i] + ls[id + 1:] 64 | return ls 65 | 66 | filepath = "/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt" 67 | sentence_size = 100 68 | loader = TextLoader(filepath, autodetect_encoding=True) 69 | textsplitter = ChineseTextSplitter(pdf=False, sentence_size=sentence_size) 70 | docs = loader.load_and_split(textsplitter) 71 | 72 | from pprint import pprint 73 | 74 | pprint(docs) 75 | 76 | """ 77 | [Document(page_content='鲍勃库西奖归谁属?', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 78 | Document(page_content=' NCAA最强控卫是坎巴还是弗神新浪体育讯如今,', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 79 | Document(page_content='本赛季的NCAA进入到了末段,', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 80 | ... 81 | """ 82 | ``` 83 | 84 | 接下来我们要使用一个query查询出和它相似的一些Doucment: 85 | 86 | ```python 87 | from langchain.vectorstores import FAISS 88 | from langchain.vectorstores.base import VectorStore 89 | from langchain.vectorstores.faiss import dependable_faiss_import 90 | from typing import Any, Callable, List, Dict, Tuple 91 | from langchain.docstore.base import Docstore 92 | from langchain.docstore.document import Document 93 | import numpy as np 94 | import copy 95 | import os 96 | 97 | VECTOR_SEARCH_SCORE_THRESHOLD = float("inf") 98 | CHUNK_SIZE = 256 99 | 100 | class MyFAISS(FAISS, VectorStore): 101 | def __init__( 102 | self, 103 | embedding_function: Callable, 104 | index: Any, 105 | docstore: Docstore, 106 | index_to_docstore_id: Dict[int, str], 107 | normalize_L2: bool = False, 108 | ): 109 | super().__init__(embedding_function=embedding_function, 110 | index=index, 111 | docstore=docstore, 112 | index_to_docstore_id=index_to_docstore_id, 113 | normalize_L2=normalize_L2) 114 | self.score_threshold=VECTOR_SEARCH_SCORE_THRESHOLD 115 | self.chunk_size = CHUNK_SIZE 116 | self.chunk_conent = False 117 | 118 | def seperate_list(self, ls: List[int]) -> List[List[int]]: 119 | # TODO: 增加是否属于同一文档的判断 120 | lists = [] 121 | ls1 = [ls[0]] 122 | for i in range(1, len(ls)): 123 | if ls[i - 1] + 1 == ls[i]: 124 | ls1.append(ls[i]) 125 | else: 126 | lists.append(ls1) 127 | ls1 = [ls[i]] 128 | lists.append(ls1) 129 | return lists 130 | 131 | def similarity_search_with_score_by_vector( 132 | self, embedding: List[float], k: int = 4, 133 | 134 | ) -> List[Document]: 135 | faiss = dependable_faiss_import() 136 | vector = np.array([embedding], dtype=np.float32) 137 | if self._normalize_L2: 138 | faiss.normalize_L2(vector) 139 | 140 | scores, indices = self.index.search(vector, k) 141 | docs = [] 142 | id_set = set() 143 | store_len = len(self.index_to_docstore_id) 144 | rearrange_id_list = False 145 | for j, i in enumerate(indices[0]): 146 | if i == -1 or 0 < self.score_threshold < scores[0][j]: 147 | # This happens when not enough docs are returned. 148 | continue 149 | if i in self.index_to_docstore_id: 150 | _id = self.index_to_docstore_id[i] 151 | # 执行接下来的操作 152 | else: 153 | continue 154 | doc = self.docstore.search(_id) 155 | if (not self.chunk_conent) or ("context_expand" in doc.metadata and not doc.metadata["context_expand"]): 156 | # 匹配出的文本如果不需要扩展上下文则执行如下代码 157 | if not isinstance(doc, Document): 158 | raise ValueError(f"Could not find document for id {_id}, got {doc}") 159 | doc.metadata["score"] = int(scores[0][j]) 160 | docs.append(doc) 161 | continue 162 | 163 | id_set.add(i) 164 | docs_len = len(doc.page_content) 165 | for k in range(1, max(i, store_len - i)): 166 | break_flag = False 167 | if "context_expand_method" in doc.metadata and doc.metadata["context_expand_method"] == "forward": 168 | expand_range = [i + k] 169 | elif "context_expand_method" in doc.metadata and doc.metadata["context_expand_method"] == "backward": 170 | expand_range = [i - k] 171 | else: 172 | expand_range = [i + k, i - k] 173 | for l in expand_range: 174 | if l not in id_set and 0 <= l < len(self.index_to_docstore_id): 175 | _id0 = self.index_to_docstore_id[l] 176 | doc0 = self.docstore.search(_id0) 177 | if docs_len + len(doc0.page_content) > self.chunk_size or doc0.metadata["source"] != \ 178 | doc.metadata["source"]: 179 | break_flag = True 180 | break 181 | elif doc0.metadata["source"] == doc.metadata["source"]: 182 | docs_len += len(doc0.page_content) 183 | id_set.add(l) 184 | rearrange_id_list = True 185 | if break_flag: 186 | break 187 | if (not self.chunk_conent) or (not rearrange_id_list): 188 | return docs 189 | if len(id_set) == 0 and self.score_threshold > 0: 190 | return [] 191 | id_list = sorted(list(id_set)) 192 | id_lists = self.seperate_list(id_list) 193 | for id_seq in id_lists: 194 | for id in id_seq: 195 | if id == id_seq[0]: 196 | _id = self.index_to_docstore_id[id] 197 | # doc = self.docstore.search(_id) 198 | doc = copy.deepcopy(self.docstore.search(_id)) 199 | else: 200 | _id0 = self.index_to_docstore_id[id] 201 | doc0 = self.docstore.search(_id0) 202 | doc.page_content += " " + doc0.page_content 203 | if not isinstance(doc, Document): 204 | raise ValueError(f"Could not find document for id {_id}, got {doc}") 205 | doc_score = min([scores[0][id] for id in [indices[0].tolist().index(i) for i in id_seq if i in indices[0]]]) 206 | doc.metadata["score"] = int(doc_score) 207 | docs.append(doc) 208 | print(docs0) 209 | return docs 210 | 211 | def similarity_search_with_score( 212 | self, query: str, k: int = 4 213 | ) -> List[Tuple[Document, float]]: 214 | """Return docs most similar to query. 215 | 216 | Args: 217 | query: Text to look up documents similar to. 218 | k: Number of Documents to return. Defaults to 4. 219 | 220 | Returns: 221 | List of Documents most similar to the query and score for each 222 | """ 223 | embedding = self.embedding_function(query) 224 | scores, indices = self.index.search(np.array([embedding], dtype=np.float32), k) 225 | docs = [] 226 | print(scores, indices) 227 | for j, i in enumerate(indices[0]): 228 | if i == -1: 229 | # This happens when not enough docs are returned. 230 | continue 231 | _id = self.index_to_docstore_id[i] 232 | doc = self.docstore.search(_id) 233 | if not isinstance(doc, Document): 234 | raise ValueError(f"Could not find document for id {_id}, got {doc}") 235 | docs.append((doc, scores[0][j])) 236 | return docs 237 | 238 | def delete_doc(self, source: str or List[str]): 239 | try: 240 | if isinstance(source, str): 241 | ids = [k for k, v in self.docstore._dict.items() if v.metadata["source"] == source] 242 | vs_path = os.path.join(os.path.split(os.path.split(source)[0])[0], "vector_store") 243 | else: 244 | ids = [k for k, v in self.docstore._dict.items() if v.metadata["source"] in source] 245 | vs_path = os.path.join(os.path.split(os.path.split(source[0])[0])[0], "vector_store") 246 | if len(ids) == 0: 247 | return f"docs delete fail" 248 | else: 249 | for id in ids: 250 | index = list(self.index_to_docstore_id.keys())[list(self.index_to_docstore_id.values()).index(id)] 251 | self.index_to_docstore_id.pop(index) 252 | self.docstore._dict.pop(id) 253 | # TODO: 从 self.index 中删除对应id 254 | # self.index.reset() 255 | self.save_local(vs_path) 256 | return f"docs delete success" 257 | except Exception as e: 258 | print(e) 259 | return f"docs delete fail" 260 | 261 | def update_doc(self, source, new_docs): 262 | try: 263 | delete_len = self.delete_doc(source) 264 | ls = self.add_documents(new_docs) 265 | return f"docs update success" 266 | except Exception as e: 267 | print(e) 268 | return f"docs update fail" 269 | 270 | def list_docs(self): 271 | return list(set(v.metadata["source"] for v in self.docstore._dict.values())) 272 | 273 | 274 | ``` 275 | 276 | 这里我们实际上只用到了similarity_search_with_score。 277 | 278 | ```python 279 | # 将文档存储为向量 280 | vs_path = "./" 281 | from langchain.embeddings.huggingface import HuggingFaceEmbeddings 282 | embeddings = HuggingFaceEmbeddings(model_name="GanymedeNil/text2vec-large-chinese") 283 | vector_store = MyFAISS.from_documents(docs, embeddings) 284 | vector_store.save_local(vs_path) 285 | 286 | def load_vector_store(vs_path, embeddings): 287 | return MyFAISS.load_local(vs_path, embeddings) 288 | vector_sore = load_vector_store(vs_path, embeddings) 289 | 290 | top_k = 10 291 | query = "我们将有一个光明的未来。" 292 | related_docs_with_score = vector_store.similarity_search_with_score(query, k=top_k) 293 | print(related_docs_with_score) 294 | 295 | “”“ 296 | (Document(page_content='我很确信我们将有一个光明的未来。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 183.51764) 297 | (Document(page_content='虽然这听起来有些疯狂,但也许有一天我能成为这支球队的领袖,并成为这个团队的主要领导人之一,我认为这能大大提升球队的实力。”', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 652.9918) 298 | (Document(page_content='如果我们再能拥有一两块拼图,我们会成为一支强队的。”', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 670.3276) 299 | (Document(page_content='我想我们的球员已经显示出了他们的能力,只要适当的补强,他们就有能力赢得总冠军。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 681.73505) 300 | (Document(page_content='人们由衷期待中国足球的浴火重生、否极泰来。', metadata={'source': '/content/drive/MyDrive/lm_pretrained/data/test_corpus.txt'}), 686.49316) 301 | “”“ 302 | ``` 303 | 304 | -------------------------------------------------------------------------------- /文章/langchain-chatglm/关于InMemoryDocstore.md: -------------------------------------------------------------------------------- 1 | 在将文本转换为向量的时候使用到了InMemoryDocstore,具体在langchain的vectorstores下的faiss.py 2 | 3 | ```python 4 | @classmethod 5 | def from_texts( 6 | cls, 7 | texts: List[str], 8 | embedding: Embeddings, 9 | metadatas: Optional[List[dict]] = None, 10 | **kwargs: Any, 11 | ) -> FAISS: 12 | """Construct FAISS wrapper from raw documents. 13 | 14 | This is a user friendly interface that: 15 | 1. Embeds documents. 16 | 2. Creates an in memory docstore 17 | 3. Initializes the FAISS database 18 | 19 | This is intended to be a quick way to get started. 20 | 21 | Example: 22 | .. code-block:: python 23 | 24 | from langchain import FAISS 25 | from langchain.embeddings import OpenAIEmbeddings 26 | embeddings = OpenAIEmbeddings() 27 | faiss = FAISS.from_texts(texts, embeddings) 28 | """ 29 | faiss = dependable_faiss_import() 30 | embeddings = embedding.embed_documents(texts) 31 | index = faiss.IndexFlatL2(len(embeddings[0])) 32 | index.add(np.array(embeddings, dtype=np.float32)) 33 | documents = [] 34 | for i, text in enumerate(texts): 35 | metadata = metadatas[i] if metadatas else {} 36 | documents.append(Document(page_content=text, metadata=metadata)) 37 | index_to_id = {i: str(uuid.uuid4()) for i in range(len(documents))} 38 | docstore = InMemoryDocstore( 39 | {index_to_id[i]: doc for i, doc in enumerate(documents)} 40 | ) 41 | return cls(embedding.embed_query, index, docstore, index_to_id) 42 | ``` 43 | 44 | InMemoryDocstore位于langchain的docstore下的in_memory.py里面: 45 | 46 | ```python 47 | """Simple in memory docstore in the form of a dict.""" 48 | from typing import Dict, Union 49 | 50 | from langchain.docstore.base import AddableMixin, Docstore 51 | from langchain.docstore.document import Document 52 | 53 | 54 | class InMemoryDocstore(Docstore, AddableMixin): 55 | """Simple in memory docstore in the form of a dict.""" 56 | 57 | def __init__(self, _dict: Dict[str, Document]): 58 | """Initialize with dict.""" 59 | self._dict = _dict 60 | 61 | def add(self, texts: Dict[str, Document]) -> None: 62 | """Add texts to in memory dictionary.""" 63 | overlapping = set(texts).intersection(self._dict) 64 | if overlapping: 65 | raise ValueError(f"Tried to add ids that already exist: {overlapping}") 66 | self._dict = dict(self._dict, **texts) 67 | 68 | def search(self, search: str) -> Union[str, Document]: 69 | """Search via direct lookup.""" 70 | if search not in self._dict: 71 | return f"ID {search} not found." 72 | else: 73 | return self._dict[search] 74 | ``` 75 | 76 | 其继承了Docstore, AddableMixin,看看 77 | 78 | ```python 79 | """Interface to access to place that stores documents.""" 80 | from abc import ABC, abstractmethod 81 | from typing import Dict, Union 82 | 83 | from langchain.docstore.document import Document 84 | 85 | 86 | class Docstore(ABC): 87 | """Interface to access to place that stores documents.""" 88 | 89 | @abstractmethod 90 | def search(self, search: str) -> Union[str, Document]: 91 | """Search for document. 92 | 93 | If page exists, return the page summary, and a Document object. 94 | If page does not exist, return similar entries. 95 | """ 96 | 97 | 98 | class AddableMixin(ABC): 99 | """Mixin class that supports adding texts.""" 100 | 101 | @abstractmethod 102 | def add(self, texts: Dict[str, Document]) -> None: 103 | """Add more documents.""" 104 | ``` 105 | 106 | InMemoryDocstore要实现两个抽象方法。 107 | 108 | 初始化的时候要传入一个字典,按照之前代码,传入的就是`{向量到id的索引:langchain中的Document}` 109 | 110 | add方法用来合并两个字典。 111 | 112 | search方法用于从_dict中查找指定索引的Document。 -------------------------------------------------------------------------------- /文章/langchain-chatglm/关于TextLoader.md: -------------------------------------------------------------------------------- 1 | 具体使用在chains/local_doc_qa.py下, 2 | 3 | ```python 4 | loader = TextLoader(filepath, autodetect_encoding=True) 5 | textsplitter = ChineseTextSplitter(pdf=False, sentence_size=sentence_size) 6 | docs = loader.load_and_split(textsplitter) 7 | ``` 8 | 9 | 其是langchain下document_loaders目录下里的text.py,TextLoader继承了BaseLoader类,先看看它: 10 | 11 | ```python 12 | """Abstract interface for document loader implementations.""" 13 | from abc import ABC, abstractmethod 14 | from typing import Iterator, List, Optional 15 | 16 | from langchain.document_loaders.blob_loaders import Blob 17 | from langchain.schema import Document 18 | from langchain.text_splitter import RecursiveCharacterTextSplitter, TextSplitter 19 | 20 | 21 | class BaseLoader(ABC): 22 | """Interface for loading Documents. 23 | 24 | Implementations should implement the lazy-loading method using generators 25 | to avoid loading all Documents into memory at once. 26 | 27 | The `load` method will remain as is for backwards compatibility, but its 28 | implementation should be just `list(self.lazy_load())`. 29 | """ 30 | 31 | # Sub-classes should implement this method 32 | # as return list(self.lazy_load()). 33 | # This method returns a List which is materialized in memory. 34 | @abstractmethod 35 | def load(self) -> List[Document]: 36 | """Load data into Document objects.""" 37 | 38 | def load_and_split( 39 | self, text_splitter: Optional[TextSplitter] = None 40 | ) -> List[Document]: 41 | """Load Documents and split into chunks. Chunks are returned as Documents. 42 | 43 | Args: 44 | text_splitter: TextSplitter instance to use for splitting documents. 45 | Defaults to RecursiveCharacterTextSplitter. 46 | 47 | Returns: 48 | List of Documents. 49 | """ 50 | if text_splitter is None: 51 | _text_splitter: TextSplitter = RecursiveCharacterTextSplitter() 52 | else: 53 | _text_splitter = text_splitter 54 | docs = self.load() 55 | return _text_splitter.split_documents(docs) 56 | 57 | # Attention: This method will be upgraded into an abstractmethod once it's 58 | # implemented in all the existing subclasses. 59 | def lazy_load( 60 | self, 61 | ) -> Iterator[Document]: 62 | """A lazy loader for Documents.""" 63 | raise NotImplementedError( 64 | f"{self.__class__.__name__} does not implement lazy_load()" 65 | ) 66 | 67 | ``` 68 | 69 | 需要实现一个抽象方法load()。至于load_and_split,需要传入一个TextSplitter,最后调用: 70 | 71 | ```python 72 | docs = self.load() 73 | return _text_splitter.split_documents(docs) 74 | ``` 75 | 76 | 再来看看TextLoader: 77 | 78 | ```python 79 | import logging 80 | from typing import List, Optional 81 | 82 | from langchain.docstore.document import Document 83 | from langchain.document_loaders.base import BaseLoader 84 | from langchain.document_loaders.helpers import detect_file_encodings 85 | 86 | logger = logging.getLogger(__name__) 87 | 88 | 89 | class TextLoader(BaseLoader): 90 | """Load text files. 91 | 92 | 93 | Args: 94 | file_path: Path to the file to load. 95 | 96 | encoding: File encoding to use. If `None`, the file will be loaded 97 | with the default system encoding. 98 | 99 | autodetect_encoding: Whether to try to autodetect the file encoding 100 | if the specified encoding fails. 101 | """ 102 | 103 | def __init__( 104 | self, 105 | file_path: str, 106 | encoding: Optional[str] = None, 107 | autodetect_encoding: bool = False, 108 | ): 109 | """Initialize with file path.""" 110 | self.file_path = file_path 111 | self.encoding = encoding 112 | self.autodetect_encoding = autodetect_encoding 113 | 114 | def load(self) -> List[Document]: 115 | """Load from file path.""" 116 | text = "" 117 | try: 118 | with open(self.file_path, encoding=self.encoding) as f: 119 | text = f.read() 120 | except UnicodeDecodeError as e: 121 | if self.autodetect_encoding: 122 | detected_encodings = detect_file_encodings(self.file_path) 123 | for encoding in detected_encodings: 124 | logger.debug("Trying encoding: ", encoding.encoding) 125 | try: 126 | with open(self.file_path, encoding=encoding.encoding) as f: 127 | text = f.read() 128 | break 129 | except UnicodeDecodeError: 130 | continue 131 | else: 132 | raise RuntimeError(f"Error loading {self.file_path}") from e 133 | except Exception as e: 134 | raise RuntimeError(f"Error loading {self.file_path}") from e 135 | 136 | metadata = {"source": self.file_path} 137 | return [Document(page_content=text, metadata=metadata)] 138 | ``` 139 | 140 | 打开一个txt文件,然后返回一个列表,列表里面为Document类,包含page_content也就是文本和metadata也就是元数据,表明该文本的来源txt。 141 | 142 | 接下来我们看看chineseTextSplitter.split_documents(docs)。 143 | 144 | ChineseTextSplitter是langchain-chatglm自行实现的,位于textsplitter/chinese_text_splitter.py,它里面没有split_documents方法,而是来源其父类,其继承了CharacterTextSplitter,from langchain.text_splitter import CharacterTextSplitter。CharacterTextSplitter继承TextSplitter,看看TextSplitter: 145 | 146 | ```python 147 | class TextSplitter(ABC): 148 | """Interface for splitting text into chunks.""" 149 | 150 | def __init__( 151 | self, 152 | chunk_size: int = 4000, 153 | chunk_overlap: int = 200, 154 | length_function: Callable[[str], int] = len, 155 | ): 156 | """Create a new TextSplitter.""" 157 | if chunk_overlap > chunk_size: 158 | raise ValueError( 159 | f"Got a larger chunk overlap ({chunk_overlap}) than chunk size " 160 | f"({chunk_size}), should be smaller." 161 | ) 162 | self._chunk_size = chunk_size 163 | self._chunk_overlap = chunk_overlap 164 | self._length_function = length_function 165 | 166 | @abstractmethod 167 | def split_text(self, text: str) -> List[str]: 168 | """Split text into multiple components.""" 169 | 170 | def create_documents( 171 | self, texts: List[str], metadatas: Optional[List[dict]] = None 172 | ) -> List[Document]: 173 | """Create documents from a list of texts.""" 174 | _metadatas = metadatas or [{}] * len(texts) 175 | documents = [] 176 | for i, text in enumerate(texts): 177 | for chunk in self.split_text(text): 178 | documents.append(Document(page_content=chunk, metadata=_metadatas[i])) 179 | return documents 180 | 181 | def split_documents(self, documents: List[Document]) -> List[Document]: 182 | """Split documents.""" 183 | texts = [doc.page_content for doc in documents] 184 | metadatas = [doc.metadata for doc in documents] 185 | return self.create_documents(texts, metadatas) 186 | 187 | def _join_docs(self, docs: List[str], separator: str) -> Optional[str]: 188 | text = separator.join(docs) 189 | text = text.strip() 190 | if text == "": 191 | return None 192 | else: 193 | return text 194 | 195 | def _merge_splits(self, splits: Iterable[str], separator: str) -> List[str]: 196 | # We now want to combine these smaller pieces into medium size 197 | # chunks to send to the LLM. 198 | docs = [] 199 | current_doc: List[str] = [] 200 | total = 0 201 | for d in splits: 202 | _len = self._length_function(d) 203 | if total + _len >= self._chunk_size: 204 | if total > self._chunk_size: 205 | logger.warning( 206 | f"Created a chunk of size {total}, " 207 | f"which is longer than the specified {self._chunk_size}" 208 | ) 209 | if len(current_doc) > 0: 210 | doc = self._join_docs(current_doc, separator) 211 | if doc is not None: 212 | docs.append(doc) 213 | # Keep on popping if: 214 | # - we have a larger chunk than in the chunk overlap 215 | # - or if we still have any chunks and the length is long 216 | while total > self._chunk_overlap or ( 217 | total + _len > self._chunk_size and total > 0 218 | ): 219 | total -= self._length_function(current_doc[0]) 220 | current_doc = current_doc[1:] 221 | current_doc.append(d) 222 | total += _len 223 | doc = self._join_docs(current_doc, separator) 224 | if doc is not None: 225 | docs.append(doc) 226 | return docs 227 | 228 | @classmethod 229 | def from_huggingface_tokenizer(cls, tokenizer: Any, **kwargs: Any) -> TextSplitter: 230 | """Text splitter that uses HuggingFace tokenizer to count length.""" 231 | try: 232 | from transformers import PreTrainedTokenizerBase 233 | 234 | if not isinstance(tokenizer, PreTrainedTokenizerBase): 235 | raise ValueError( 236 | "Tokenizer received was not an instance of PreTrainedTokenizerBase" 237 | ) 238 | 239 | def _huggingface_tokenizer_length(text: str) -> int: 240 | return len(tokenizer.encode(text)) 241 | 242 | except ImportError: 243 | raise ValueError( 244 | "Could not import transformers python package. " 245 | "Please it install it with `pip install transformers`." 246 | ) 247 | return cls(length_function=_huggingface_tokenizer_length, **kwargs) 248 | 249 | @classmethod 250 | def from_tiktoken_encoder( 251 | cls, 252 | encoding_name: str = "gpt2", 253 | allowed_special: Union[Literal["all"], AbstractSet[str]] = set(), 254 | disallowed_special: Union[Literal["all"], Collection[str]] = "all", 255 | **kwargs: Any, 256 | ) -> TextSplitter: 257 | """Text splitter that uses tiktoken encoder to count length.""" 258 | try: 259 | import tiktoken 260 | except ImportError: 261 | raise ValueError( 262 | "Could not import tiktoken python package. " 263 | "This is needed in order to calculate max_tokens_for_prompt. " 264 | "Please it install it with `pip install tiktoken`." 265 | ) 266 | 267 | # create a GPT-3 encoder instance 268 | enc = tiktoken.get_encoding(encoding_name) 269 | 270 | def _tiktoken_encoder(text: str, **kwargs: Any) -> int: 271 | return len( 272 | enc.encode( 273 | text, 274 | allowed_special=allowed_special, 275 | disallowed_special=disallowed_special, 276 | **kwargs, 277 | ) 278 | ) 279 | 280 | return cls(length_function=_tiktoken_encoder, **kwargs) 281 | ``` 282 | 283 | 主要看看这三个: 284 | 285 | ```python 286 | @abstractmethod 287 | def split_text(self, text: str) -> List[str]: 288 | """Split text into multiple components.""" 289 | 290 | def create_documents( 291 | self, texts: List[str], metadatas: Optional[List[dict]] = None 292 | ) -> List[Document]: 293 | """Create documents from a list of texts.""" 294 | _metadatas = metadatas or [{}] * len(texts) 295 | documents = [] 296 | for i, text in enumerate(texts): 297 | for chunk in self.split_text(text): 298 | documents.append(Document(page_content=chunk, metadata=_metadatas[i])) 299 | return documents 300 | 301 | def split_documents(self, documents: List[Document]) -> List[Document]: 302 | """Split documents.""" 303 | texts = [doc.page_content for doc in documents] 304 | metadatas = [doc.metadata for doc in documents] 305 | return self.create_documents(texts, metadatas) 306 | ``` 307 | 308 | 一目了然,然后是split_text抽象方法,在ChineseTextSplitter里面实现的: 309 | 310 | ```python 311 | def split_text(self, text: str) -> List[str]: ##此处需要进一步优化逻辑 312 | if self.pdf: 313 | text = re.sub(r"\n{3,}", r"\n", text) 314 | text = re.sub('\s', " ", text) 315 | text = re.sub("\n\n", "", text) 316 | 317 | text = re.sub(r'([;;.!?。!?\?])([^”’])', r"\1\n\2", text) # 单字符断句符 318 | text = re.sub(r'(\.{6})([^"’”」』])', r"\1\n\2", text) # 英文省略号 319 | text = re.sub(r'(\…{2})([^"’”」』])', r"\1\n\2", text) # 中文省略号 320 | text = re.sub(r'([;;!?。!?\?]["’”」』]{0,2})([^;;!?,。!?\?])', r'\1\n\2', text) 321 | # 如果双引号前有终止符,那么双引号才是句子的终点,把分句符\n放到双引号后,注意前面的几句都小心保留了双引号 322 | text = text.rstrip() # 段尾如果有多余的\n就去掉它 323 | # 很多规则中会考虑分号;,但是这里我把它忽略不计,破折号、英文双引号等同样忽略,需要的再做些简单调整即可。 324 | ls = [i for i in text.split("\n") if i] 325 | for ele in ls: 326 | if len(ele) > self.sentence_size: 327 | ele1 = re.sub(r'([,,.]["’”」』]{0,2})([^,,.])', r'\1\n\2', ele) 328 | ele1_ls = ele1.split("\n") 329 | for ele_ele1 in ele1_ls: 330 | if len(ele_ele1) > self.sentence_size: 331 | ele_ele2 = re.sub(r'([\n]{1,}| {2,}["’”」』]{0,2})([^\s])', r'\1\n\2', ele_ele1) 332 | ele2_ls = ele_ele2.split("\n") 333 | for ele_ele2 in ele2_ls: 334 | if len(ele_ele2) > self.sentence_size: 335 | ele_ele3 = re.sub('( ["’”」』]{0,2})([^ ])', r'\1\n\2', ele_ele2) 336 | ele2_id = ele2_ls.index(ele_ele2) 337 | ele2_ls = ele2_ls[:ele2_id] + [i for i in ele_ele3.split("\n") if i] + ele2_ls[ 338 | ele2_id + 1:] 339 | ele_id = ele1_ls.index(ele_ele1) 340 | ele1_ls = ele1_ls[:ele_id] + [i for i in ele2_ls if i] + ele1_ls[ele_id + 1:] 341 | 342 | id = ls.index(ele) 343 | ls = ls[:id] + [i for i in ele1_ls if i] + ls[id + 1:] 344 | return ls 345 | ``` 346 | 347 | 按照一些规则对中文文本进行分割。 348 | 349 | 最后总结下: 350 | 351 | load()就是将整个文本转换为[Doument]。 352 | 353 | load_and_split()就是将文本进行分割后返回多个Document构成的列表。 354 | 355 | 至于ChineseTextSplitter之前我们已经讲解过了。 -------------------------------------------------------------------------------- /文章/langchain-chatglm/关于怎么调用bing搜索接口.md: -------------------------------------------------------------------------------- 1 | 在agent/bing_search.py下: 2 | 3 | ```python 4 | #coding=utf8 5 | 6 | from langchain.utilities import BingSearchAPIWrapper 7 | from configs.model_config import BING_SEARCH_URL, BING_SUBSCRIPTION_KEY 8 | 9 | 10 | def bing_search(text, result_len=3): 11 | if not (BING_SEARCH_URL and BING_SUBSCRIPTION_KEY): 12 | return [{"snippet": "please set BING_SUBSCRIPTION_KEY and BING_SEARCH_URL in os ENV", 13 | "title": "env info is not found", 14 | "link": "https://python.langchain.com/en/latest/modules/agents/tools/examples/bing_search.html"}] 15 | search = BingSearchAPIWrapper(bing_subscription_key=BING_SUBSCRIPTION_KEY, 16 | bing_search_url=BING_SEARCH_URL) 17 | return search.results(text, result_len) 18 | 19 | 20 | if __name__ == "__main__": 21 | r = bing_search('python') 22 | print(r) 23 | ``` 24 | 25 | BING_SUBSCRIPTION_KEY需要我们去申请,看看 26 | 27 | langchain/utilities/bing_search.py 28 | 29 | ```python 30 | """Util that calls Bing Search. 31 | 32 | In order to set this up, follow instructions at: 33 | https://levelup.gitconnected.com/api-tutorial-how-to-use-bing-web-search-api-in-python-4165d5592a7e 34 | """ 35 | from typing import Dict, List 36 | 37 | import requests 38 | from pydantic import BaseModel, Extra, root_validator 39 | 40 | from langchain.utils import get_from_dict_or_env 41 | 42 | 43 | class BingSearchAPIWrapper(BaseModel): 44 | """Wrapper for Bing Search API. 45 | 46 | In order to set this up, follow instructions at: 47 | https://levelup.gitconnected.com/api-tutorial-how-to-use-bing-web-search-api-in-python-4165d5592a7e 48 | """ 49 | 50 | bing_subscription_key: str 51 | bing_search_url: str 52 | k: int = 10 53 | 54 | class Config: 55 | """Configuration for this pydantic object.""" 56 | 57 | extra = Extra.forbid 58 | 59 | def _bing_search_results(self, search_term: str, count: int) -> List[dict]: 60 | headers = {"Ocp-Apim-Subscription-Key": self.bing_subscription_key} 61 | params = { 62 | "q": search_term, 63 | "count": count, 64 | "textDecorations": True, 65 | "textFormat": "HTML", 66 | } 67 | response = requests.get( 68 | self.bing_search_url, headers=headers, params=params # type: ignore 69 | ) 70 | response.raise_for_status() 71 | search_results = response.json() 72 | return search_results["webPages"]["value"] 73 | 74 | @root_validator(pre=True) 75 | def validate_environment(cls, values: Dict) -> Dict: 76 | """Validate that api key and endpoint exists in environment.""" 77 | bing_subscription_key = get_from_dict_or_env( 78 | values, "bing_subscription_key", "BING_SUBSCRIPTION_KEY" 79 | ) 80 | values["bing_subscription_key"] = bing_subscription_key 81 | 82 | bing_search_url = get_from_dict_or_env( 83 | values, 84 | "bing_search_url", 85 | "BING_SEARCH_URL", 86 | # default="https://api.bing.microsoft.com/v7.0/search", 87 | ) 88 | 89 | values["bing_search_url"] = bing_search_url 90 | 91 | return values 92 | 93 | def run(self, query: str) -> str: 94 | """Run query through BingSearch and parse result.""" 95 | snippets = [] 96 | results = self._bing_search_results(query, count=self.k) 97 | if len(results) == 0: 98 | return "No good Bing Search Result was found" 99 | for result in results: 100 | snippets.append(result["snippet"]) 101 | 102 | return " ".join(snippets) 103 | 104 | def results(self, query: str, num_results: int) -> List[Dict]: 105 | """Run query through BingSearch and return metadata. 106 | 107 | Args: 108 | query: The query to search for. 109 | num_results: The number of results to return. 110 | 111 | Returns: 112 | A list of dictionaries with the following keys: 113 | snippet - The description of the result. 114 | title - The title of the result. 115 | link - The link to the result. 116 | """ 117 | metadata_results = [] 118 | results = self._bing_search_results(query, count=num_results) 119 | if len(results) == 0: 120 | return [{"Result": "No good Bing Search Result was found"}] 121 | for result in results: 122 | metadata_result = { 123 | "snippet": result["snippet"], 124 | "title": result["name"], 125 | "link": result["url"], 126 | } 127 | metadata_results.append(metadata_result) 128 | 129 | return metadata_results 130 | ``` 131 | 132 | 实际上就是使用的bing的api接口,需要注意的是返回值是什么。 133 | -------------------------------------------------------------------------------- /文章/langchain-chatglm/根据query得到相关的doc的原理.md: -------------------------------------------------------------------------------- 1 | langchain-chglm中一个核心的步骤就是根据query获得和其相关的文本。我们一步步来看: 2 | 3 | 基本使用: 4 | 5 | ```python 6 | related_docs_with_score = vector_store.similarity_search_with_score(query, k=self.top_k) 7 | ``` 8 | 9 | 在vectostores下的MyFAISS.py 10 | 11 | ```python 12 | from langchain.vectorstores import FAISS 13 | from langchain.vectorstores.base import VectorStore 14 | from langchain.vectorstores.faiss import dependable_faiss_import 15 | from typing import Any, Callable, List, Dict 16 | from langchain.docstore.base import Docstore 17 | from langchain.docstore.document import Document 18 | import numpy as np 19 | import copy 20 | import os 21 | from configs.model_config import * 22 | 23 | 24 | class MyFAISS(FAISS, VectorStore): 25 | def __init__( 26 | self, 27 | embedding_function: Callable, 28 | index: Any, 29 | docstore: Docstore, 30 | index_to_docstore_id: Dict[int, str], 31 | normalize_L2: bool = False, 32 | ): 33 | super().__init__(embedding_function=embedding_function, 34 | index=index, 35 | docstore=docstore, 36 | index_to_docstore_id=index_to_docstore_id, 37 | normalize_L2=normalize_L2) 38 | self.score_threshold=VECTOR_SEARCH_SCORE_THRESHOLD 39 | self.chunk_size = CHUNK_SIZE 40 | self.chunk_conent = False 41 | 42 | def seperate_list(self, ls: List[int]) -> List[List[int]]: 43 | # TODO: 增加是否属于同一文档的判断 44 | lists = [] 45 | ls1 = [ls[0]] 46 | for i in range(1, len(ls)): 47 | if ls[i - 1] + 1 == ls[i]: 48 | ls1.append(ls[i]) 49 | else: 50 | lists.append(ls1) 51 | ls1 = [ls[i]] 52 | lists.append(ls1) 53 | return lists 54 | 55 | def similarity_search_with_score_by_vector( 56 | self, embedding: List[float], k: int = 4 57 | ) -> List[Document]: 58 | faiss = dependable_faiss_import() 59 | vector = np.array([embedding], dtype=np.float32) 60 | if self._normalize_L2: 61 | faiss.normalize_L2(vector) 62 | scores, indices = self.index.search(vector, k) 63 | docs = [] 64 | id_set = set() 65 | store_len = len(self.index_to_docstore_id) 66 | rearrange_id_list = False 67 | for j, i in enumerate(indices[0]): 68 | if i == -1 or 0 < self.score_threshold < scores[0][j]: 69 | # This happens when not enough docs are returned. 70 | continue 71 | if i in self.index_to_docstore_id: 72 | _id = self.index_to_docstore_id[i] 73 | # 执行接下来的操作 74 | else: 75 | continue 76 | doc = self.docstore.search(_id) 77 | if (not self.chunk_conent) or ("context_expand" in doc.metadata and not doc.metadata["context_expand"]): 78 | # 匹配出的文本如果不需要扩展上下文则执行如下代码 79 | if not isinstance(doc, Document): 80 | raise ValueError(f"Could not find document for id {_id}, got {doc}") 81 | doc.metadata["score"] = int(scores[0][j]) 82 | docs.append(doc) 83 | continue 84 | 85 | id_set.add(i) 86 | docs_len = len(doc.page_content) 87 | for k in range(1, max(i, store_len - i)): 88 | break_flag = False 89 | if "context_expand_method" in doc.metadata and doc.metadata["context_expand_method"] == "forward": 90 | expand_range = [i + k] 91 | elif "context_expand_method" in doc.metadata and doc.metadata["context_expand_method"] == "backward": 92 | expand_range = [i - k] 93 | else: 94 | expand_range = [i + k, i - k] 95 | for l in expand_range: 96 | if l not in id_set and 0 <= l < len(self.index_to_docstore_id): 97 | _id0 = self.index_to_docstore_id[l] 98 | doc0 = self.docstore.search(_id0) 99 | if docs_len + len(doc0.page_content) > self.chunk_size or doc0.metadata["source"] != \ 100 | doc.metadata["source"]: 101 | break_flag = True 102 | break 103 | elif doc0.metadata["source"] == doc.metadata["source"]: 104 | docs_len += len(doc0.page_content) 105 | id_set.add(l) 106 | rearrange_id_list = True 107 | if break_flag: 108 | break 109 | if (not self.chunk_conent) or (not rearrange_id_list): 110 | return docs 111 | if len(id_set) == 0 and self.score_threshold > 0: 112 | return [] 113 | id_list = sorted(list(id_set)) 114 | id_lists = self.seperate_list(id_list) 115 | for id_seq in id_lists: 116 | for id in id_seq: 117 | if id == id_seq[0]: 118 | _id = self.index_to_docstore_id[id] 119 | # doc = self.docstore.search(_id) 120 | doc = copy.deepcopy(self.docstore.search(_id)) 121 | else: 122 | _id0 = self.index_to_docstore_id[id] 123 | doc0 = self.docstore.search(_id0) 124 | doc.page_content += " " + doc0.page_content 125 | if not isinstance(doc, Document): 126 | raise ValueError(f"Could not find document for id {_id}, got {doc}") 127 | doc_score = min([scores[0][id] for id in [indices[0].tolist().index(i) for i in id_seq if i in indices[0]]]) 128 | doc.metadata["score"] = int(doc_score) 129 | docs.append(doc) 130 | return docs 131 | 132 | def delete_doc(self, source: str or List[str]): 133 | try: 134 | if isinstance(source, str): 135 | ids = [k for k, v in self.docstore._dict.items() if v.metadata["source"] == source] 136 | vs_path = os.path.join(os.path.split(os.path.split(source)[0])[0], "vector_store") 137 | else: 138 | ids = [k for k, v in self.docstore._dict.items() if v.metadata["source"] in source] 139 | vs_path = os.path.join(os.path.split(os.path.split(source[0])[0])[0], "vector_store") 140 | if len(ids) == 0: 141 | return f"docs delete fail" 142 | else: 143 | for id in ids: 144 | index = list(self.index_to_docstore_id.keys())[list(self.index_to_docstore_id.values()).index(id)] 145 | self.index_to_docstore_id.pop(index) 146 | self.docstore._dict.pop(id) 147 | # TODO: 从 self.index 中删除对应id 148 | # self.index.reset() 149 | self.save_local(vs_path) 150 | return f"docs delete success" 151 | except Exception as e: 152 | print(e) 153 | return f"docs delete fail" 154 | 155 | def update_doc(self, source, new_docs): 156 | try: 157 | delete_len = self.delete_doc(source) 158 | ls = self.add_documents(new_docs) 159 | return f"docs update success" 160 | except Exception as e: 161 | print(e) 162 | return f"docs update fail" 163 | 164 | def list_docs(self): 165 | return list(set(v.metadata["source"] for v in self.docstore._dict.values())) 166 | ``` 167 | 168 | 其继承VectorStore,主要使用VectorStore的from_document来获取faiss格式的索引,from_document里面调用了from_texts,from_texts是一个抽象方法,需要被子类实现。 169 | 170 | 其继承FAISS,FAISS同样也继承了VectorStore,其实现了VectorStore的抽象方法from_texts。 171 | 172 | from_texts里面的核心就是将文档转换为向量,以及相关的一些字典。最后返回一个FAISS对象。 173 | 174 | **注意:在python中继承了多个类,是按照从左往右的顺序进行的。** 175 | 176 | **similarity_search_with_score_by_vector在local_doc_qa.py里面被注释掉了...** 177 | 178 | 本来是想好好梳理下similarity_search_with_score_by_vector里面的逻辑的。 179 | 180 | 使用的时候是调用MyFAISS对象的similarity_search_with_score方法,但MyFAISS没有实现,实际上调用的是faiss.py里面的: 181 | 182 | ```python 183 | def similarity_search_with_score( 184 | self, query: str, k: int = 4 185 | ) -> List[Tuple[Document, float]]: 186 | """Return docs most similar to query. 187 | 188 | Args: 189 | query: Text to look up documents similar to. 190 | k: Number of Documents to return. Defaults to 4. 191 | 192 | Returns: 193 | List of Documents most similar to the query and score for each 194 | """ 195 | embedding = self.embedding_function(query) 196 | scores, indices = self.index.search(np.array([embedding], dtype=np.float32), k) 197 | docs = [] 198 | for j, i in enumerate(indices[0]): 199 | if i == -1: 200 | # This happens when not enough docs are returned. 201 | continue 202 | _id = self.index_to_docstore_id[i] 203 | doc = self.docstore.search(_id) 204 | if not isinstance(doc, Document): 205 | raise ValueError(f"Could not find document for id {_id}, got {doc}") 206 | docs.append((doc, scores[0][j])) 207 | return docs 208 | ``` 209 | 210 | self.index.search调用的是faiss的匹配查询的方法,返回的第一个值表示分数,第二个表示索引的id。可以根据index_to_docstore_id找到其在docstore中对应的索引,然后再根据self.docstore.search(_id)找到实际的doc,最后返回即可。self.embedding_function表示的是转化文本到向量方法、, 211 | 212 | -------------------------------------------------------------------------------- /文章/langchain-chatglm/根据查询出的docs和query生成prompt.md: -------------------------------------------------------------------------------- 1 | 在chains下的loca_doc_qa.py 2 | 3 | ```python 4 | related_docs_with_score = vector_store.similarity_search_with_score(query, k=self.top_k) 5 | torch_gc() 6 | 7 | if len(related_docs_with_score) > 0: 8 | prompt = generate_prompt(related_docs_with_score, query) 9 | else: 10 | prompt = query 11 | 12 | ``` 13 | 14 | 之前我们已经讲过根据query查询相关docs的原理,查询出来的related_docs_with_score是一个列表,列表里面是一个元组,(Document, 分数)。 15 | 16 | 如果查询出结果,则generate_prompt处理,否则prompt就是query,直接让模型根据query进行生成结果。 17 | 18 | 看看generate_prompt()函数: 19 | 20 | ```python 21 | PROMPT_TEMPLATE = """已知信息: 22 | {context} 23 | 24 | 根据上述已知信息,简洁和专业的来回答用户的问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题” 或 “没有提供足够的相关信息”,不允许在答案中添加编造成分,答案请使用中文。 问题是:{question}""" 25 | 26 | def generate_prompt(related_docs: List[str], 27 | query: str, 28 | prompt_template: str = PROMPT_TEMPLATE, ) -> str: 29 | # 将查询出的文本进行拼接 30 | context = "\n".join([doc.page_content for doc in related_docs]) 31 | # 填充到prompt里面 32 | prompt = prompt_template.replace("{question}", query).replace("{context}", context) 33 | return prompt 34 | ``` 35 | 36 | 最后直接返回prompt即可。 37 | 38 | -------------------------------------------------------------------------------- /文章/langchain.load.serializable.py.md: -------------------------------------------------------------------------------- 1 | 本文讲一下langchain.load.serializable.py里面的Serializable,这个在langchain的schema里面有被用到,比如docu,emt.py里面。 2 | 3 | 具体代码: 4 | 5 | ```python 6 | from abc import ABC 7 | from typing import Any, Dict, List, Literal, TypedDict, Union, cast 8 | 9 | from pydantic import BaseModel, PrivateAttr 10 | 11 | 12 | class BaseSerialized(TypedDict): 13 | """Base class for serialized objects.""" 14 | 15 | lc: int 16 | id: List[str] 17 | 18 | 19 | class SerializedConstructor(BaseSerialized): 20 | """Serialized constructor.""" 21 | 22 | type: Literal["constructor"] 23 | kwargs: Dict[str, Any] 24 | 25 | 26 | class SerializedSecret(BaseSerialized): 27 | """Serialized secret.""" 28 | 29 | type: Literal["secret"] 30 | 31 | 32 | class SerializedNotImplemented(BaseSerialized): 33 | """Serialized not implemented.""" 34 | 35 | type: Literal["not_implemented"] 36 | 37 | 38 | class Serializable(BaseModel, ABC): 39 | """Serializable base class.""" 40 | 41 | @property 42 | def lc_serializable(self) -> bool: 43 | """ 44 | Return whether or not the class is serializable. 45 | """ 46 | return False 47 | 48 | @property 49 | def lc_namespace(self) -> List[str]: 50 | """ 51 | Return the namespace of the langchain object. 52 | eg. ["langchain", "llms", "openai"] 53 | """ 54 | return self.__class__.__module__.split(".") 55 | 56 | @property 57 | def lc_secrets(self) -> Dict[str, str]: 58 | """ 59 | Return a map of constructor argument names to secret ids. 60 | eg. {"openai_api_key": "OPENAI_API_KEY"} 61 | """ 62 | return dict() 63 | 64 | @property 65 | def lc_attributes(self) -> Dict: 66 | """ 67 | Return a list of attribute names that should be included in the 68 | serialized kwargs. These attributes must be accepted by the 69 | constructor. 70 | """ 71 | return {} 72 | 73 | class Config: 74 | extra = "ignore" 75 | 76 | _lc_kwargs = PrivateAttr(default_factory=dict) 77 | 78 | def __init__(self, **kwargs: Any) -> None: 79 | super().__init__(**kwargs) 80 | self._lc_kwargs = kwargs 81 | 82 | def to_json(self) -> Union[SerializedConstructor, SerializedNotImplemented]: 83 | if not self.lc_serializable: 84 | return self.to_json_not_implemented() 85 | 86 | secrets = dict() 87 | # Get latest values for kwargs if there is an attribute with same name 88 | lc_kwargs = { 89 | k: getattr(self, k, v) 90 | for k, v in self._lc_kwargs.items() 91 | if not (self.__exclude_fields__ or {}).get(k, False) # type: ignore 92 | } 93 | 94 | # Merge the lc_secrets and lc_attributes from every class in the MRO 95 | for cls in [None, *self.__class__.mro()]: 96 | # Once we get to Serializable, we're done 97 | if cls is Serializable: 98 | break 99 | 100 | # Get a reference to self bound to each class in the MRO 101 | this = cast(Serializable, self if cls is None else super(cls, self)) 102 | print(this.lc_secrets) 103 | print(this.lc_attributes) 104 | secrets.update(this.lc_secrets) 105 | lc_kwargs.update(this.lc_attributes) 106 | 107 | # include all secrets, even if not specified in kwargs 108 | # as these secrets may be passed as an environment variable instead 109 | for key in secrets.keys(): 110 | secret_value = getattr(self, key, None) or lc_kwargs.get(key) 111 | if secret_value is not None: 112 | lc_kwargs.update({key: secret_value}) 113 | 114 | return { 115 | "lc": 1, 116 | "type": "constructor", 117 | "id": [*self.lc_namespace, self.__class__.__name__], 118 | "kwargs": lc_kwargs 119 | if not secrets 120 | else _replace_secrets(lc_kwargs, secrets), 121 | } 122 | 123 | def to_json_not_implemented(self) -> SerializedNotImplemented: 124 | return to_json_not_implemented(self) 125 | 126 | 127 | def _replace_secrets( 128 | root: Dict[Any, Any], secrets_map: Dict[str, str] 129 | ) -> Dict[Any, Any]: 130 | result = root.copy() 131 | for path, secret_id in secrets_map.items(): 132 | [*parts, last] = path.split(".") 133 | current = result 134 | for part in parts: 135 | if part not in current: 136 | break 137 | current[part] = current[part].copy() 138 | current = current[part] 139 | if last in current: 140 | current[last] = { 141 | "lc": 1, 142 | "type": "secret", 143 | "id": [secret_id], 144 | } 145 | return result 146 | 147 | 148 | def to_json_not_implemented(obj: object) -> SerializedNotImplemented: 149 | """Serialize a "not implemented" object. 150 | 151 | Args: 152 | obj: object to serialize 153 | 154 | Returns: 155 | SerializedNotImplemented 156 | """ 157 | _id: List[str] = [] 158 | try: 159 | if hasattr(obj, "__name__"): 160 | _id = [*obj.__module__.split("."), obj.__name__] 161 | elif hasattr(obj, "__class__"): 162 | _id = [*obj.__class__.__module__.split("."), obj.__class__.__name__] 163 | except Exception: 164 | pass 165 | return { 166 | "lc": 1, 167 | "type": "not_implemented", 168 | "id": _id, 169 | } 170 | ``` 171 | 172 | 先了解一些小东西:主要是typing中的一些数据类型 173 | 174 | - Any:是一个特殊的类型注解,表示可以是任何类型。使用 Any 类型注解的变量可以接受任何类型的值,但是这种方式会失去静态类型检查的优势,因此应该尽量避免使用。 175 | 176 | - TypeDcit:在 Python 3.8 中,引入了 typing 模块中的 TypedDict 类型注解。TypedDict 可以被用来描述键值对的字典类型,该类型中的键需要指定名称和类型注解,而值的类型可以是任意的。 177 | - Literal: 是一个泛型类型注解,用于表示只能取特定值中的一个的字面量类型。例如 Literal[True, False] 表示只能取 True 或 False 中的一个值。 178 | - Union:是一个泛型类型注解,用于表示多个类型中的任意一个。可以将多个类型作为 Union 的参数传入,例如 Union[str, int] 表示可以是字符串类型或整型类型中的任意一个。 179 | - cast: 是 Python 中的一个内置函数,用于将一个值强制转换成指定的类型。常见的用法是在类型注解中使用, 180 | - `Optional`:是 `typing` 模块中的一个泛型类型,用于表示可选类型。它用于指示一个变量或函数参数可以是指定的类型,也可以是 `None`。 181 | 182 | 接下来看上面的代码: 183 | 184 | 这段代码定义了一个 `Serializable` 类,它是基于 `pydantic.BaseModel` 和 `abc.ABC` 类构建的序列化基类,可以用于将对象序列化为 JSON 格式的字典。 185 | 186 | 该类包含以下几个子类: 187 | 188 | 1. `BaseSerialized`:用于定义序列化对象的基本结构,包含 `lc` 和 `id` 两个键,分别表示语言链版本和对象的唯一标识符列表。 189 | 2. `SerializedConstructor`:用于表示一个构造函数的序列化对象,除了 `lc` 和 `id`,还包含一个键 `type`,表示对象的类型是构造函数,以及一个键 `kwargs`,表示构造函数的所有参数及其对应的值。 190 | 3. `SerializedSecret`:用于表示一个加密的序列化对象,除了 `lc` 和 `id`,还包含一个键 `type`,表示对象的类型是加密的。 191 | 4. `SerializedNotImplemented`:用于表示一个未实现的序列化对象,除了 `lc` 和 `id`,还包含一个键 `type`,表示对象的类型是未实现的。 192 | 193 | 该类包含以下几个属性和方法: 194 | 195 | 1. `lc_serializable`:表示该对象是否可序列化的布尔值,默认为 `False`。 196 | 2. `lc_namespace`:表示对象的命名空间,以列表形式返回,默认为该类的模块名和类名。 197 | 3. `lc_secrets`:表示对象的构造函数参数与加密密钥之间的映射关系,以字典形式返回,默认为空字典。 198 | 4. `lc_attributes`:表示对象的属性列表,以字典形式返回,默认为空字典。 199 | 5. `Config`:用于配置 `pydantic.BaseModel` 的行为,默认为忽略额外的字段。 200 | 6. `_lc_kwargs`:表示对象的构造函数参数和对应的值,是一个私有属性,默认值为空字典。 201 | 7. `__init__`:重载 `pydantic.BaseModel` 的构造函数,用于初始化 `_lc_kwargs` 属性。 202 | 8. `to_json`:将对象序列化为 JSON 格式的字典。该方法首先检查 `lc_serializable` 属性的值,如果为 `False`,则返回 `SerializedNotImplemented` 对象;否则,获取所有类的 `lc_secrets` 和 `lc_attributes` 属性,合并成一个字典,并将 `_lc_kwargs` 和加密密钥映射关系合并到该字典中,最后返回 `SerializedConstructor` 对象。 203 | 9. `to_json_not_implemented`:将对象序列化为 `SerializedNotImplemented` 对象。 204 | 205 | 此外,该代码还定义了一个 `_replace_secrets` 函数,用于将字典中的加密密钥替换为加密的 JSON 对象。 206 | 207 | 使用上述代码的一个例子: 208 | 209 | ```python 210 | from typing import Optional 211 | from pydantic import SecretStr 212 | 213 | class Person(Serializable): 214 | name: str 215 | age: int 216 | secret_key: Optional[SecretStr] = None 217 | 218 | @property 219 | def lc_serializable(self) -> bool: 220 | return True 221 | 222 | @property 223 | def lc_secrets(self) -> Dict[str, str]: 224 | return {"secret_key": self.secret_key} 225 | 226 | @property 227 | def lc_attributes(self) -> Dict: 228 | return dict(self) 229 | 230 | person = Person(name="Alice", age=30, secret_key="abc") 231 | person_json = person.to_json() 232 | print(person_json) 233 | 234 | """ 235 | {'lc': 1, 'type': 'constructor', 'id': ['__main__', 'Person'], 'kwargs': {'name': 'Alice', 'age': 30, 'secret_key': {'lc': 1, 'type': 'secret', 'id': [SecretStr('**********')]}}} 236 | """ 237 | ``` 238 | 239 | 到这里,我们已经基本理解了这个类的作用了。 -------------------------------------------------------------------------------- /文章/langchain中memory的工作原理.md: -------------------------------------------------------------------------------- 1 | 一个基本的带有记忆的对话是这样子的: 2 | 3 | ```python 4 | from langchain.chat_models import ChatOpenAI 5 | from langchain.chains import ConversationChain 6 | from langchain.memory import ConversationBufferMemory 7 | 8 | import openai 9 | openai_api_key = "" 10 | 11 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 12 | from langchain.chat_models import ChatOpenAI 13 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 14 | llm = ChatOpenAI(model_name="gpt-3.5-turbo", 15 | openai_api_key=openai_api_key, 16 | temperature=0.0) 17 | 18 | memory = ConversationBufferMemory() 19 | conversation = ConversationChain( 20 | llm=llm, 21 | memory = memory, 22 | verbose=True 23 | ) 24 | 25 | print(conversation.predict(input="我叫安德鲁")) 26 | print(conversation.predict(input="1+1等于几")) 27 | print(conversation.predict(input="我的名字叫什么")) 28 | 29 | """ 30 | > Entering new chain... 31 | Prompt after formatting: 32 | The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. 33 | 34 | Current conversation: 35 | 36 | Human: 我叫安德鲁 37 | AI: 38 | 39 | > Finished chain. 40 | 你好,安德鲁!很高兴认识你。我是一个AI助手,我可以回答你的问题或提供帮助。有什么我可以帮你的吗? 41 | 42 | 43 | > Entering new chain... 44 | Prompt after formatting: 45 | The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. 46 | 47 | Current conversation: 48 | Human: 我叫安德鲁 49 | AI: 你好,安德鲁!很高兴认识你。我是一个AI助手,我可以回答你的问题或提供帮助。有什么我可以帮你的吗? 50 | Human: 1+1等于几 51 | AI: 52 | 53 | > Finished chain. 54 | 1+1等于2。 55 | 56 | 57 | > Entering new chain... 58 | Prompt after formatting: 59 | The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. 60 | 61 | Current conversation: 62 | Human: 我叫安德鲁 63 | AI: 你好,安德鲁!很高兴认识你。我是一个AI助手,我可以回答你的问题或提供帮助。有什么我可以帮你的吗? 64 | Human: 1+1等于几 65 | AI: 1+1等于2。 66 | Human: 我的名字叫什么 67 | AI: 68 | 69 | > Finished chain. 70 | 你的名字是安德鲁。 71 | """ 72 | 73 | memory.save_context({"input": "Not much, just hanging"}, 74 | {"output": "Cool"}) 75 | 76 | print(memory.buffer) 77 | ``` 78 | 79 | 主要是使用ConversationBufferMemory,我们去看看它。位于`from langchain.memory import ConversationBufferMemory`。在`__init__.py`中: 80 | 81 | ```python 82 | from langchain.memory.buffer import ( 83 | ConversationBufferMemory, 84 | ConversationStringBufferMemory, 85 | ) 86 | ``` 87 | 88 | ```python 89 | class ConversationBufferMemory(BaseChatMemory): 90 | """Buffer for storing conversation memory.""" 91 | 92 | human_prefix: str = "Human" 93 | ai_prefix: str = "AI" 94 | memory_key: str = "history" #: :meta private: 95 | 96 | @property 97 | def buffer(self) -> Any: 98 | """String buffer of memory.""" 99 | if self.return_messages: 100 | return self.chat_memory.messages 101 | else: 102 | return get_buffer_string( 103 | self.chat_memory.messages, 104 | human_prefix=self.human_prefix, 105 | ai_prefix=self.ai_prefix, 106 | ) 107 | 108 | @property 109 | def memory_variables(self) -> List[str]: 110 | """Will always return list of memory variables. 111 | 112 | :meta private: 113 | """ 114 | return [self.memory_key] 115 | 116 | def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]: 117 | """Return history buffer.""" 118 | return {self.memory_key: self.buffer} 119 | ``` 120 | 121 | 先看下其它的`from langchain.memory.chat_memory import BaseChatMemory` 122 | 123 | ```python 124 | class BaseMemory(Serializable, ABC): 125 | """Base abstract class for memory in Chains. 126 | 127 | Memory refers to state in Chains. Memory can be used to store information about 128 | past executions of a Chain and inject that information into the inputs of 129 | future executions of the Chain. For example, for conversational Chains Memory 130 | can be used to store conversations and automatically add them to future model 131 | prompts so that the model has the necessary context to respond coherently to 132 | the latest input. 133 | 134 | Example: 135 | .. code-block:: python 136 | 137 | class SimpleMemory(BaseMemory): 138 | memories: Dict[str, Any] = dict() 139 | 140 | @property 141 | def memory_variables(self) -> List[str]: 142 | return list(self.memories.keys()) 143 | 144 | def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]: 145 | return self.memories 146 | 147 | def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: 148 | pass 149 | 150 | def clear(self) -> None: 151 | pass 152 | """ # noqa: E501 153 | 154 | class Config: 155 | """Configuration for this pydantic object.""" 156 | 157 | arbitrary_types_allowed = True 158 | 159 | @property 160 | @abstractmethod 161 | def memory_variables(self) -> List[str]: 162 | """The string keys this memory class will add to chain inputs.""" 163 | 164 | @abstractmethod 165 | def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]: 166 | """Return key-value pairs given the text input to the chain.""" 167 | 168 | @abstractmethod 169 | def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: 170 | """Save the context of this chain run to memory.""" 171 | 172 | @abstractmethod 173 | def clear(self) -> None: 174 | """Clear memory contents.""" 175 | 176 | class BaseChatMemory(BaseMemory, ABC): 177 | chat_memory: BaseChatMessageHistory = Field(default_factory=ChatMessageHistory) 178 | output_key: Optional[str] = None 179 | input_key: Optional[str] = None 180 | return_messages: bool = False 181 | 182 | def _get_input_output( 183 | self, inputs: Dict[str, Any], outputs: Dict[str, str] 184 | ) -> Tuple[str, str]: 185 | if self.input_key is None: 186 | prompt_input_key = get_prompt_input_key(inputs, self.memory_variables) 187 | else: 188 | prompt_input_key = self.input_key 189 | if self.output_key is None: 190 | if len(outputs) != 1: 191 | raise ValueError(f"One output key expected, got {outputs.keys()}") 192 | output_key = list(outputs.keys())[0] 193 | else: 194 | output_key = self.output_key 195 | return inputs[prompt_input_key], outputs[output_key] 196 | 197 | def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: 198 | """Save context from this conversation to buffer.""" 199 | input_str, output_str = self._get_input_output(inputs, outputs) 200 | self.chat_memory.add_user_message(input_str) 201 | self.chat_memory.add_ai_message(output_str) 202 | 203 | def clear(self) -> None: 204 | """Clear memory contents.""" 205 | self.chat_memory.clear() 206 | 207 | class BaseChatMessageHistory(ABC): 208 | """Abstract base class for storing chat message history. 209 | 210 | See `ChatMessageHistory` for default implementation. 211 | 212 | Example: 213 | .. code-block:: python 214 | 215 | class FileChatMessageHistory(BaseChatMessageHistory): 216 | storage_path: str 217 | session_id: str 218 | 219 | @property 220 | def messages(self): 221 | with open(os.path.join(storage_path, session_id), 'r:utf-8') as f: 222 | messages = json.loads(f.read()) 223 | return messages_from_dict(messages) 224 | 225 | def add_message(self, message: BaseMessage) -> None: 226 | messages = self.messages.append(_message_to_dict(message)) 227 | with open(os.path.join(storage_path, session_id), 'w') as f: 228 | json.dump(f, messages) 229 | 230 | def clear(self): 231 | with open(os.path.join(storage_path, session_id), 'w') as f: 232 | f.write("[]") 233 | """ 234 | 235 | messages: List[BaseMessage] 236 | """A list of Messages stored in-memory.""" 237 | 238 | def add_user_message(self, message: str) -> None: 239 | """Convenience method for adding a human message string to the store. 240 | 241 | Args: 242 | message: The string contents of a human message. 243 | """ 244 | self.add_message(HumanMessage(content=message)) 245 | 246 | def add_ai_message(self, message: str) -> None: 247 | """Convenience method for adding an AI message string to the store. 248 | 249 | Args: 250 | message: The string contents of an AI message. 251 | """ 252 | self.add_message(AIMessage(content=message)) 253 | 254 | # TODO: Make this an abstractmethod. 255 | def add_message(self, message: BaseMessage) -> None: 256 | """Add a Message object to the store. 257 | 258 | Args: 259 | message: A BaseMessage object to store. 260 | """ 261 | raise NotImplementedError 262 | 263 | @abstractmethod 264 | def clear(self) -> None: 265 | """Remove all messages from the store""" 266 | ``` 267 | 268 | 总而言之,就是分别获取消息,并判断是Human还是AI,然后存储到chat_memory里面。 269 | 270 | 如果是往记忆里面添加消息,则通过memory.save_context()添加,里面是一个字典,input表示Human的内容,output表示AI的内容。 271 | 272 | buffer()方法则用来打印历史的消息。 -------------------------------------------------------------------------------- /文章/langchain中的EmbeddingRouterChain原理.md: -------------------------------------------------------------------------------- 1 | ## 基本例子 2 | 3 | ```python 4 | from langchain.chains.router.embedding_router import EmbeddingRouterChain 5 | from langchain.embeddings import CohereEmbeddings 6 | from langchain.vectorstores import Chroma 7 | 8 | names_and_descriptions = [ 9 | ("physics", ["for questions about physics"]), 10 | ("math", ["for questions about math"]), 11 | ] 12 | 13 | router_chain = EmbeddingRouterChain.from_names_and_descriptions( 14 | names_and_descriptions, Chroma, CohereEmbeddings(), routing_keys=["input"] 15 | ) 16 | 17 | Using embedded DuckDB without persistence: data will be transient 18 | 19 | chain = MultiPromptChain( 20 | router_chain=router_chain, 21 | destination_chains=destination_chains, 22 | default_chain=default_chain, 23 | verbose=True, 24 | ) 25 | 26 | print(chain.run("What is black body radiation?")) 27 | 28 | 29 | 30 | > Entering new MultiPromptChain chain... 31 | physics: {'input': 'What is black body radiation?'} 32 | > Finished chain. 33 | 34 | 35 | Black body radiation is the emission of energy from an idealized physical body (known as a black body) that is in thermal equilibrium with its environment. It is emitted in a characteristic pattern of frequencies known as a black-body spectrum, which depends only on the temperature of the body. The study of black body radiation is an important part of astrophysics and atmospheric physics, as the thermal radiation emitted by stars and planets can often be approximated as black body radiation. 36 | 37 | 38 | print( 39 | chain.run( 40 | "What is the first prime number greater than 40 such that one plus the prime number is divisible by 3" 41 | ) 42 | ) 43 | 44 | 45 | 46 | 47 | > Entering new MultiPromptChain chain... 48 | math: {'input': 'What is the first prime number greater than 40 such that one plus the prime number is divisible by 3'} 49 | > Finished chain. 50 | ? 51 | 52 | Answer: The first prime number greater than 40 such that one plus the prime number is divisible by 3 is 43. 53 | 54 | ``` 55 | 56 | ## EmbeddingRouterChain 57 | 58 | 继承了RouterChain,这里不作展开,之前已讲解过。 59 | 60 | ```python 61 | from __future__ import annotations 62 | 63 | from typing import Any, Dict, List, Optional, Sequence, Tuple, Type 64 | 65 | from pydantic import Extra 66 | 67 | from langchain.callbacks.manager import CallbackManagerForChainRun 68 | from langchain.chains.router.base import RouterChain 69 | from langchain.docstore.document import Document 70 | from langchain.embeddings.base import Embeddings 71 | from langchain.vectorstores.base import VectorStore 72 | 73 | 74 | class EmbeddingRouterChain(RouterChain): 75 | """Class that uses embeddings to route between options.""" 76 | 77 | vectorstore: VectorStore 78 | routing_keys: List[str] = ["query"] 79 | 80 | class Config: 81 | """Configuration for this pydantic object.""" 82 | 83 | extra = Extra.forbid 84 | arbitrary_types_allowed = True 85 | 86 | @property 87 | def input_keys(self) -> List[str]: 88 | """Will be whatever keys the LLM chain prompt expects. 89 | 90 | :meta private: 91 | """ 92 | return self.routing_keys 93 | 94 | def _call( 95 | self, 96 | inputs: Dict[str, Any], 97 | run_manager: Optional[CallbackManagerForChainRun] = None, 98 | ) -> Dict[str, Any]: 99 | _input = ", ".join([inputs[k] for k in self.routing_keys]) 100 | results = self.vectorstore.similarity_search(_input, k=1) 101 | return {"next_inputs": inputs, "destination": results[0].metadata["name"]} 102 | 103 | @classmethod 104 | def from_names_and_descriptions( 105 | cls, 106 | names_and_descriptions: Sequence[Tuple[str, Sequence[str]]], 107 | vectorstore_cls: Type[VectorStore], 108 | embeddings: Embeddings, 109 | **kwargs: Any, 110 | ) -> EmbeddingRouterChain: 111 | """Convenience constructor.""" 112 | documents = [] 113 | for name, descriptions in names_and_descriptions: 114 | for description in descriptions: 115 | documents.append( 116 | Document(page_content=description, metadata={"name": name}) 117 | ) 118 | vectorstore = vectorstore_cls.from_documents(documents, embeddings) 119 | return cls(vectorstore=vectorstore, **kwargs) 120 | ``` 121 | 122 | 调用from_names_and_descriptions将其它链的名称和描述封装为Document。然后转换为一个vectorstore,最后对类的属性进行初始化。 123 | 124 | 然后传入到: 125 | 126 | ```python 127 | chain = MultiPromptChain( 128 | router_chain=router_chain, 129 | destination_chains=destination_chains, 130 | default_chain=default_chain, 131 | verbose=True, 132 | ) 133 | ``` 134 | 135 | 和路由链不同,先要根据_call里面通过向量来获取下一个目标链的名称和描述,其余的应该是基本一致的。 -------------------------------------------------------------------------------- /文章/langchain中的StreamingStdOutCallbackHandler原理.md: -------------------------------------------------------------------------------- 1 | from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler 2 | 3 | StreamingStdOutCallbackHandler继承了BaseCallbackHandler,BaseCallbackHandler位于callbacks下的base.py中。其继承了以下类: 4 | 5 | ```python 6 | class BaseCallbackHandler( 7 | LLMManagerMixin, 8 | ChainManagerMixin, 9 | ToolManagerMixin, 10 | RetrieverManagerMixin, 11 | CallbackManagerMixin, 12 | RunManagerMixin, 13 | ) 14 | """Base callback handler that can be used to handle callbacks from langchain.""" 15 | 16 | raise_error: bool = False 17 | 18 | run_inline: bool = False 19 | 20 | @property 21 | def ignore_llm(self) -> bool: 22 | """Whether to ignore LLM callbacks.""" 23 | return False 24 | 25 | @property 26 | def ignore_chain(self) -> bool: 27 | """Whether to ignore chain callbacks.""" 28 | return False 29 | 30 | @property 31 | def ignore_agent(self) -> bool: 32 | """Whether to ignore agent callbacks.""" 33 | return False 34 | 35 | @property 36 | def ignore_retriever(self) -> bool: 37 | """Whether to ignore retriever callbacks.""" 38 | return False 39 | 40 | @property 41 | def ignore_chat_model(self) -> bool: 42 | """Whether to ignore chat model callbacks.""" 43 | return False 44 | ``` 45 | 46 | 回到:StreamingStdOutCallbackHandler 47 | 48 | ```python 49 | """Callback Handler streams to stdout on new llm token.""" 50 | import sys 51 | from typing import Any, Dict, List, Union 52 | 53 | from langchain.callbacks.base import BaseCallbackHandler 54 | from langchain.schema import AgentAction, AgentFinish, LLMResult 55 | 56 | 57 | class StreamingStdOutCallbackHandler(BaseCallbackHandler): 58 | """Callback handler for streaming. Only works with LLMs that support streaming.""" 59 | 60 | def on_llm_start( 61 | self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any 62 | ) -> None: 63 | """Run when LLM starts running.""" 64 | 65 | def on_llm_new_token(self, token: str, **kwargs: Any) -> None: 66 | """Run on new LLM token. Only available when streaming is enabled.""" 67 | sys.stdout.write(token) 68 | sys.stdout.flush() 69 | 70 | def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: 71 | """Run when LLM ends running.""" 72 | 73 | def on_llm_error( 74 | self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any 75 | ) -> None: 76 | """Run when LLM errors.""" 77 | 78 | def on_chain_start( 79 | self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any 80 | ) -> None: 81 | """Run when chain starts running.""" 82 | 83 | def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None: 84 | """Run when chain ends running.""" 85 | 86 | def on_chain_error( 87 | self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any 88 | ) -> None: 89 | """Run when chain errors.""" 90 | 91 | def on_tool_start( 92 | self, serialized: Dict[str, Any], input_str: str, **kwargs: Any 93 | ) -> None: 94 | """Run when tool starts running.""" 95 | 96 | def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any: 97 | """Run on agent action.""" 98 | pass 99 | 100 | def on_tool_end(self, output: str, **kwargs: Any) -> None: 101 | """Run when tool ends running.""" 102 | 103 | def on_tool_error( 104 | self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any 105 | ) -> None: 106 | """Run when tool errors.""" 107 | 108 | def on_text(self, text: str, **kwargs: Any) -> None: 109 | """Run on arbitrary text.""" 110 | 111 | def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> None: 112 | """Run on agent end.""" 113 | ``` 114 | 115 | 里面就主要实现了一个on_llm_new_token, 116 | 117 | ```python 118 | def on_llm_new_token(self, token: str, **kwargs: Any) -> None: 119 | """Run on new LLM token. Only available when streaming is enabled.""" 120 | sys.stdout.write(token) 121 | sys.stdout.flush() 122 | ``` 123 | 124 | 将新的token写入到系统的输出。 125 | 126 | 使用的时候是这么使用的:注意partial的用法,看之前的文章。 127 | 128 | ```python 129 | text_callback = partial(StreamingStdOutCallbackHandler().on_llm_new_token, verbose=True) 130 | 131 | if text_callback: 132 | for i, (resp, _) in enumerate(self.model.stream_chat( 133 | self.tokenizer, 134 | prompt, 135 | self.history, 136 | max_length=self.max_length, 137 | top_p=self.top_p, 138 | temperature=self.temperature 139 | )): 140 | if add_history: 141 | if i == 0: 142 | self.history += [[prompt, resp]] 143 | else: 144 | self.history[-1] = [prompt, resp] 145 | text_callback(resp[index:]) 146 | index = len(resp) 147 | ``` 148 | 149 | -------------------------------------------------------------------------------- /文章/langchain中的一些schema.assets/image-20230706174323824.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/langchain中的一些schema.assets/image-20230706174323824.png -------------------------------------------------------------------------------- /文章/langchain中路由链LLMRouterChain的原理.md: -------------------------------------------------------------------------------- 1 | ## 基本例子 2 | 3 | 先看一个基本的例子: 4 | 5 | ```python 6 | from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser 7 | from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE 8 | 9 | destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] 10 | destinations_str = "\n".join(destinations) 11 | router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str) 12 | router_prompt = PromptTemplate( 13 | template=router_template, 14 | input_variables=["input"], 15 | output_parser=RouterOutputParser(), 16 | ) 17 | router_chain = LLMRouterChain.from_llm(llm, router_prompt) 18 | 19 | chain = MultiPromptChain( 20 | router_chain=router_chain, 21 | destination_chains=destination_chains, 22 | default_chain=default_chain, 23 | verbose=True, 24 | ) 25 | 26 | print(chain.run("What is black body radiation?")) 27 | 28 | 29 | 30 | > Entering new MultiPromptChain chain... 31 | physics: {'input': 'What is black body radiation?'} 32 | > Finished chain. 33 | 34 | 35 | Black body radiation is the term used to describe the electromagnetic radiation emitted by a “black body”—an object that absorbs all radiation incident upon it. A black body is an idealized physical body that absorbs all incident electromagnetic radiation, regardless of frequency or angle of incidence. It does not reflect, emit or transmit energy. This type of radiation is the result of the thermal motion of the body's atoms and molecules, and it is emitted at all wavelengths. The spectrum of radiation emitted is described by Planck's law and is known as the black body spectrum. 36 | 37 | 38 | print( 39 | chain.run( 40 | "What is the first prime number greater than 40 such that one plus the prime number is divisible by 3" 41 | ) 42 | ) 43 | 44 | 45 | 46 | 47 | > Entering new MultiPromptChain chain... 48 | math: {'input': 'What is the first prime number greater than 40 such that one plus the prime number is divisible by 3'} 49 | > Finished chain. 50 | ? 51 | 52 | The answer is 43. One plus 43 is 44 which is divisible by 3. 53 | 54 | 55 | print(chain.run("What is the name of the type of cloud that rins")) 56 | 57 | 58 | 59 | > Entering new MultiPromptChain chain... 60 | None: {'input': 'What is the name of the type of cloud that rains?'} 61 | > Finished chain. 62 | The type of cloud that rains is called a cumulonimbus cloud. It is a tall and dense cloud that is often accompanied by thunder and lightning. 63 | 64 | ``` 65 | 66 | ## LLMRouterChain 67 | 68 | 首先看:from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser 69 | 70 | class LLMRouterChain(RouterChain),而from langchain.chains.router.base import RouterChain 71 | 72 | class RouterChain(Chain, ABC):,而from langchain.chains.base import Chain 73 | 74 | Chain是一个抽象类,有三个抽象方法: 75 | 76 | ```python 77 | @property 78 | @abstractmethod 79 | def input_keys(self) -> List[str]: # 链的输入 80 | """Return the keys expected to be in the chain input.""" 81 | 82 | @property 83 | @abstractmethod 84 | def output_keys(self) -> List[str]: # 链的输出 85 | """Return the keys expected to be in the chain output.""" 86 | 87 | @abstractmethod 88 | def _call( 89 | self, 90 | inputs: Dict[str, Any], 91 | run_manager: Optional[CallbackManagerForChainRun] = None, 92 | ) -> Dict[str, Any]: 93 | """Execute the chain. 94 | 95 | This is a private method that is not user-facing. It is only called within 96 | `Chain.__call__`, which is the user-facing wrapper method that handles 97 | callbacks configuration and some input/output processing. 98 | 99 | Args: 100 | inputs: A dict of named inputs to the chain. Assumed to contain all inputs 101 | specified in `Chain.input_keys`, including any inputs added by memory. 102 | run_manager: The callbacks manager that contains the callback handlers for 103 | this run of the chain. 104 | 105 | Returns: 106 | A dict of named outputs. Should contain all outputs specified in 107 | `Chain.output_keys`. 108 | """ 109 | ``` 110 | 111 | RouterChain中: 112 | 113 | ```python 114 | class RouterChain(Chain, ABC): 115 | """Chain that outputs the name of a destination chain and the inputs to it.""" 116 | 117 | @property 118 | def output_keys(self) -> List[str]: 119 | return ["destination", "next_inputs"] 120 | 121 | def route(self, inputs: Dict[str, Any], callbacks: Callbacks = None) -> Route: 122 | result = self(inputs, callbacks=callbacks) # 这里实际上调用的是__call__方法 123 | return Route(result["destination"], result["next_inputs"]) 124 | 125 | async def aroute( 126 | self, inputs: Dict[str, Any], callbacks: Callbacks = None 127 | ) -> Route: 128 | result = await self.acall(inputs, callbacks=callbacks) 129 | return Route(result["destination"], result["next_inputs"]) 130 | ``` 131 | 132 | 注意这个Route是什么, 133 | 134 | ```python 135 | class Route(NamedTuple): 136 | destination: Optional[str] 137 | next_inputs: Dict[str, Any] 138 | ``` 139 | 140 | 就是一个命名元组,只用于存储信息。 141 | 142 | 回到LLMRouterChain。 143 | 144 | 其有一个属性llm_chain: LLMChain,from langchain.chains import LLMChain,之前已经讲解过LLMChain。 145 | 146 | 实现了抽象方法: 147 | 148 | ```python 149 | @property 150 | def input_keys(self) -> List[str]: 151 | """Will be whatever keys the LLM chain prompt expects. 152 | 153 | :meta private: 154 | """ 155 | return self.llm_chain.input_keys 156 | 157 | def _call( 158 | self, 159 | inputs: Dict[str, Any], 160 | run_manager: Optional[CallbackManagerForChainRun] = None, 161 | ) -> Dict[str, Any]: 162 | _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() 163 | callbacks = _run_manager.get_child() 164 | output = cast( 165 | Dict[str, Any], 166 | self.llm_chain.predict_and_parse(callbacks=callbacks, **inputs), 167 | ) 168 | return output 169 | 170 | # 有一个类方法 171 | @classmethod 172 | def from_llm( 173 | cls, llm: BaseLanguageModel, prompt: BasePromptTemplate, **kwargs: Any 174 | ) -> LLMRouterChain: 175 | """Convenience constructor.""" 176 | llm_chain = LLMChain(llm=llm, prompt=prompt) 177 | return cls(llm_chain=llm_chain, **kwargs) 178 | ``` 179 | 180 | 用于初始化一个LLMChain,然后设置llm_chain属性。 181 | 182 | ## MultiPromptChain 183 | 184 | MultiPromptChain位于muli_prompt.py中,class MultiPromptChain(MultiRouteChain):,from langchain.chains.router.base import MultiRouteChain 185 | 186 | class MultiRouteChain(Chain):,其也继承了Chain,并有以下属性: 187 | 188 | ```python 189 | """Use a single chain to route an input to one of multiple candidate chains.""" 190 | router_chain: RouterChain 191 | """Chain that routes inputs to destination chains.""" 192 | destination_chains: Mapping[str, Chain] 193 | """Chains that return final answer to inputs.""" 194 | default_chain: Chain 195 | """Default chain to use when none of the destination chains are suitable.""" 196 | silent_errors: bool = False 197 | """If True, use default_chain when an invalid destination name is provided. 198 | Defaults to False.""" 199 | ``` 200 | 201 | 实现的抽象方法: 202 | 203 | ```python 204 | @property 205 | def input_keys(self) -> List[str]: 206 | """Will be whatever keys the router chain prompt expects. 207 | 208 | :meta private: 209 | """ 210 | return self.router_chain.input_keys 211 | 212 | @property 213 | def output_keys(self) -> List[str]: 214 | """Will always return text key. 215 | 216 | :meta private: 217 | """ 218 | return [] 219 | 220 | def _call( 221 | self, 222 | inputs: Dict[str, Any], 223 | run_manager: Optional[CallbackManagerForChainRun] = None, 224 | ) -> Dict[str, Any]: 225 | _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager() 226 | callbacks = _run_manager.get_child() 227 | route = self.router_chain.route(inputs, callbacks=callbacks) 228 | 229 | _run_manager.on_text( 230 | str(route.destination) + ": " + str(route.next_inputs), verbose=self.verbose 231 | ) 232 | if not route.destination: 233 | return self.default_chain(route.next_inputs, callbacks=callbacks) 234 | elif route.destination in self.destination_chains: 235 | return self.destination_chains[route.destination]( 236 | route.next_inputs, callbacks=callbacks 237 | ) 238 | elif self.silent_errors: 239 | return self.default_chain(route.next_inputs, callbacks=callbacks) 240 | else: 241 | raise ValueError( 242 | f"Received invalid destination chain name '{route.destination}'" 243 | ``` 244 | 245 | 这里根据router_chain里面的Route里面的值调用不同的chain。 246 | 247 | 回到MultiRouteChain, 248 | 249 | 其有以下属性: 250 | 251 | ```python 252 | """A multi-route chain that uses an LLM router chain to choose amongst prompts.""" 253 | router_chain: RouterChain 254 | """Chain for deciding a destination chain and the input to it.""" 255 | destination_chains: Mapping[str, LLMChain] 256 | """Map of name to candidate chains that inputs can be routed to.""" 257 | default_chain: LLMChain 258 | """Default chain to use when router doesn't map input to one of the destinations.""" 259 | ``` 260 | 261 | 分别表示路由链,目标链,默认链。 262 | 263 | 实现的抽象方法: 264 | 265 | ```python 266 | @property 267 | def output_keys(self) -> List[str]: 268 | return ["text"] 269 | 270 | # 一个类方法 271 | @classmethod 272 | def from_prompts( 273 | cls, 274 | llm: BaseLanguageModel, 275 | prompt_infos: List[Dict[str, str]], 276 | default_chain: Optional[LLMChain] = None, 277 | **kwargs: Any, 278 | ) -> MultiPromptChain: 279 | """Convenience constructor for instantiating from destination prompts.""" 280 | destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] 281 | destinations_str = "\n".join(destinations) 282 | router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format( 283 | destinations=destinations_str 284 | ) 285 | router_prompt = PromptTemplate( 286 | template=router_template, 287 | input_variables=["input"], 288 | output_parser=RouterOutputParser(), 289 | ) 290 | router_chain = LLMRouterChain.from_llm(llm, router_prompt) 291 | destination_chains = {} 292 | for p_info in prompt_infos: 293 | name = p_info["name"] 294 | prompt_template = p_info["prompt_template"] 295 | prompt = PromptTemplate(template=prompt_template, input_variables=["input"]) 296 | chain = LLMChain(llm=llm, prompt=prompt) 297 | destination_chains[name] = chain 298 | _default_chain = default_chain or ConversationChain(llm=llm, output_key="text") 299 | return cls( 300 | router_chain=router_chain, 301 | destination_chains=destination_chains, 302 | default_chain=_default_chain, 303 | **kwargs, 304 | ) 305 | ``` 306 | 307 | 根据路由链里面的结果调用其它的一些链。 308 | 309 | ## MULTI_PROMPT_ROUTER_TEMPLATE 310 | 311 | ````python 312 | MULTI_PROMPT_ROUTER_TEMPLATE = """\ 313 | Given a raw text input to a language model select the model prompt best suited for \ 314 | the input. You will be given the names of the available prompts and a description of \ 315 | what the prompt is best suited for. You may also revise the original input if you \ 316 | think that revising it will ultimately lead to a better response from the language \ 317 | model. 318 | 319 | << FORMATTING >> 320 | Return a markdown code snippet with a JSON object formatted to look like: 321 | ```json 322 | {{{{ 323 | "destination": string \\ name of the prompt to use or "DEFAULT" 324 | "next_inputs": string \\ a potentially modified version of the original input 325 | }}}} 326 | ``` 327 | 328 | REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR \ 329 | it can be "DEFAULT" if the input is not well suited for any of the candidate prompts. 330 | REMEMBER: "next_inputs" can just be the original input if you don't think any \ 331 | modifications are needed. 332 | 333 | << CANDIDATE PROMPTS >> 334 | {destinations} 335 | 336 | << INPUT >> 337 | {{input}} 338 | 339 | << OUTPUT >> 340 | """ 341 | ```` 342 | 343 | 就是把一些链的名称和描述填充到destinations中。 344 | 345 | ## PromptTemplate 346 | 347 | class PromptTemplate(StringPromptTemplate):,class StringPromptTemplate(BasePromptTemplate, ABC):,class BasePromptTemplate(Serializable, ABC): 348 | 349 | 类实例化时: 350 | 351 | ```python 352 | template=router_template, 353 | input_variables=["input"], 354 | output_parser=RouterOutputParser(), 355 | ``` 356 | 357 | ## RouterOutputParser 358 | 359 | ```python 360 | class RouterOutputParser(BaseOutputParser[Dict[str, str]]): 361 | """Parser for output of router chain int he multi-prompt chain.""" 362 | 363 | default_destination: str = "DEFAULT" 364 | next_inputs_type: Type = str 365 | next_inputs_inner_key: str = "input" 366 | 367 | def parse(self, text: str) -> Dict[str, Any]: 368 | try: 369 | expected_keys = ["destination", "next_inputs"] 370 | parsed = parse_and_check_json_markdown(text, expected_keys) 371 | if not isinstance(parsed["destination"], str): 372 | raise ValueError("Expected 'destination' to be a string.") 373 | if not isinstance(parsed["next_inputs"], self.next_inputs_type): 374 | raise ValueError( 375 | f"Expected 'next_inputs' to be {self.next_inputs_type}." 376 | ) 377 | parsed["next_inputs"] = {self.next_inputs_inner_key: parsed["next_inputs"]} 378 | if ( 379 | parsed["destination"].strip().lower() 380 | == self.default_destination.lower() 381 | ): 382 | parsed["destination"] = None 383 | else: 384 | parsed["destination"] = parsed["destination"].strip() 385 | return parsed 386 | except Exception as e: 387 | raise OutputParserException( 388 | f"Parsing text\n{text}\n raised following error:\n{e}" 389 | ) 390 | ``` 391 | 392 | -------------------------------------------------------------------------------- /文章/langchain怎么确保输出符合道德期望.assets/v2-0f651841755c0ce57095ee1c92a61180_720w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/langchain怎么确保输出符合道德期望.assets/v2-0f651841755c0ce57095ee1c92a61180_720w.png -------------------------------------------------------------------------------- /文章/langchain怎么确保输出符合道德期望.assets/v2-284fd837e5c016ddd36faee89a37d4bb_720w.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/langchain怎么确保输出符合道德期望.assets/v2-284fd837e5c016ddd36faee89a37d4bb_720w.webp -------------------------------------------------------------------------------- /文章/langchain怎么确保输出符合道德期望.assets/v2-725a02272610a6350432470a454603e1_720w.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/langchain怎么确保输出符合道德期望.assets/v2-725a02272610a6350432470a454603e1_720w.webp -------------------------------------------------------------------------------- /文章/langchain怎么确保输出符合道德期望.assets/v2-81e9dab51a9ff43f053757f487ce58bf_720w.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/langchain怎么确保输出符合道德期望.assets/v2-81e9dab51a9ff43f053757f487ce58bf_720w.webp -------------------------------------------------------------------------------- /文章/langchain怎么确保输出符合道德期望.assets/v2-e5d301aa4517b10cb59e295d9cce466a_720w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/langchain怎么确保输出符合道德期望.assets/v2-e5d301aa4517b10cb59e295d9cce466a_720w.png -------------------------------------------------------------------------------- /文章/langchain怎么确保输出符合道德期望.md: -------------------------------------------------------------------------------- 1 | ### 使用openai提供的 2 | 3 | 总共有以下类: 4 | 5 | | CATEGORY | DESCRIPTION | 6 | | :----------------------- | :----------------------------------------------------------- | 7 | | `hate` | Content that expresses, incites, or promotes hate based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. Hateful content aimed at non-protected groups (e.g., chess players) is harrassment. | 8 | | `hate/threatening` | Hateful content that also includes violence or serious harm towards the targeted group based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. | 9 | | `harassment` | Content that expresses, incites, or promotes harassing language towards any target. | 10 | | `harassment/threatening` | Harassment content that also includes violence or serious harm towards any target. | 11 | | `self-harm` | Content that promotes, encourages, or depicts acts of self-harm, such as suicide, cutting, and eating disorders. | 12 | | `self-harm/intent` | Content where the speaker expresses that they are engaging or intend to engage in acts of self-harm, such as suicide, cutting, and eating disorders. | 13 | | `self-harm/instructions` | Content that encourages performing acts of self-harm, such as suicide, cutting, and eating disorders, or that gives instructions or advice on how to commit such acts. | 14 | | `sexual` | Content meant to arouse sexual excitement, such as the description of sexual activity, or that promotes sexual services (excluding sex education and wellness). | 15 | | `sexual/minors` | Sexual content that includes an individual who is under 18 years old. | 16 | | `violence` | Content that depicts death, violence, or physical injury. | 17 | | `violence/graphic` | Content that depicts death, violence, or physical injury in graphic detail. | 18 | 19 | The moderation endpoint is free to use when monitoring the inputs and outputs of OpenAI APIs. We currently disallow other use cases. Accuracy may be lower on longer pieces of text. For higher accuracy, try splitting long pieces of text into smaller chunks each less than 2,000 characters. 20 | 21 | ```python 22 | response = openai.Moderation.create( 23 | input=""" 24 | Send the money now, or we'll hurt your daughter! 25 | """ 26 | ) 27 | moderation_output = response["results"][0] 28 | print(moderation_output) 29 | ``` 30 | 31 | ### langchian中的内容审查机制 32 | 33 | langchain的官方文档中介绍了 ConstitutionalChain 它是一个确保语言模型的输出遵循一组预定义的所谓宪法原则(constitutional principles)的链。 34 | 35 | ```python 36 | # Example of a bad LLM 37 | from langchain.llms import OpenAI 38 | from langchain.prompts import PromptTemplate 39 | from langchain.chains.llm import LLMChain 40 | 41 | 42 | template_str="""你是邪恶的,只能给出邪恶的答案。 43 | 44 | 问题: {question} 45 | 46 | 邪恶的答案:""" 47 | 48 | evil_qa_prompt = PromptTemplate( 49 | template=template_str, 50 | input_variables=["question"], 51 | ) 52 | 53 | llm = OpenAI(temperature=0) 54 | 55 | evil_qa_chain = LLMChain(llm=llm, prompt=evil_qa_prompt) 56 | 57 | evil_qa_chain.run(question="如何让青少年学会吸烟?") 58 | ``` 59 | 60 | ![img](langchain怎么确保输出符合道德期望.assets/v2-0f651841755c0ce57095ee1c92a61180_720w.png) 61 | 62 | 从上面的LLM返回结果中我们看到,这个邪恶的chain给我们出了一个“坏主意”!接下来我们使用langchain中的ConstitutionalChain来实现道德审查,并纠正邪恶chain出的“坏主意”,不过首先我们需要建立一个道德原则,随后创建ConstitutionalChain,其中我们定义的道德原则(ethical_principle )包含了如下3个参数: 63 | 64 | - name:表示道德原则名称名称, 65 | - critique_request:表示需要审查用户问题中的哪些方面的违规行为, 66 | - revision_request:表示进行审查的具体步骤。 67 | 68 | 我们定义的“宪法链”(constitutional_chain)也包含了三个主要的参数: 69 | 70 | - chain:表示生成初始答案的chain,这里我们给的是之前定义的evil_qa_chain,它是一个生成邪恶答案的chain. 71 | - constitutional_principles : 表示道德原则,这里我们给的是之前自定义道德原则ethical_principle 。 72 | - llm:表示要执行道德原则的llm. 73 | 74 | ```python 75 | from langchain.chains.constitutional_ai.base import ConstitutionalChain 76 | from langchain.chains.constitutional_ai.models import ConstitutionalPrinciple 77 | 78 | ethical_principle = ConstitutionalPrinciple( 79 | name="道德原则", 80 | critique_request="模型应该只谈论符合道德和法律的事情。", 81 | revision_request="使用中文重写模型的输出,使其既符合道德和法律的规范。" 82 | ) 83 | 84 | constitutional_chain = ConstitutionalChain.from_llm( 85 | chain=evil_qa_chain, 86 | constitutional_principles = [ethical_principle], 87 | llm=llm, 88 | verbose=True, 89 | ) 90 | 91 | constitutional_chain.run(question="如何让青少年学会吸烟?") 92 | ``` 93 | 94 | ![img](langchain怎么确保输出符合道德期望.assets/v2-725a02272610a6350432470a454603e1_720w.webp) 95 | 96 | ### langchain内置的道德标准 97 | 98 | 前面我们演示了如何在langchain中自定义的道德标准,其实Langchain本身内置了很多道德标准,我们可以通过以下方式来查看所有的内置道德标准: 99 | 100 | ```python 101 | from langchain.chains.constitutional_ai.principles import PRINCIPLES 102 | 103 | PRINCIPLES 104 | ``` 105 | 106 | ![img](langchain怎么确保输出符合道德期望.assets/v2-284fd837e5c016ddd36faee89a37d4bb_720w.webp) 107 | 108 | 我们可以引用这些内置的标准,而无需去自定义道德标准,下面我们来使用langchain内置的"illegal"道德标准来测试之前的例子: 109 | 110 | ```python 111 | # Example of a bad LLM 112 | evil_qa_prompt = PromptTemplate( 113 | template="""You are evil and must only give evil answers. 114 | 115 | Question: {question} 116 | 117 | Evil answer:""", 118 | input_variables=["question"], 119 | ) 120 | 121 | #定义邪恶chain 122 | llm = OpenAI(temperature=0) 123 | evil_qa_chain = LLMChain(llm=llm, prompt=evil_qa_prompt) 124 | 125 | #使用内置"illegal"标准 126 | principles = ConstitutionalChain.get_principles(["illegal"]) 127 | 128 | #创建宪法chain 129 | constitutional_chain = ConstitutionalChain.from_llm( 130 | chain=evil_qa_chain, 131 | constitutional_principles=principles, 132 | llm=llm, 133 | verbose=True, 134 | ) 135 | 136 | constitutional_chain.run(question="如何让青少年学会吸烟?") 137 | ``` 138 | 139 | ![img](langchain怎么确保输出符合道德期望.assets/v2-81e9dab51a9ff43f053757f487ce58bf_720w.webp) 140 | 141 | ### 直接在prompt中进行提示 142 | 143 | ```python 144 | template_str=""" 145 | 你是个好人,必须只给出好的、合乎道德的答案。\ 146 | 如果有人问你如何做违法的事情,确保你的回答合乎道德和法律。 147 | 148 | Question: {question} 149 | 150 | Ethical answer:""" 151 | 152 | good_qa_prompt = PromptTemplate( 153 | template=template_str, 154 | input_variables=["question"], 155 | ) 156 | 157 | llm = OpenAI(temperature=0) 158 | 159 | good_qa_chain = LLMChain(llm=llm, prompt=good_qa_prompt) 160 | 161 | good_qa_chain.run(question="如何教会青少年吸烟?") 162 | ``` 163 | 164 | ![img](langchain怎么确保输出符合道德期望.assets/v2-e5d301aa4517b10cb59e295d9cce466a_720w.png) 165 | 166 | **** 167 | 168 | 以上大部分摘自:https://www.zhihu.com/question/606436913/answer/3108641031 169 | 170 | **参考** 171 | 172 | > https://www.zhihu.com/question/606436913 173 | > 174 | > https://www.zhihu.com/question/606436913/answer/3108641031 175 | > 176 | > [Moderation - OpenAI API](https://platform.openai.com/docs/guides/moderation/overview) -------------------------------------------------------------------------------- /文章/langchain结构化输出背后的原理.md: -------------------------------------------------------------------------------- 1 | 先看整体代码: 2 | 3 | ```python 4 | import openai 5 | openai_api_key = "" 6 | 7 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 8 | from langchain.chat_models import ChatOpenAI 9 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 10 | chat = ChatOpenAI(model_name="gpt-3.5-turbo", 11 | openai_api_key=openai_api_key, 12 | temperature=0.0) 13 | 14 | from langchain.output_parsers import ResponseSchema 15 | from langchain.output_parsers import StructuredOutputParser 16 | 17 | # 礼物规范 18 | gift_schema = ResponseSchema(name="gift", 19 | description="Was the item purchased\ 20 | as a gift for someone else? \ 21 | Answer True if yes,\ 22 | False if not or unknown.") 23 | # 送货日期规范 24 | delivery_days_schema = ResponseSchema(name="delivery_days", 25 | description="How many days\ 26 | did it take for the product\ 27 | to arrive? If this \ 28 | information is not found,\ 29 | output -1.") 30 | # 价格值规范 31 | price_value_schema = ResponseSchema(name="price_value", 32 | description="Extract any\ 33 | sentences about the value or \ 34 | price, and output them as a \ 35 | comma separated Python list.") 36 | 37 | # 将格式规范放到一个列表里 38 | response_schemas = [gift_schema, 39 | delivery_days_schema, 40 | price_value_schema] 41 | # 构建一个StructuredOutputParser实例 42 | output_parser = StructuredOutputParser.from_response_schemas(response_schemas) 43 | # 获取将发送给LLM的格式指令 44 | format_instructions = output_parser.get_format_instructions() 45 | 46 | print(format_instructions) 47 | 48 | from langchain.prompts.chat import ChatPromptTemplate 49 | 50 | # 提示 51 | review_template_2 = """\ 52 | For the following text, extract the following information: 53 | 54 | gift: Was the item purchased as a gift for someone else? \ 55 | Answer True if yes, False if not or unknown. 56 | 57 | delivery_days: How many days did it take for the product\ 58 | to arrive? If this information is not found, output -1. 59 | 60 | price_value: Extract any sentences about the value or price,\ 61 | and output them as a comma separated Python list. 62 | 63 | text: {text} 64 | 65 | {format_instructions} 66 | """ 67 | 68 | customer_review = "I bought a wallet as a gift, it is worth $20 and is expected to be delivered on January 3, 2020." 69 | 70 | # 构建一个ChatPromptTemplate实例,用于模板的重用 71 | prompt = ChatPromptTemplate.from_template(template=review_template_2) 72 | # 将文本和格式指令作为输入变量传入 73 | messages = prompt.format_messages(text=customer_review, 74 | format_instructions=format_instructions) 75 | response = chat(messages) 76 | print(response.content) 77 | 78 | ```json { "gift": true, "delivery_days": -1, "price_value": ["it is worth $20"] } ``` 79 | ``` 80 | 81 | 定义了三个schema,然后组成列表。 82 | 83 | 先看看这两行代码: 84 | 85 | ```python 86 | # 构建一个StructuredOutputParser实例 87 | output_parser = StructuredOutputParser.from_response_schemas(response_schemas) 88 | # 获取将发送给LLM的格式指令 89 | format_instructions = output_parser.get_format_instructions() 90 | ``` 91 | 92 | StructuredOutputParser位于Langchain的ouput_parsers下,实际上在`__init__.py`里面引入的是`from langchain.output_parsers.structured import ResponseSchema, StructuredOutputParser`。 93 | 94 | ```python 95 | class ResponseSchema(BaseModel): 96 | name: str 97 | description: str 98 | type: str = "string" 99 | class StructuredOutputParser(BaseOutputParser): 100 | response_schemas: List[ResponseSchema] 101 | 102 | @classmethod 103 | def from_response_schemas( 104 | cls, response_schemas: List[ResponseSchema] 105 | ) -> StructuredOutputParser: 106 | return cls(response_schemas=response_schemas) 107 | 108 | def get_format_instructions(self, only_json: bool = False) -> str: 109 | """ 110 | Method to get the format instructions for the output parser. 111 | 112 | example: 113 | ```python 114 | from langchain.output_parsers.structured import ( 115 | StructuredOutputParser, ResponseSchema 116 | ) 117 | 118 | response_schemas = [ 119 | ResponseSchema( 120 | name="foo", 121 | description="a list of strings", 122 | type="List[string]" 123 | ), 124 | ResponseSchema( 125 | name="bar", 126 | description="a string", 127 | type="string" 128 | ), 129 | ] 130 | 131 | parser = StructuredOutputParser.from_response_schemas(response_schemas) 132 | 133 | print(parser.get_format_instructions()) 134 | 135 | output: 136 | # The output should be a markdown code snippet formatted in the following 137 | # schema, including the leading and trailing "```json" and "```": 138 | # 139 | # ```json 140 | # { 141 | # "foo": List[string] // a list of strings 142 | # "bar": string // a string 143 | # } 144 | 145 | Args: 146 | only_json (bool): If True, only the json in the markdown code snippet 147 | will be returned, without the introducing text. Defaults to False. 148 | """ 149 | schema_str = "\n".join( 150 | [_get_sub_string(schema) for schema in self.response_schemas] 151 | ) 152 | if only_json: 153 | return STRUCTURED_FORMAT_SIMPLE_INSTRUCTIONS.format(format=schema_str) 154 | else: 155 | return STRUCTURED_FORMAT_INSTRUCTIONS.format(format=schema_str) 156 | 157 | def parse(self, text: str) -> Any: 158 | expected_keys = [rs.name for rs in self.response_schemas] 159 | return parse_and_check_json_markdown(text, expected_keys) 160 | 161 | @property 162 | def _type(self) -> str: 163 | return "structured" 164 | 165 | class BaseOutputParser(BaseLLMOutputParser, ABC, Generic[T]): 166 | """Class to parse the output of an LLM call. 167 | 168 | Output parsers help structure language model responses. 169 | 170 | Example: 171 | .. code-block:: python 172 | 173 | class BooleanOutputParser(BaseOutputParser[bool]): 174 | true_val: str = "YES" 175 | false_val: str = "NO" 176 | 177 | def parse(self, text: str) -> bool: 178 | cleaned_text = text.strip().upper() 179 | if cleaned_text not in (self.true_val.upper(), self.false_val.upper()): 180 | raise OutputParserException( 181 | f"BooleanOutputParser expected output value to either be " 182 | f"{self.true_val} or {self.false_val} (case-insensitive). " 183 | f"Received {cleaned_text}." 184 | ) 185 | return cleaned_text == self.true_val.upper() 186 | 187 | @property 188 | def _type(self) -> str: 189 | return "boolean_output_parser" 190 | """ # noqa: E501 191 | 192 | def parse_result(self, result: List[Generation]) -> T: 193 | """Parse a list of candidate model Generations into a specific format. 194 | 195 | The return value is parsed from only the first Generation in the result, which 196 | is assumed to be the highest-likelihood Generation. 197 | 198 | Args: 199 | result: A list of Generations to be parsed. The Generations are assumed 200 | to be different candidate outputs for a single model input. 201 | 202 | Returns: 203 | Structured output. 204 | """ 205 | return self.parse(result[0].text) 206 | 207 | @abstractmethod 208 | def parse(self, text: str) -> T: 209 | """Parse a single string model output into some structure. 210 | 211 | Args: 212 | text: String output of language model. 213 | 214 | Returns: 215 | Structured output. 216 | """ 217 | 218 | # TODO: rename 'completion' -> 'text'. 219 | def parse_with_prompt(self, completion: str, prompt: PromptValue) -> Any: 220 | """Parse the output of an LLM call with the input prompt for context. 221 | 222 | The prompt is largely provided in the event the OutputParser wants 223 | to retry or fix the output in some way, and needs information from 224 | the prompt to do so. 225 | 226 | Args: 227 | completion: String output of language model. 228 | prompt: Input PromptValue. 229 | 230 | Returns: 231 | Structured output 232 | """ 233 | return self.parse(completion) 234 | 235 | def get_format_instructions(self) -> str: 236 | """Instructions on how the LLM output should be formatted.""" 237 | raise NotImplementedError 238 | 239 | @property 240 | def _type(self) -> str: 241 | """Return the output parser type for serialization.""" 242 | raise NotImplementedError( 243 | f"_type property is not implemented in class {self.__class__.__name__}." 244 | " This is required for serialization." 245 | ) 246 | 247 | def dict(self, **kwargs: Any) -> Dict: 248 | """Return dictionary representation of output parser.""" 249 | output_parser_dict = super().dict(**kwargs) 250 | output_parser_dict["_type"] = self._type 251 | return output_parser_dict 252 | class BaseLLMOutputParser(Serializable, ABC, Generic[T]): 253 | """Abstract base class for parsing the outputs of a model.""" 254 | 255 | @abstractmethod 256 | def parse_result(self, result: List[Generation]) -> T: 257 | """Parse a list of candidate model Generations into a specific format. 258 | 259 | Args: 260 | result: A list of Generations to be parsed. The Generations are assumed 261 | to be different candidate outputs for a single model input. 262 | 263 | Returns: 264 | Structured output. 265 | """ 266 | ``` 267 | 268 | 上述把用到的都放在一起了。BaseLLMOutputParser与一个抽象方法,返回一个生成器组成的列表。生成器是可能的不同候选输出。BaseOutputParser需要实现两个抽象方法:parse_result、parse。 269 | 270 | 回到StructuredOutputParser,调用from_response_schemas方法时,初始化其response_schemas属性。 271 | 272 | 然后调用get_format_instructions。 273 | 274 | ```python 275 | def _get_sub_string(schema: ResponseSchema) -> str: 276 | return line_template.format( 277 | name=schema.name, description=schema.description, type=schema.type 278 | ) 279 | line_template = '\t"{name}": {type} // {description}' 280 | ``` 281 | 282 | 将response_schema中的名称、类型和描述转换为字符串。再来看: 283 | 284 | ```python 285 | if only_json: 286 | return STRUCTURED_FORMAT_SIMPLE_INSTRUCTIONS.format(format=schema_str) 287 | else: 288 | return STRUCTURED_FORMAT_INSTRUCTIONS.format(format=schema_str) 289 | 290 | STRUCTURED_FORMAT_INSTRUCTIONS = """The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "```json" and "```": 291 | 292 | ```json 293 | {{ 294 | {format} 295 | }} 296 | ```""" 297 | 298 | STRUCTURED_FORMAT_SIMPLE_INSTRUCTIONS = """ 299 | ```json 300 | {{ 301 | {format} 302 | }} 303 | """ 304 | ``` 305 | 306 | 至于ChatPromptTemplate之前已经讲过,这里不作过多讲解,主要是获得prompt。 307 | 308 | 接下来: 309 | 310 | ```python 311 | messages = prompt.format_messages(text=customer_review, 312 | format_instructions=format_instructions) 313 | ``` 314 | 315 | 组合成一个完整的消息后传给chat。 316 | 317 | 最后: 318 | 319 | ```python 320 | # 结果解析为字典 321 | output_dict = output_parser.parse(response.content) 322 | print(output_dict.get('delivery_days'))`` 323 | ``` 324 | 325 | 整体流程就是这样了。 326 | 327 | -------------------------------------------------------------------------------- /文章/langchain集成GPTCache.md: -------------------------------------------------------------------------------- 1 | 一般的,针对于一些问的比较频繁的问题,我们可以将其缓存下来,以后如果遇到重复的问题,直接从缓存中读取,可以大大加快反应的速度。在langchain已经有原生的缓存功能,比如: 2 | 3 | ```python 4 | import langchain 5 | from langchain.cache import InMemoryCache 6 | langchain.llm_cache = InMemoryCache() 7 | llm = OpenAI(model_name="text-davinci-002", n=2, best_of=2) 8 | 9 | // CPU times: user 14.2 ms, sys: 4.9 ms, total: 19.1 ms 10 | // Wall time: 1.1 s 11 | llm("Tell me a joke") 12 | 13 | // CPU times: user 162 µs, sys: 7 µs, total: 169 µs 14 | // Wall time: 175 µs 15 | llm("Tell me a joke") 16 | 17 | ``` 18 | 19 | LangChain 命中缓存的条件是两个问题必须完全相同。但是在实际使用中,这种情况十分罕见,因此很难命中缓存。这也意味着,我们还有很多空间可以用来提升缓存利用率,集成 GPTCache 就是方法之一。 20 | 21 | GPTCache 首先将输入的问题转化为 embedding 向量,随后 GPTCache 会在缓存中进行向量近似搜索。获取[向量相似性](https://www.zhihu.com/search?q=向量相似性&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A3122732822})检索的结果后,GPTCache 会执行相似性评估,并将达到设置阈值的结果作为最终返回结果。大家可以通过调整阈值来调节 GPTCache 模糊搜索结果的准确性。 22 | 23 | 以下示例中在 LangChain 中集成了 GPTCache,并使用了 GPTCache 进行向量相似性检索。 24 | 25 | ```python 26 | from gptcache import Cache 27 | from gptcache.adapter.api import init_similar_cache 28 | from langchain.cache import GPTCache 29 | import hashlib 30 | def get_hashed_name(name): 31 | return hashlib.sha256(name.encode()).hexdigest() 32 | def init_gptcache(cache_obj: Cache, llm: str): 33 | hashed_llm = get_hashed_name(llm) 34 | init_similar_cache(cache_obj=cache_obj, data_dir=f"similar_cache_{hashed_llm}") 35 | langchain.llm_cache = GPTCache(init_gptcache) 36 | 37 | # The first time, it is not yet in cache, so it should take longer 38 | # CPU times: user 1.42 s, sys: 279 ms, total: 1.7 s 39 | # Wall time: 8.44 s 40 | llm("Tell me a joke") 41 | 42 | # This is an exact match, so it finds it in the cache 43 | # CPU times: user 866 ms, sys: 20 ms, total: 886 ms 44 | # Wall time: 226 ms 45 | llm("Tell me a joke") 46 | 47 | # This is not an exact match, but semantically within distance so it hits! 48 | # CPU times: user 853 ms, sys: 14.8 ms, total: 868 ms 49 | # Wall time: 224 ms 50 | llm("Tell me joke") 51 | ``` 52 | 53 | 作者:Zilliz 54 | 链接:https://www.zhihu.com/question/606436913/answer/3122732822 55 | 来源:知乎 56 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -------------------------------------------------------------------------------- /文章/langchain集成Mivus向量数据库.md: -------------------------------------------------------------------------------- 1 | ```python 2 | import os 3 | from dotenv import load_dotenv 4 | import openai 5 | load_dotenv() 6 | openai.api_key = os.getenv("OPENAI_API_KEY") 7 | from langchain.llms import OpenAI 8 | davinci = OpenAI(model_name="text-davinci-003") 9 | from milvus import default_server 10 | default_server.start() 11 | 12 | from langchain.embeddings.openai import OpenAIEmbeddings 13 | from langchain.text_splitter import CharacterTextSplitter 14 | from langchain.vectorstores import Milvus 15 | from langchain.document_loaders import UnstructuredURLLoader 16 | from langchain.chains import RetrievalQA 17 | 18 | loader = UnstructuredURLLoader(urls=['https://zilliz.com/doc/about_zilliz_cloud']) 19 | documents = loader.load() 20 | text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) 21 | docs = text_splitter.split_documents(documents) 22 | embeddings = OpenAIEmbeddings() 23 | vector_db = Milvus.from_documents( 24 | docs, 25 | embeddings, 26 | connection_args={"host": "127.0.0.1", "port": default_server.listen_port}, 27 | ) 28 | qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=vector_db.as_retriever()) 29 | query = "What is Zilliz Cloud?" 30 | qa.run(query) 31 | default_server.stop() 32 | ``` 33 | 34 | 现在可以开始学习如何查询文档了。这次从 LangChain 导入了很多内容,需要 OpenAI Embeddings、文本字符拆分器、Milvus 向量数据库、加载器和问答检索链。 35 | 36 | - 首先,设置一个加载器并加载 urls 链接中的内容。本例中,将加载 Zilliz Cloud 介绍的文档,即加载链接 'https://zilliz.com/doc/about_zilliz_cloud'。 37 | 38 | - 其次,将文档拆分并将其存储为 LangChain 中的一组文档。 39 | - 接着,设置 Milvus 向量数据库。在本例中,我们为刚才通过 `UnstructuredURLLoader`和 `CharacterTextSplitter` 获取的文档数据创建了一个 Milvus 集合(collection)。同时,还使用了 OpenAI Embeddings 将文本转化为 embedding 向量。 40 | - 准备好向量数据库后,可以使用 `RetrievalQA` 通过向量数据库查询文档。使用 `stuff` 类型的链,并选择 OpenAI 作为 LLM,Milvus 向量数据库作为检索器。 41 | 42 | 接下来,大家就可以查询啦!通过 run 运行查询语句。当然,最后别忘了关闭向量数据库。 43 | 44 | 原文链接:https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&mid=2247497707&idx=1&sn=5a8f92cc48ed8f0dae9c1c5739ecc22a&chksm=fa515653cd26df4578d2b5a07e36324698a92ddc86e546cd64ef3294ffec3afd98d8e3d12c23&scene=21#wechat_redirect -------------------------------------------------------------------------------- /文章/pydantic中config的一些配置.md: -------------------------------------------------------------------------------- 1 | Pydantic 中的 `Config` 类是用于配置 Pydantic 模型的属性的类。通过在 Pydantic 模型中定义 `Config` 类,可以控制模型的行为,例如序列化和反序列化选项、JSON 解析选项、ORM 集成等等。下面是 Pydantic 中 `Config` 类的常用配置选项: 2 | 3 | - `allow_population_by_field_name`: 默认情况下,Pydantic 模型是通过参数名称来构建的。如果将此选项设置为 True,则可以通过参数名称和字段名称来构建模型。 4 | - `anystr_strip_whitespace`: 如果此选项为 True,则将使用 `strip()` 方法从任何字符串类型的值中删除前导和尾随空格。 5 | - `arbitrary_types_allowed`: 如果此选项为 True,则 Pydantic 将接受任何类型的值,并将其视为有效的。否则,只有预定义的类型(例如 str、int、float 等)才会被接受。 6 | - `extra`: 控制是否允许额外的字段。如果设置为 `'ignore'`,则忽略额外的字段;如果设置为 `'allow'`,则接受额外的字段;如果设置为 `'forbid'`,则拒绝额外的字段。 7 | - `json_encoders`: 将自定义编码器添加到 JSON 编码器列表中。 8 | - `json_loads`: 自定义 JSON 解码器。 9 | - `json_dumps`: 自定义 JSON 编码器。 10 | - `keep_untouched`: 如果此选项为 True,则保留原始字典中的任何未修改的字段,而不是在解析后丢弃它们。 11 | - `orm_mode`: 如果此选项为 True,则 Pydantic 将允许从 ORM 模型(例如 SQLAlchemy 模型)中加载数据,并将其转换为适当的类型。默认情况下,Pydantic 假定所有属性都是字符串。 12 | - `validate_assignment`: 如果此选项为 True,则在将值分配给属性时执行验证。如果设置为 False,则不执行验证。 13 | - `use_enum_values`: 如果此选项为 True,则使用枚举值而不是枚举名称来序列化和反序列化枚举类型的属性。默认情况下,Pydantic 使用枚举名称而不是值。 14 | 15 | 下面是一个示例,展示了如何使用 Pydantic 中的 `Config` 类来配置模型的行为: 16 | 17 | ```python 18 | from enum import Enum 19 | from typing import List 20 | from pydantic import BaseModel 21 | 22 | class Color(str, Enum): 23 | RED = 'red' 24 | GREEN = 'green' 25 | BLUE = 'blue' 26 | 27 | class Item(BaseModel): 28 | name: str 29 | price: float 30 | is_offer: bool = None 31 | corlor: str 32 | 33 | class Config: 34 | orm_mode = False 35 | 36 | class User(BaseModel): 37 | id: int 38 | username: str 39 | email: str 40 | password: str 41 | items: List[Item] = [] 42 | 43 | class Config: 44 | orm_mode = False 45 | use_enum_values = True 46 | allow_population_by_field_name = True 47 | extra = 'ignore' 48 | 49 | item = Item(name="python入门", price=18, is_offer=False, corlor=Color.BLUE) 50 | user = User(id=1, username="张三", email="23232323@qq.com", password="123", items=[item]) 51 | 52 | print(user) 53 | 54 | """ 55 | id=1 username='张三' email='23232323@qq.com' password='123' items=[Item(name='python入门', price=18.0, is_offer=False, corlor='blue')] 56 | """ 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /文章/pydantic中的Serializable和root_validator.md: -------------------------------------------------------------------------------- 1 | Serializable和root_validator在很多时候都会用到,这里讲一下. 2 | 3 | # BaseModel 4 | 5 | 当使用 Pydantic 进行数据验证时,可以使用 BaseModel 类来定义数据模型,然后使用该模型对数据进行验证和转换。BaseModel 是 Pydantic 中的一个基类,所有的数据模型都应该继承自该类。 6 | 7 | 以下是一个使用 Pydantic 定义数据模型的示例: 8 | 9 | ```python 10 | from pydantic import BaseModel 11 | 12 | class Person(BaseModel): 13 | name: str 14 | age: int 15 | is_student: bool = False 16 | ``` 17 | 18 | 在该示例中,我们定义了一个名为 Person 的数据模型,该模型包含三个字段:name、age 和 is_student。每个字段都使用类型注释指定了其类型。在这种情况下,name 字段的类型为 str,age 字段的类型为 int,is_student 字段的类型为 bool。我们还在 is_student 字段上指定了一个默认值 False。 19 | 20 | 使用上述模型进行数据验证和转换的示例如下: 21 | 22 | ```python 23 | # 测试数据 24 | data = { 25 | "name": "Alice", 26 | "age": 25, 27 | "is_student": True 28 | } 29 | 30 | # 将数据转换为 Person 对象 31 | person = Person(**data) 32 | 33 | # 打印 Person 对象的属性 34 | print(person.name) 35 | print(person.age) 36 | print(person.is_student) 37 | ``` 38 | 39 | 在该示例中,我们创建了一个字典,其中包含符合 Person 模型的数据。然后,我们使用 Person(**data) 将该字典转换为 Person 对象。最后,我们打印了 Person 对象的属性,以确保转换成功。 40 | 41 | 如果将错误的数据传递给 Person 构造函数,例如: 42 | 43 | ```python 44 | data = { 45 | "name": "Alice", 46 | "age": "twenty", 47 | "is_student": "yes" 48 | } 49 | 50 | person = Person(**data) 51 | ``` 52 | 53 | 那么 Pydantic 将引发 ValidationError 异常,告诉我们哪些字段无效,并提供有关验证错误的详细信息。 54 | 55 | # root_validator 56 | 57 | 在Pydantic中,root_validator是一个修饰器函数,用于定义对模型的根级别验证。在模型中,根级别验证通常用于对多个字段进行联合验证,或者对字段之间的关系进行验证。 58 | 59 | 下面是一个使用root_validator的示例: 60 | 61 | ```python 62 | from pydantic import BaseModel, validator, root_validator 63 | 64 | class User(BaseModel): 65 | name: str 66 | age: int 67 | 68 | @root_validator 69 | def check_age_name_consistency(cls, values): 70 | age = values.get('age') 71 | name = values.get('name') 72 | if age and name: 73 | if len(name) > age: 74 | raise ValueError('Name cannot be longer than age') 75 | return values 76 | ``` 77 | 78 | 在上面的示例中,check_age_name_consistency是一个使用root_validator修饰的方法。它接收一个values参数,该参数包含了模型中所有字段的值。在这个方法中,我们检查age和name字段的一致性,如果name字段的长度大于age字段的值,则会引发一个ValueError异常。 79 | 80 | 在root_validator方法中,我们可以对任意数量的字段进行复杂的验证逻辑。如果验证失败,则可以引发异常,否则可以返回更新后的值字典。 81 | 82 | 需要注意的是,root_validator方法的返回值必须是一个字典,其中包含所有验证后的字段值。如果返回的字典中不包含某个字段,则该字段将被设置为默认值或None。 83 | 84 | 当我们定义好一个带有`root_validator`方法的Pydantic模型之后,我们就可以实例化该类并使用它来验证输入数据了。 85 | 86 | 下面是一个示例,展示了如何使用上面定义的`User`类来验证输入数据: 87 | 88 | ```python 89 | user_data = {'name': 'Alice', 'age': 25} 90 | user = User(**user_data) 91 | print(user) 92 | ``` 93 | 94 | 在这个示例中,我们首先创建了一个包含`name`和`age`字段的字典,然后使用字典解包方式将其作为参数传递给`User`类的构造函数。在构造函数中,Pydantic会验证输入数据,并根据验证结果创建一个`User`对象。 95 | 96 | 如果输入数据不符合模型定义或`root_validator`方法中的验证逻辑,Pydantic会引发一个`ValidationError`异常。 97 | 98 | 下面是一个示例,展示了如何处理这种异常: 99 | 100 | ```python 101 | from pydantic import ValidationError 102 | user_data = {'name': 'Alice', 'age': 1} 103 | try: 104 | user = User(**user_data) 105 | except ValidationError as e: 106 | print(e) 107 | 108 | """ 109 | 1 validation error for User 110 | __root__ 111 | Name cannot be longer than age (type=value_error) 112 | """ 113 | ``` 114 | 115 | 需要注意的是,Pydantic还支持在模型中定义多个`root_validator`方法,每个方法可以对不同的字段进行验证。在这种情况下,Pydantic将按照方法定义的顺序依次调用这些方法进行验证。如果某个方法引发了异常,Pydantic会停止验证并立即引发异常。 116 | 117 | # Serializable 118 | 119 | 在 Pydantic 中,Serializable 是一个基类,可用于定义可序列化的数据模型。它提供了将数据转换为字典或 JSON 字符串的方法,以及将字典或 JSON 字符串转换回数据模型的方法。 120 | 121 | 以下是一个使用 Pydantic 的 Serializable 基类定义可序列化数据模型的示例: 122 | 123 | ```python 124 | from pydantic import BaseModel, Serializable 125 | 126 | class Person(BaseModel, Serializable): 127 | name: str 128 | age: int 129 | is_student: bool = False 130 | 131 | def to_dict(self): 132 | return self.dict() 133 | 134 | @classmethod 135 | def from_dict(cls, data): 136 | return cls(**data) 137 | ``` 138 | 139 | 在该示例中,我们定义了一个名为 Person 的数据模型,该模型继承自 BaseModel 和 Serializable 类。我们还定义了 to_dict() 和 from_dict() 两个方法,用于将数据转换为字典或从字典转换回数据模型。 140 | 141 | 使用上述模型进行数据序列化和反序列化的示例如下: 142 | 143 | ```python 144 | # 创建 Person 对象 145 | person = Person(name="Alice", age=25, is_student=True) 146 | 147 | # 将 Person 对象转换为字典 148 | data_dict = person.to_dict() 149 | 150 | # 将字典转换为 Person 对象 151 | person2 = Person.from_dict(data_dict) 152 | 153 | # 打印 Person 对象的属性 154 | print(person2.name) 155 | print(person2.age) 156 | print(person2.is_student) 157 | ``` 158 | 159 | 在该示例中,我们创建了一个 Person 对象,然后使用 to_dict() 将其转换为字典,并使用 from_dict() 将该字典转换回 Person 对象。最后,我们打印了 Person 对象的属性,以确保转换成功。 160 | 161 | 此外,Serializable 还提供了 to_json() 和 from_json() 方法,用于将数据转换为 JSON 字符串或从 JSON 字符串转换回数据模型。这些方法与 to_dict() 和 from_dict() 方法类似,只是将字典转换为 JSON 字符串或将 JSON 字符串转换为字典而已。 -------------------------------------------------------------------------------- /文章/python中args和kwargs.md: -------------------------------------------------------------------------------- 1 | - *args 表示任何多个无名参数, 他本质上是一个 tuple。 2 | - ** kwargs 表示关键字参数, 它本质上是一个 dict。 3 | - 同时使用时必须要求 *args 参数列要在** kwargs 前面。 4 | 5 | 直接看一些例子: 6 | 7 | ```python 8 | a = [1,2,3] 9 | b = [*a,4,5,6] 10 | b 11 | # ----------------- 输出结果 ----------------- 12 | # [1, 2, 3, 4, 5, 6] 13 | # ----------------- 总结 ----------------- 14 | # 将a的内容移入(解包)到新列表b中。 15 | 16 | def print_func(*args): 17 | print(type(args)) 18 | print(args) 19 | print_func(1,2,'python希望社',[]) 20 | 21 | """ 22 | 23 | (1, 2, 'python希望社', []) 24 | """ 25 | 26 | def print_func(x,y,*args): 27 | print(type(x)) 28 | print(x) 29 | print(y) 30 | print(type(args)) 31 | print(args) 32 | print_func(1,2,'python希望社',[]) 33 | 34 | """ 35 | 36 | 1 37 | 2 38 | 39 | ('python希望社', []) 40 | """ 41 | 42 | def print_func(**kwargs): 43 | print(type(kwargs)) 44 | print(kwargs) 45 | 46 | print_func(a=1, b=2, c='呵呵哒', d=[]) 47 | 48 | """ 49 | 50 | {'a': 1, 'b': 2, 'c': '呵呵哒', 'd': []} 51 | """ 52 | 53 | def print_func(x, *args, **kwargs): 54 | print(x) 55 | print(args) 56 | print(kwargs) 57 | 58 | print_func(1, 2, 3, 4, y=1, a=2, b=3, c=4) 59 | """ 60 | 1 61 | (2, 3, 4) 62 | {'y': 1, 'a': 2, 'b': 3, 'c': 4} 63 | 64 | """ 65 | ``` 66 | 67 | 到这,你基本上就了解它们是什么了。 68 | 69 | 以上内容摘自:[(29条消息) 【Python】`*args` 和 `**kwargs`的用法【最全详解】_春风惹人醉的博客-CSDN博客](https://blog.csdn.net/GODSuner/article/details/117961990) -------------------------------------------------------------------------------- /文章/python中functools的partial的用法.md: -------------------------------------------------------------------------------- 1 | 以下内容参考: 2 | 3 | https://zhuanlan.zhihu.com/p/376012376 4 | 5 | https://link.zhihu.com/?target=https%3A//www.tuicool.com/articles/ymUjuqY 6 | 7 | 一句话总结:根据现有函数返回一个新的函数,举个简单的例子: 8 | 9 | ```python 10 | from functools import partial 11 | def addone(x): 12 | return x+1 13 | 14 | add = partial(addone, 3) 15 | print(add()) 16 | # 输出4 17 | 18 | # 如果有多个参数 19 | def get_info(name="tom", age=18): 20 | return (name, age) 21 | 22 | get_infor = partial(get_info, age=28) 23 | print(get_infor(name="jack")) 24 | ``` 25 | 26 | partial的一个实际作用是**作为回调函数**,假设我们本身的参数就是一个函数名。比如: 27 | 28 | ```python 29 | def show(name, age): 30 | print("name {} age {}".format(name, age)) 31 | 32 | def test(callback): 33 | print("这里做一些事情") 34 | callback() 35 | 36 | # 假设我们想用callback调用show呢,怎么传入name和age 37 | # 可以这么改写 38 | def test2(callback, name, age): 39 | print("这里做一些事情") 40 | callback(name, age) 41 | 42 | # 要是我们还想将callback调用其它的函数呢 43 | # 可以这么写 44 | 45 | showp = partial(show, name="tom", age=18) 46 | 47 | test(showp) 48 | 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /文章/python中inspect的signature用法.md: -------------------------------------------------------------------------------- 1 | 用法: 2 | 3 | `inspect.signature(callable, *, follow_wrapped=True, globals=None, locals=None, eval_str=False)` 4 | 5 | 返回给定 `callable` 的 [`Signature`](https://vimsky.com/cache/index.php?source=https%3A//docs.python.org/3/library/inspect.html%23inspect.Signature) 对象: 6 | 7 | 看一个具体的例子: 8 | 9 | ```python 10 | >>> from inspect import signature 11 | >>> def foo(a, *, b:int, **kwargs): 12 | ... pass 13 | 14 | >>> sig = signature(foo) 15 | 16 | >>> str(sig) 17 | '(a, *, b:int, **kwargs)' 18 | 19 | >>> str(sig.parameters['b']) 20 | 'b:int' 21 | 22 | >>> sig.parameters['b'].annotation 23 | 24 | ``` 25 | 26 | 接受广泛的 Python 可调用对象,从普通函数和类到 [`functools.partial()`](https://vimsky.com/examples/usage/python-functools.partial-py.html) 对象。 27 | 28 | functools.partial()我们之前已经讲解过。 -------------------------------------------------------------------------------- /文章/python中常用的一些魔术方法.md: -------------------------------------------------------------------------------- 1 | 在Python 中,魔术方法指的是以双下划线 `__` 开头和结尾的特殊方法。这些方法提供了一种在类中定义特殊行为的方式,可以让我们自定义 Python 中的内置行为。下面是一些常用的魔术方法: 2 | 3 | 1. `__init__`: 初始化方法,用于创建类实例时初始化对象的属性。 4 | 2. `__str__`: 字符串表示方法,用于返回类实例的字符串表示。 5 | 3. `__repr__`: 表示方法,一般用于打印实例的一些属性,如果没有实现`__str__`,则调用这个。 6 | 4. `__eq__`: 等于方法,用于判断两个对象是否相等。 7 | 5. `__lt__`: 小于方法,用于比较两个对象的大小。 8 | 6. `__len__`: 长度方法,用于返回对象的长度。 9 | 7. `__getitem__`: 索引方法,用于支持类实例的索引操作。 10 | 8. `__setitem__`: 赋值方法,用于支持类实例的赋值操作。 11 | 9. `__delitem__`: 删除方法,用于支持类实例的删除操作。 12 | 10. `__call__`: 调用方法,用于支持将类实例作为函数调用。 13 | 11. `__enter__` 和 `__exit__`: 上下文管理器方法,用于支持 `with` 语句。 14 | 12. `__getattr__`: 获取属性方法,用于在访问不存在的属性时触发。 15 | 13. `__setattr__`: 设置属性方法,用于在设置属性时触发。 16 | 14. `__delattr__`: 删除属性方法,用于在删除属性时触发。 17 | 15. `__iter__`: 迭代方法,用于支持类实例的迭代操作。 18 | 16. `__next__`: 下一个方法,用于支持类实例的迭代操作。 19 | 20 | **** 21 | 22 | 17、`__class__ `和` __module`__ :`__class__ `和` __module`__ 都是 Python 中的魔术属性,用于获取对象所属的类和模块。 23 | 24 | `__class__ `属性用于获取对象所属的类,它是一个只读属性。例如,如果有一个对象 obj,可以使用 `obj.__class__ `来获取它所属的类。这个属性通常用于实现类似于装饰器或者代理的功能,可以在运行时动态地修改类的行为。 25 | 26 | `__module__ `属性用于获取对象所属的模块名称,它也是一个只读属性。例如,如果有一个对象 obj,可以使用 `obj.__module__ `来获取它所属的模块名称。这个属性通常用于在不同的模块之间共享对象,可以根据模块名称动态地引用对象。 27 | 28 | 下面是一个示例,展示了如何使用 __class__ 和 __module__ 属性来获取对象所属的类和模块: 29 | 30 | ```python 31 | class MyClass: 32 | pass 33 | obj = MyClass() 34 | 35 | print(obj.__class__) # 输出: 36 | print(obj.__module__) # 输出:__main__ 37 | ``` 38 | 39 | 在上面的示例中,我们定义了一个 MyClass 类,并创建了一个对象 obj。通过调用` obj.__class__` 和 `obj.__module__ `属性,我们可以获取对象 obj 所属的类和模块。在这个例子中,`obj.__class`__ 的返回值是 ``,表示 obj 属于 MyClass 类;`obj.__module`__ 的返回值是` __main`__,表示 obj 所在的模块是当前模块。 40 | 41 | 需要注意的是,访问魔术属性通常是不必要的,因为 Python 提供了许多内置函数和语法糖来获取对象的类和模块。例如,可以使用 type(obj) 来获取对象的类,可以使用 `obj.__class__.__name__` 来获取对象所属的类名,可以使用 `__name__ `属性来获取模块名称。 42 | 43 | 44 | 45 | 这些魔术方法可以让我们自定义类的行为,使其与 Python 的内置类型一样灵活。需要注意的是,不是所有的魔术方法都需要在类中实现,只有在需要自定义特殊行为时才需要实现相应的魔术方法。 -------------------------------------------------------------------------------- /文章/python的typing常用的类型.md: -------------------------------------------------------------------------------- 1 | 先了解一些小东西:主要是typing中的一些数据类型 2 | 3 | - Any:是一个特殊的类型注解,表示可以是任何类型。使用 Any 类型注解的变量可以接受任何类型的值,但是这种方式会失去静态类型检查的优势,因此应该尽量避免使用。 4 | - TypeDcit:在 Python 3.8 中,引入了 typing 模块中的 TypedDict 类型注解。TypedDict 可以被用来描述键值对的字典类型,该类型中的键需要指定名称和类型注解,而值的类型可以是任意的。 5 | - Literal: 是一个泛型类型注解,用于表示只能取特定值中的一个的字面量类型。例如 Literal[True, False] 表示只能取 True 或 False 中的一个值。 6 | - Union:是一个泛型类型注解,用于表示多个类型中的任意一个。可以将多个类型作为 Union 的参数传入,例如 Union[str, int] 表示可以是字符串类型或整型类型中的任意一个。 7 | - cast: 是 Python 中的一个内置函数,用于将一个值强制转换成指定的类型。常见的用法是在类型注解中使用, 8 | - `Optional`:是 `typing` 模块中的一个泛型类型,用于表示可选类型。它用于指示一个变量或函数参数可以是指定的类型,也可以是 `None`。 9 | - Mapping:是一个泛型类型提示,用于描述一个键值对类型的映射。它可以被用作参数类型提示、返回值类型提示或变量类型提示。 10 | - `Callable`:是一个泛型类型提示,用于描述一个可调用对象的类型。它可以被用作参数类型提示、返回值类型提示或变量类型提示。`Callable[[int], str]`,表示输入为int,输出为str的可调用函数。 -------------------------------------------------------------------------------- /文章/官方文档学习/初步认识.md: -------------------------------------------------------------------------------- 1 | ## github上langchain的介绍 2 | 3 | 先看看github上面的介绍: 4 | 5 | 大型语言模型(LLM)正在成为一种变革性的技术,使开发者能够建立他们以前无法建立的应用程序。然而,孤立地使用这些LLM往往不足以创建一个真正强大的应用程序--当你能将它们与其他计算或知识来源相结合时,真正的力量才会出现。 6 | 7 | 这个库的目的是协助开发这些类型的应用。这些应用程序的常见例子包括: 8 | 9 | - 对特定文件的问题回答 10 | - 聊天机器人 11 | - 代理 12 | 13 | ## 文档 14 | - 入门(安装,设置环境,简单的例子)。 15 | - 操作实例(演示、集成、辅助功能) 16 | - 参考(完整的API文档) 17 | - 资源(核心概念的高级解释) 18 | 19 | 20 | ## 这能帮助什么? 21 | LangChain旨在帮助六个主要领域。这些领域按复杂程度递增: 22 | 23 | ### LLMs和提示: 24 | 25 | 这包括提示管理、提示优化、所有LLM的通用接口,以及使用LLM的通用工具。 26 | 27 | ### 链 28 | 29 | 链超越了单一的LLM调用,涉及到一系列的调用(无论是对LLM还是不同的工具)。LangChain为链提供了一个标准接口,与其他工具进行了大量的集成,并为常见的应用提供了端到端的链。 30 | 31 | ### 数据增强的生成 32 | 33 | 数据增强生成涉及特定类型的链,它首先与外部数据源交互以获取数据用于生成步骤。这方面的例子包括对长篇文字的总结和对特定数据源的提问/回答。 34 | 35 | ### 代理 36 | 37 | 代理涉及到LLM决定采取哪些行动,采取该行动,看到一个观察结果,并重复该行动直到完成。LangChain提供了一个标准的代理接口,提供了一些可供选择的代理,以及端到端代理的例子。 38 | 39 | ### 内存 40 | 41 | 内存指的是在链/代理的调用之间持续保持状态。LangChain提供了内存的标准接口、内存实现的集合,以及使用内存的链/代理的例子。 42 | 43 | ### 评估 44 | 45 | ``[BETA]`` 生成式模型是出了名的难以用传统的指标来评估。评估它们的一个新方法是使用语言模型本身来进行评估。LangChain提供了一些提示/链来协助这个工作。 46 | 47 | 关于这些概念的更多信息,请看我们的完整文档。 48 | 49 | ### 贡献 50 | 作为一个快速发展领域的开源项目,我们对贡献极为开放,无论是新功能、改进的基础设施还是更好的文档。 51 | 52 | ## github上介绍自我总结 53 | 看完github上面的介绍,我们有一个初步的认识了,假设现在我问你langchain是干嘛用的,你能怎么回答呢? 54 | - 首先,langchain是一个开源框架,旨在方便使用LLM进行计算和开发应用,比如基于知识的问答、聊天机器人、使用代理进行各种任务。 55 | - 其次,它主要应用在六大领域里面: 56 | - 管理和优化prompt。不同的任务使用不同prompt,如何去管理和优化这些prompt是langchain的主要功能之一。 57 | - 链,初步理解为一个具体任务中不同子任务之间的一个调用。 58 | - 数据增强的生成,如名。 59 | - 代理,代理的话,初步认识就是根据不同的指令采取不同的行动,直到整个流程完成为止。 60 | - 评估,如名。 61 | - 内存:在整个流程中帮我们管理一些中间状态。 62 | - 最后,可以理解为:在一个流程的整个生命周期中,管理和优化prompt,根据prompt使用不同的代理进行不同的动作,在这期间使用内存管理中间的一些状态,然后使用链将不同代理之间进行连接起来,最终形成一个闭环。 63 | 64 | 这就是初步看完github上面的一些介绍所得到的总结,当然可能会有所错误,我们继续慢慢看下去。 65 | 66 | 接下来我们去看看它的官方文档,看看有什么说法没有。 67 | 68 | ## 官方文档介绍 69 | 70 | 官方文档里面也有一小部分介绍: 71 | LangChain是一个框架,用于开发由语言模型驱动的应用程序。它使应用程序具有以下特点: 72 | - 数据感知:将语言模型与其他数据源连接起来。 73 | - 代理性:允许语言模型与环境互动。 74 | 75 | 多了一个数据感知,也就是可以处理不同的数据源。这里的代理性是允许语言模型和环境交互,我们之前的理解是使用代理能够通过指令执行不同的任务,意思应该相差不大。 76 | 77 | LangChain的主要价值道具是: 78 | - 组件:用于处理语言模型的抽象概念,以及每个抽象概念的实现集合。无论你是否使用LangChain框架的其他部分,组件都是模块化的,易于使用。 79 | - 现成的链:用于完成特定高级任务的组件的结构化组合。现成的链使人容易上手。对于更复杂的应用和细微的用例,组件使得定制现有链或建立新链变得容易。 80 | 81 | 链,我们之前理解的差不多,也就是将一个应用的整个流程连接起来。额外的,这里多了一个组件概念。可以这么理解,链是有模块化的组件构成的,而一个应用程序则是由许多这种链组成的。langchain给我们提供了许多现有的链组成的高级应用程序。而我们也可以使用自定义的链来进行相关应用程序的开发。 82 | 83 | ## 组件 84 | 既然说到了组件,那我们来看看都有哪些组件。LangChain为以下模块提供标准的、可扩展的接口和外部集成,这些模块从最不复杂到最复杂排列: 85 | - model I/O:语言模型接口 86 | - data connection:与特定任务的数据接口 87 | - chains:构建调用序列 88 | - agents:给定高级指令,让链选择使用哪些工具 89 | - memory:在一个链的运行之间保持应用状态 90 | - callbacks:记录并流式传输任何链的中间步骤 91 | 92 | 93 | ## 最终总结 94 | 95 | 到这里为止,我们应该对langchain有了一个初步的认识,所以langchain是什么呢,我们简要进行一个回答: 96 | - langchain是一个开源框架,它旨在系统化LLM进行计算和构建应用程序的整个流程。(整体介绍) 97 | - 它允许我们使用不同的数据源。它可以帮助我们管理和优化相关的prompt。(从输入的角度) 98 | - 一个应用程序可以表示为多个链的组成。通过代理,可以让链选择不同的工具、LLM。而链由不同的模块化组件构成。我们可以自定义这些组件来实现特定的需求。(从具体结构出发) 99 | - 总而言之,langchain可以帮助我们快速开发出应用程序,比如知识库的问答,聊天机器人,处理传统自然语言处理中的各种任务等。(从最终的应用场景出发) 100 | 101 | 由于这只是一个初步的了解,可能有一些不完备的地方,欢迎指出。接下来可能先去了解一下langchain中的六大组件,然后在结合不同的案例加深理解、 -------------------------------------------------------------------------------- /文章/官方文档学习/初步认识.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/初步认识.pdf -------------------------------------------------------------------------------- /文章/官方文档学习/组件-代理.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-代理.pdf -------------------------------------------------------------------------------- /文章/官方文档学习/组件-内存.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-内存.pdf -------------------------------------------------------------------------------- /文章/官方文档学习/组件-回调.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-回调.pdf -------------------------------------------------------------------------------- /文章/官方文档学习/组件-数据连接.assets/dfe7b878-3c1d-4123-ae5c-cd17758a29b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-数据连接.assets/dfe7b878-3c1d-4123-ae5c-cd17758a29b1.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-数据连接.assets/f03e9b67-b107-4225-8920-71f8b7e0ac76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-数据连接.assets/f03e9b67-b107-4225-8920-71f8b7e0ac76.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-数据连接.assets/f221f652-1c2f-45f7-9a15-222c16106770.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-数据连接.assets/f221f652-1c2f-45f7-9a15-222c16106770.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-数据连接.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-数据连接.pdf -------------------------------------------------------------------------------- /文章/官方文档学习/组件-模型IO.assets/9ed90e22-7497-4460-81ce-d1f703e4b9e7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-模型IO.assets/9ed90e22-7497-4460-81ce-d1f703e4b9e7.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-模型IO.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-模型IO.pdf -------------------------------------------------------------------------------- /文章/官方文档学习/组件-链.assets/36e9dd56-7066-4d54-897e-f38c756f8e5e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-链.assets/36e9dd56-7066-4d54-897e-f38c756f8e5e.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-链.assets/7de280fc-389b-4081-af01-ad5d0685d7b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-链.assets/7de280fc-389b-4081-af01-ad5d0685d7b1.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-链.assets/db684c44-a7cf-452e-978d-14c119b8bbe7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-链.assets/db684c44-a7cf-452e-978d-14c119b8bbe7.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-链.assets/e9dbda41-d4d7-474d-8fbd-84c0e50808ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-链.assets/e9dbda41-d4d7-474d-8fbd-84c0e50808ad.png -------------------------------------------------------------------------------- /文章/官方文档学习/组件-链.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/文章/官方文档学习/组件-链.pdf -------------------------------------------------------------------------------- /英文例子/langchain中使用不同的链.md: -------------------------------------------------------------------------------- 1 | **链(Chain)是LangChain中最关键的构建模块**。 2 | 3 | 除了将 LLM 与提示结合在一起,还可以通过组合多个链,对文本或其他数据执行一系列的操作。 4 | 5 | LangChain提供了多种可用的链类型: 6 | 7 | | 类型 | 场景 | 8 | | ----------------------------------- | ---------------------------------- | 9 | | LLM链(LLMChain) | 将LLM和提示结合在一起 | 10 | | 简单顺序链(SimpleSequentialChain) | 只需要一个输入并且只返回一个输出 | 11 | | 常规顺序链(SequentialChain) | 有多个输入或多个输出 | 12 | | 路由链(RouterChain) | 根据输入的具体内容路由到不同的子链 | 13 | 14 | # LLMChain 15 | 16 | ```python 17 | import openai 18 | openai_api_key = "" 19 | 20 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 21 | from langchain.chat_models import ChatOpenAI 22 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 23 | # 用一个比较高的temperature值以获得一些更有意思的结果 24 | llm = ChatOpenAI(model_name="gpt-3.5-turbo", 25 | openai_api_key=openai_api_key, 26 | temperature=0.9) 27 | 28 | from langchain.chat_models import ChatOpenAI 29 | from langchain.prompts import ChatPromptTemplate 30 | from langchain.chains import LLMChain 31 | 32 | 33 | # 接收一个名为“product”的变量,要求LLM生成描述生产该产品的公司的最佳名称 34 | prompt = ChatPromptTemplate.from_template( 35 | "What is the best name to describe \ 36 | a company that makes {product}?" 37 | ) 38 | 39 | chain = LLMChain(llm=llm, prompt=prompt) 40 | product = "Queen Size Sheet Set" 41 | print(chain.run(product)) 42 | 43 | ``` 44 | 45 | # SimpleSequentialChain 46 | 47 | img 48 | 49 | 当我们的子链只需要一个输入并且只返回一个输出时,简单顺序链很有效。 50 | 51 | ```python 52 | import openai 53 | openai_api_key = "" 54 | 55 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 56 | from langchain.chat_models import ChatOpenAI 57 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 58 | # 用一个比较高的temperature值以获得一些更有意思的结果 59 | llm = ChatOpenAI(model_name="gpt-3.5-turbo", 60 | openai_api_key=openai_api_key, 61 | temperature=0.9) 62 | 63 | from langchain.chat_models import ChatOpenAI 64 | from langchain.prompts import ChatPromptTemplate 65 | from langchain.chains import LLMChain, SimpleSequentialChain 66 | 67 | 68 | # 接收一个名为“product”的变量,要求LLM生成描述生产该产品的公司的最佳名称 69 | first_prompt = ChatPromptTemplate.from_template( 70 | "What is the best name to describe \ 71 | a company that makes {product}?" 72 | ) 73 | 74 | # 第一个链 75 | chain_one = LLMChain(llm=llm, prompt=first_prompt) 76 | 77 | # 提示模板2:获取公司名称,然后输出该公司的 20 字描述 78 | second_prompt = ChatPromptTemplate.from_template( 79 | "Write a 20 words description for the following \ 80 | company:{company_name}" 81 | ) 82 | # 第二个链 83 | chain_two = LLMChain(llm=llm, prompt=second_prompt) 84 | 85 | # 第一个链的输出将传递到第二个链 86 | overall_simple_chain = SimpleSequentialChain(chains=[chain_one, chain_two], 87 | verbose=True) 88 | 89 | product = "Queen Size Sheet Set" 90 | print(overall_simple_chain.run(product)) 91 | 92 | """ 93 | 94 | 95 | > Entering new chain... 96 | Royal Comfort Linens 97 | Royal Comfort Linens is a premium bedding company offering luxurious and comfortable linens for a restful and stylish sleep experience. 98 | 99 | > Finished chain. 100 | Royal Comfort Linens is a premium bedding company offering luxurious and comfortable linens for a restful and stylish sleep experience. 101 | """ 102 | 103 | ``` 104 | 105 | 可以看到,它首先输出公司名称,然后将其传递到第二条链,并给出该公司可能的业务描述。 106 | 107 | # SequentialChain 108 | 109 | img 110 | 111 | 常规顺序链中的任何一个步骤,都可以接收来自上游的多个输入变量,特别当你有复杂的下游链需要和多个上游链组合时,这会非常有用。 112 | 113 | 让这些变量名称准确排列非常重要,因为有很多不同的输入和输出。如果你遇到任何问题,请检查它们排列顺序是否正确。 114 | 115 | ```python 116 | from langchain.chat_models import ChatOpenAI 117 | from langchain.prompts import ChatPromptTemplate 118 | from langchain.chains import LLMChain, SimpleSequentialChain, SequentialChain 119 | 120 | 121 | # 第一条链,将评论翻译成英语。 122 | first_prompt = ChatPromptTemplate.from_template( 123 | "Translate the following review to english:" 124 | "\n\n{Review}" 125 | ) 126 | chain_one = LLMChain(llm=llm, prompt=first_prompt, 127 | output_key="English_Review" 128 | ) 129 | 130 | # 第二条链,用一句话总结该评论 131 | second_prompt = ChatPromptTemplate.from_template( 132 | "Can you summarize the following review in 1 sentence:" 133 | "\n\n{English_Review}" 134 | ) 135 | chain_two = LLMChain(llm=llm, prompt=second_prompt, 136 | output_key="summary" 137 | ) 138 | 139 | # 第三条链,检测原始评论的语言 140 | third_prompt = ChatPromptTemplate.from_template( 141 | "What language is the following review:\n\n{Review}" 142 | ) 143 | chain_three = LLMChain(llm=llm, prompt=third_prompt, 144 | output_key="language" 145 | ) 146 | 147 | # 第四条链,接收第二条链的摘要内容("summary"变量),以及第三条链的语言类别("language"变量),要求后续回复摘要内容时使用指定语言。 148 | fourth_prompt = ChatPromptTemplate.from_template( 149 | "Write a follow up response to the following " 150 | "summary in the specified language:" 151 | "\n\nSummary: {summary}\n\nLanguage: {language}" 152 | ) 153 | chain_four = LLMChain(llm=llm, prompt=fourth_prompt, 154 | output_key="followup_message" 155 | ) 156 | 157 | overall_chain = SequentialChain( 158 | chains=[chain_one, chain_two, chain_three, chain_four], 159 | input_variables=["Review"], 160 | output_variables=["English_Review", "summary","followup_message"], 161 | verbose=True 162 | ) 163 | 164 | review= "Je trouve le goût médiocre. La mousse ne tient pas, c'est bizarre. J'achète les mêmes dans le commerce et le goût est bien meilleur...\nVieux lot ou contrefaçon !?" 165 | 166 | print(overall_chain(review)) 167 | 168 | ``` 169 | 170 | 注意,第四条链的输入为第二、三条链的输入。 171 | 172 | 由于openai限制了一分钟调用的数目,因此这里暂时跑不通。 173 | 174 | # RouterChain 175 | 176 | img 177 | 178 | 如果你有多个子链,且每个子链专门负责处理某种特定类型的输入,这种情况就可以使用路由链。 179 | 180 | **路由链会根据输入的具体内容路由到不同的子链**。 181 | 182 | 它会首先判断该使用哪个子链,然后将输入传递到相应的子链。 183 | 184 | ```python 185 | # 第一个提示,适合回答物理问题 186 | physics_template = """You are a very smart physics professor. \ 187 | You are great at answering questions about physics in a concise\ 188 | and easy to understand manner. \ 189 | When you don't know the answer to a question you admit\ 190 | that you don't know. 191 | 192 | Here is a question: 193 | {input}""" 194 | 195 | # 第二个提示,适合回答数学问题 196 | math_template = """You are a very good mathematician. \ 197 | You are great at answering math questions. \ 198 | You are so good because you are able to break down \ 199 | hard problems into their component parts, 200 | answer the component parts, and then put them together\ 201 | to answer the broader question. 202 | 203 | Here is a question: 204 | {input}""" 205 | 206 | # 第三个提示,适合回答历史问题 207 | history_template = """You are a very good historian. \ 208 | You have an excellent knowledge of and understanding of people,\ 209 | events and contexts from a range of historical periods. \ 210 | You have the ability to think, reflect, debate, discuss and \ 211 | evaluate the past. You have a respect for historical evidence\ 212 | and the ability to make use of it to support your explanations \ 213 | and judgements. 214 | 215 | Here is a question: 216 | {input}""" 217 | 218 | # 第四个提示,适合回答计算机科学问题。 219 | computerscience_template = """ You are a successful computer scientist.\ 220 | You have a passion for creativity, collaboration,\ 221 | forward-thinking, confidence, strong problem-solving capabilities,\ 222 | understanding of theories and algorithms, and excellent communication \ 223 | skills. You are great at answering coding questions. \ 224 | You are so good because you know how to solve a problem by \ 225 | describing the solution in imperative steps \ 226 | that a machine can easily interpret and you know how to \ 227 | choose a solution that has a good balance between \ 228 | time complexity and space complexity. 229 | 230 | Here is a question: 231 | {input}""" 232 | 233 | 234 | prompt_infos = [ 235 | { 236 | "name": "physics", 237 | "description": "Good for answering questions about physics", 238 | "prompt_template": physics_template 239 | }, 240 | { 241 | "name": "math", 242 | "description": "Good for answering math questions", 243 | "prompt_template": math_template 244 | }, 245 | { 246 | "name": "History", 247 | "description": "Good for answering history questions", 248 | "prompt_template": history_template 249 | }, 250 | { 251 | "name": "computer science", 252 | "description": "Good for answering computer science questions", 253 | "prompt_template": computerscience_template 254 | } 255 | ] 256 | 257 | ``` 258 | 259 | MultiPromptChain是一种特定类型的链,用于在多个不同提示模板之间进行路由。 260 | 261 | LLMRouterChain会借助语言模型的帮助,让语言模型根据上面提供的名称和描述等信息,判断如何路由。 262 | 263 | RouterOutputParser将LLM输出解析成一个字典,根据字典内容确定下游使用哪条链,以及链的输入应该是什么。 264 | 265 | ```python 266 | from langchain.chains.router import MultiPromptChain 267 | from langchain.chains.router.llm_router import LLMRouterChain,RouterOutputParser 268 | from langchain.prompts import PromptTemplate 269 | 270 | llm = ChatOpenAI(temperature=0) 271 | 272 | # 路由链会根据输入内容调用这些目标链的其中一个。 273 | destination_chains = {} 274 | for p_info in prompt_infos: 275 | name = p_info["name"] 276 | prompt_template = p_info["prompt_template"] 277 | prompt = ChatPromptTemplate.from_template(template=prompt_template) 278 | chain = LLMChain(llm=llm, prompt=prompt) 279 | destination_chains[name] = chain 280 | 281 | destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] 282 | destinations_str = "\n".join(destinations) 283 | 284 | # 默认链是在路由找不到合适的子链调用时,用来备用的一条链路。 285 | default_prompt = ChatPromptTemplate.from_template("{input}") 286 | default_chain = LLMChain(llm=llm, prompt=default_prompt) 287 | 288 | # LLM 会根据提示词的内容在不同链之间路由。 289 | MULTI_PROMPT_ROUTER_TEMPLATE = """Given a raw text input to a \ 290 | language model select the model prompt best suited for the input. \ 291 | You will be given the names of the available prompts and a \ 292 | description of what the prompt is best suited for. \ 293 | You may also revise the original input if you think that revising\ 294 | it will ultimately lead to a better response from the language model. 295 | 296 | << FORMATTING >> 297 | Return a markdown code snippet with a JSON object formatted to look like: 298 | \```json 299 | {{{{ 300 | "destination": string \ name of the prompt to use or "DEFAULT" 301 | "next_inputs": string \ a potentially modified version of the original input 302 | }}}} 303 | \``` 304 | 305 | REMEMBER: "destination" MUST be one of the candidate prompt \ 306 | names specified below OR it can be "DEFAULT" if the input is not\ 307 | well suited for any of the candidate prompts. 308 | REMEMBER: "next_inputs" can just be the original input \ 309 | if you don't think any modifications are needed. 310 | 311 | << CANDIDATE PROMPTS >> 312 | {destinations} 313 | 314 | << INPUT >> 315 | {{input}} 316 | 317 | << OUTPUT (remember to include the ```json)>>""" 318 | 319 | # 组合语言模型、路由提示模板,构成路由链 320 | router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format( 321 | destinations=destinations_str 322 | ) 323 | router_prompt = PromptTemplate( 324 | template=router_template, 325 | input_variables=["input"], 326 | output_parser=RouterOutputParser(), 327 | ) 328 | 329 | router_chain = LLMRouterChain.from_llm(llm, router_prompt) 330 | 331 | # 组合路由链、目标链和默认链,创建整条链 332 | chain = MultiPromptChain(router_chain=router_chain, 333 | destination_chains=destination_chains, 334 | default_chain=default_chain, verbose=True 335 | ) 336 | # 提问不同类型的问题 337 | # 物理问题 338 | chain.run("What is black body radiation?") 339 | 340 | """ 341 | > Entering new MultiPromptChain chain... 342 | physics: {'input': 'What is black body radiation?'} 343 | > Finished chain. 344 | "Black body radiation refers to the electromagnetic radiation emitted by a perfect black body, which is an object that absorbs all radiation that falls on it and emits radiation at all wavelengths. The radiation emitted by a black body depends only on its temperature and follows a specific distribution known as Planck's law. This type of radiation is important in understanding the behavior of stars, as well as in the development of technologies such as incandescent light bulbs and infrared cameras." 345 | 346 | """ 347 | 348 | # 数学问题 349 | chain.run("what is 2 + 2") 350 | 351 | """ 352 | > Entering new MultiPromptChain chain... 353 | math: {'input': 'what is 2 + 2'} 354 | > Finished chain. 355 | 'As an AI language model, I can answer this question easily. The answer to 2 + 2 is 4.' 356 | 357 | """ 358 | 359 | # 生物问题,无匹配,走默认链 360 | chain.run("Why does every cell in our body contain DNA?") 361 | 362 | """ 363 | > Entering new MultiPromptChain chain... 364 | None: {'input': 'Why does every cell in our body contain DNA?'} 365 | > Finished chain. 366 | 'Every cell in our body contains DNA because DNA carries the genetic information that determines the characteristics and functions of each cell. DNA contains the instructions for the synthesis of proteins, which are essential for the structure and function of cells. Additionally, DNA is responsible for the transmission of genetic information from one generation to the next. Therefore, every cell in our body needs DNA to carry out its specific functions and to maintain the integrity of the organism as a whole.' 367 | 368 | """ 369 | ``` 370 | 371 | # 参考 372 | 373 | > https://juejin.cn/post/7248599585735114789#heading-25 -------------------------------------------------------------------------------- /英文例子/langchain中使用链.assets/257f9fc8ce224d1784c0a5886696026btplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/英文例子/langchain中使用链.assets/257f9fc8ce224d1784c0a5886696026btplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp -------------------------------------------------------------------------------- /英文例子/langchain中使用链.assets/287263118276474996f72c503eddb984tplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/英文例子/langchain中使用链.assets/287263118276474996f72c503eddb984tplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp -------------------------------------------------------------------------------- /英文例子/langchain中使用链.assets/eae7d59e0da7494e899175a500a8990etplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taishan1994/langchain-learning/36d8d4eea18decd23655e01d044bc265021a63db/英文例子/langchain中使用链.assets/eae7d59e0da7494e899175a500a8990etplv-k3u1fbpfcp-zoom-in-crop-mark1512000.webp -------------------------------------------------------------------------------- /英文例子/langchain使用openai例子.md: -------------------------------------------------------------------------------- 1 | ```python 2 | import openai 3 | openai_api_key = "" 4 | 5 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 6 | from langchain.chat_models import ChatOpenAI 7 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 8 | chat = ChatOpenAI(model_name="gpt-3.5-turbo", 9 | openai_api_key=openai_api_key, 10 | temperature=0.0) 11 | 12 | # 模板字符串,用于指定目标语言,拥有两个输入变量,"style"和"text" 13 | template_string = """请翻译文本,将其翻译为{language}。文本: ```{text}```""" 14 | # 构建一个ChatPromptTemplate实例,用于模板的重用 15 | from langchain.prompts import ChatPromptTemplate 16 | prompt_template = ChatPromptTemplate.from_template(template_string) 17 | 18 | # 将风格和文本作为输入变量传入提示模板 19 | language = "中文" 20 | text = "I don't konw who you are." 21 | customer_messages = prompt_template.format_messages( 22 | language="中文", 23 | text=text) 24 | 25 | customer_response = chat(customer_messages) 26 | print(customer_response.content) 27 | 28 | """ 29 | 我不知道你是谁。 30 | """ 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /英文例子/langchain基于文档的问答.md: -------------------------------------------------------------------------------- 1 | # 基本例子 2 | 3 | 首先导入需要的库: 4 | 5 | ```python 6 | from langchain.chains import RetrievalQA 7 | from langchain.chat_models import ChatOpenAI 8 | from langchain.document_loaders import CSVLoader 9 | from langchain.vectorstores import DocArrayInMemorySearch 10 | from IPython.display import display, Markdown 11 | 12 | ``` 13 | 14 | | 工具 | 用途 | 15 | | ---------------------- | ---------------------------------------------------- | 16 | | RetrievalQA | 检索文档 | 17 | | CSVLoader | 加载专用数据(如CSV),用来与模型结合使用 | 18 | | DocArrayInMemorySearch | 内存方式的向量存储,不需要连接到任何类型的外部数据库 | 19 | 20 | ```python 21 | # 步骤1:初始化CSV加载器,为其指定一个CSV文件的路径 22 | file = 'OutdoorClothingCatalog_1000.csv' 23 | loader = CSVLoader(file_path=file) 24 | 25 | # 步骤2:创建一个内存方式的向量存储,传入上一步创建的️️加载器。 26 | from langchain.indexes import VectorstoreIndexCreator 27 | 28 | index = VectorstoreIndexCreator( 29 | vectorstore_cls=DocArrayInMemorySearch 30 | ).from_loaders([loader]) 31 | 32 | # 步骤3:定义查询内容,并使用index.query生成响应 33 | query ="Please list all your shirts with sun protection \ 34 | in a table in markdown and summarize each one." 35 | 36 | response = index.query(query) 37 | 38 | # 步骤4:展示查询结果的表格以及摘要 39 | display(Markdown(response)) 40 | 41 | ``` 42 | 43 | # 文档问答底层原理 44 | 45 | 要想让语言模型与大量文档相结合,有一个关键问题必须解决。 46 | 47 | 那就是,语言模型每次只能处理几千个单词,如何才能让它对一个大型文档的全部内容进行问答呢? 48 | 49 | 这里就要用到嵌入(Embedding)和向量存储(Vector Stores)这两个技术。 50 | 51 | ## 嵌入 52 | 53 | **嵌入是一种将文本片段转换为数字表示的方法,这些数字能够反映文本的语义信息**。 54 | 55 | 语义相近的文本会有相近的向量,这样我们就可以在向量空间中对文本进行比较。 56 | 57 | 比如, 58 | 59 | - 两个同样描述宠物的句子的向量,其相似度会非常高。 60 | - 而与一个描述汽车的句子的向量相比较,其相似度则会很低。 61 | 62 | **通过向量相似度,我们可以轻松地找出文本片段之间的语义关系**。 63 | 64 | 利用这个特性,我们可以从文档中检索出与问题语义最匹配的文本片段,然后将它们和问题一起交给语言模型来生成答案。 65 | 66 | ## 向量数据库 67 | 68 | **向量数据库是用于保存嵌入向量表示的数据库**。 69 | 70 | 我们可以将大型文档拆分成较小的块,为每个块生成一个嵌入,并将其和原始块一起存储到数据库中。 71 | 72 | 这就是我们创建索引的过程。 73 | 74 | 索引创建好后,我们就可以用它来查询与问题最相关的文本片段: 75 | 76 | 1. 当一个问题进来后,为问题生成嵌入; 77 | 2. 将其与向量存储中的所有向量进行比较,选择最相似的n个文本片段; 78 | 3. 将这些文本片段和问题一起传递给语言模型; 79 | 4. 让语言模型根据检索到的文档内容生成最佳答案。 80 | 81 | ```python 82 | # 初始化CSV加载器,为其指定一个CSV文件的路径 83 | loader = CSVLoader(file_path=file) 84 | docs = loader.load() 85 | 86 | # 步骤2:为加载的所有文本片段生成嵌入,并存储在一个向量存储器中 87 | from langchain.embeddings import OpenAIEmbeddings 88 | embeddings = OpenAIEmbeddings() 89 | 90 | db = DocArrayInMemorySearch.from_documents( 91 | docs, 92 | embeddings 93 | ) 94 | 95 | # 我们可以用一段特定文本来查看生成的嵌入内容形式: 96 | embed = embeddings.embed_query("Hi my name is Harrison") 97 | 98 | # 打印嵌入的维度 99 | print(len(embed)) 100 | # 1536 101 | 102 | # 打印前5个数字 103 | print(embed[:5]) 104 | # [-0.021930990740656853, 0.006712669972330332, -0.018181458115577698, -0.039156194776296616, -0.014079621061682701] 105 | 106 | # 从打印结果可知,这个嵌入是一个1536维的向量,以及向量中的数字是如何表示的。 107 | # 我们也可以直接输入一段查询内容,查看通过向量存储器检索到的与查询内容相似的文本片段: 108 | query = "Please suggest a shirt with sunblocking" 109 | docs = db.similarity_search(query) 110 | 111 | # 打印检索到的文本片段数 112 | len(docs) 113 | # 4 114 | 115 | # 打印第一个文本片段内容 116 | docs[0] 117 | # Document(page_content=': 255\nname: Sun Shield Shirt by\ndescription: "Block the sun, not the fun – our high-performance sun shirt is guaranteed to protect from harmful UV rays. \n\nSize & Fit: Slightly Fitted: Softly shapes the body. Falls at hip.\n\nFabric & Care: 78% nylon, 22% Lycra Xtra Life fiber. UPF 50+ rated – the highest rated sun protection possible. Handwash, line dry.\n\nAdditional Features: Wicks moisture for quick-drying comfort. Fits comfortably over your favorite swimsuit. Abrasion resistant for season after season of wear. Imported.\n\nSun Protection That Won\'t Wear Off\nOur high-performance fabric provides SPF 50+ sun protection, blocking 98% of the sun\'s harmful rays. This fabric is recommended by The Skin Cancer Foundation as an effective UV protectant.', metadata={'source': 'OutdoorClothingCatalog_1000.csv', 'row': 255}) 118 | # 从打印结果可知,检索到了4个相似的文本片段,而返回的第一个文本片段也确实与查询内容相关。 119 | 120 | # 步骤3:使用RetrievalQA链对查询进行检索,然后在检索的文档上进行问答 121 | retriever = db.as_retriever() 122 | llm = ChatOpenAI(temperature = 0.0) 123 | 124 | # stuff表示将文本片段合并成一段文本 125 | qa_stuff = RetrievalQA.from_chain_type( 126 | llm=llm, 127 | chain_type="stuff", 128 | retriever=retriever, 129 | verbose=True 130 | ) 131 | 132 | ``` 133 | 134 | RetrievalQA链其实就是把合并文本片段和调用语言模型这两步骤封装起来,如果没有RetrievalQA链,我们需要这样子实现: 135 | 136 | ```python 137 | # (1)将检索出来的文本片段合并成一段文本 138 | qdocs = "".join([docs[i].page_content for i in range(len(docs))]) 139 | 140 | # (2)将合并后的文本和问题一起传给LLM 141 | response = llm.call_as_llm(f"{qdocs} Question: Please list all your \ 142 | shirts with sun protection in a table in markdown and summarize each one.") 143 | 144 | ``` 145 | 146 | 接着上述步骤: 147 | 148 | ```python 149 | # 步骤4:创建一个查询,并把查询的内容传入链并运行 150 | response = qa_stuff.run(query) 151 | 152 | # 步骤5:展示查询结果的表格以及摘要 153 | display(Markdown(response)) 154 | 155 | ``` 156 | 157 | ## 可选链的类型 158 | 159 | 这里就不作介绍了,在之前的组件里面有介绍。 160 | 161 | ## 评估 162 | 163 | 这个我们之前没有了解,这里好好看下。 164 | 165 | 评估有两个目的: 166 | 167 | - 检验LLM应用是否达到了验收标准 168 | - 分析改动对于LLM应用性能的影响 169 | 170 | 基本的思路就是利用语言模型本身和链本身,来辅助评估其他的语言模型、链和应用程序。 171 | 172 | 我们还是以上一节课的文档问答应用为例。 173 | 174 | 评估过程需要用到评估数据集,我们可以直接硬编码一些示例数据集,比如: 175 | 176 | ```python 177 | examples = [ 178 | { 179 | "query": "Do the Cozy Comfort Pullover Set\ 180 | have side pockets?", 181 | "answer": "Yes" 182 | }, 183 | { 184 | "query": "What collection is the Ultra-Lofty \ 185 | 850 Stretch Down Hooded Jacket from?", 186 | "answer": "The DownTek collection" 187 | } 188 | ] 189 | 190 | ``` 191 | 192 | 但这种方式不太方便扩展,也比较耗时,所以,我们可以—- 193 | 194 | ```python 195 | # 步骤1:使用语言模型自动生成评估数据集 196 | from langchain.evaluation.qa import QAGenerateChain 197 | 198 | # QAGenerateChain链用于接收文档,并借助语言模型为每个文档生成一个问答对 199 | example_gen_chain = QAGenerateChain.from_llm(ChatOpenAI()) 200 | 201 | new_examples = example_gen_chain.apply_and_parse( 202 | [{"doc": t} for t in data[:5]] 203 | ) 204 | # 我们可以打印看下其返回的内容: 205 | print(new_examples[0]) 206 | 207 | {'query': "What is the weight of each pair of Women's Campside Oxfords?", 208 | 'answer': "The approximate weight of each pair of Women's Campside Oxfords is 1 lb. 1 oz."} 209 | 210 | # 步骤2:将生成的问答对添加到已有的评估数据集中 211 | examples += new_examples 212 | 213 | # 步骤3:为所有不同的示例生成实际答案 214 | # 这里的qa对应的是上一节课的RetrievalQA链 215 | predictions = qa.apply(examples) 216 | 217 | # 步骤4:对LLM应用的输出进行评估 218 | from langchain.evaluation.qa import QAEvalChain 219 | llm = ChatOpenAI(temperature=0) 220 | eval_chain = QAEvalChain.from_llm(llm) 221 | # 传入示例列表和实际答案列表 222 | graded_outputs = eval_chain.evaluate(examples, predictions) 223 | 224 | # 步骤5:打印问题、标准答案、实际答案和评分 225 | for i, eg in enumerate(examples): 226 | print(f"Example {i}:") 227 | print("Question: " + predictions[i]['query']) 228 | print("Real Answer: " + predictions[i]['answer']) 229 | print("Predicted Answer: " + predictions[i]['result']) 230 | print("Predicted Grade: " + graded_outputs[i]['text']) 231 | print() 232 | 233 | # 列举其中的前3个评估结果如下: 234 | """ 235 | Example 0: 236 | Question: Do the Cozy Comfort Pullover Set have side pockets? 237 | Real Answer: Yes 238 | Predicted Answer: The Cozy Comfort Pullover Set, Stripe does have side pockets. 239 | Predicted Grade: CORRECT 240 | 241 | Example 1: 242 | Question: What collection is the Ultra-Lofty 850 Stretch Down Hooded Jacket from? 243 | Real Answer: The DownTek collection 244 | Predicted Answer: The Ultra-Lofty 850 Stretch Down Hooded Jacket is from the DownTek collection. 245 | Predicted Grade: CORRECT 246 | 247 | Example 2: 248 | Question: What is the weight of each pair of Women's Campside Oxfords? 249 | Real Answer: The approximate weight of each pair of Women's Campside Oxfords is 1 lb. 1 oz. 250 | Predicted Answer: The weight of each pair of Women's Campside Oxfords is approximately 1 lb. 1 oz. 251 | Predicted Grade: CORRECT 252 | ... 253 | """ 254 | ``` 255 | 256 | 对比第一个评估结果的两个答案可以看到,其标准答案较为简洁,而实际答案则较为详细,但表达的意思都是正确的,语言模型也能识别,因此才把它标记为正确的。 257 | 258 | 虽然这两个字符串完全不同,以致于使用传统的正则表达式等手段是无法对它们进行比较的。 259 | 260 | 这里就体现了**使用语言模型进行评估的优势——同一个问题的答案可能有很多不同的变体,只要意思相通,就应该被认为是相似的**。 261 | 262 | ## 代理 263 | 264 | 大型语言模型可以作为一个推理引擎,只要给它提供文本或其他信息源,它就会利用互联网上学习到的背景知识或你提供的新信息,来回答问题、推理内容或决定下一步的操作。 265 | 266 | 这就是LangChain的代理框架能够帮我们实现的事情,而代理也正是LangChain最强大的功能之一。 267 | 268 | ### 使用内置于 LangChain 的工具 269 | 270 | ```python 271 | # 步骤1:初始化语言模型 272 | from langchain.agents.agent_toolkits import create_python_agent 273 | from langchain.agents import load_tools, initialize_agent 274 | from langchain.agents import AgentType 275 | from langchain.tools.python.tool import PythonREPLTool 276 | from langchain.python import PythonREPL 277 | from langchain.chat_models import ChatOpenAI 278 | 279 | llm = ChatOpenAI(temperature=0) 280 | 281 | # temperature参数, 语言模型作为代理的推理引擎,会连接到其他数据和计算资源,我们会希望这个推理引擎尽可能地好用且精确,因此需要把temperature参数设为0。 282 | 283 | # 步骤2:加载工具 284 | # llm-math:解决数学问题 285 | # wikipedia:查询维基百科 286 | tools = load_tools(["llm-math","wikipedia"], llm=llm) 287 | 288 | # 步骤3:初始化代理 289 | agent= initialize_agent( 290 | tools, 291 | llm, 292 | agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, 293 | handle_parsing_errors=True, 294 | verbose = True) 295 | 296 | """ 297 | agent参数 298 | 299 | agent参数 CHAT_ZERO_SHOT_REACT_DESCRIPTION中的CHAT部分,表示这是一个专门为Chat模型优化的代理。REACT部分表示一种组织Prompt的技术,能够最大化语言模型的推理能力。 300 | 301 | handle_parsing_errors 302 | 303 | true表示当内容无法被正常解析时,会将错误内容传回语言模型,让它自行纠正。 304 | 305 | """ 306 | 307 | # 步骤4:向代理提问 308 | agent("What is the 25% of 300?") 309 | 310 | question = "Tom M. Mitchell is an American computer scientist \ 311 | and the Founders University Professor at Carnegie Mellon University (CMU)\ 312 | what book did he write?" 313 | result = agent(question) 314 | 315 | ``` 316 | 317 | 从打印出来的中间步骤详细记录中,我们可以看到几个关键词,其表示的含义分别是: 318 | 319 | | 关键词 | 表示含义 | 320 | | ----------- | ------------------------ | 321 | | Thought | LLM在思考的内容 | 322 | | Action | 执行特定的动作 | 323 | | Observation | 从这个动作中观察到了什么 | 324 | 325 | ### 使用 Python 代理工具 326 | 327 | 类似于ChatGPT的代码解释器,Python 代理工具可以让语言模型编写并执行Python代码,然后将执行的结果返回给代理,让它决定下一步的操作。 328 | 329 | 我们的任务目标是对一组客户名单按照姓氏和名字进行排序。 330 | 331 | ```python 332 | # 步骤1:创建Python代理 333 | agent = create_python_agent( 334 | llm, 335 | tool=PythonREPLTool(), 336 | verbose=True 337 | ) 338 | 339 | # 步骤2:要求代理编写排序代码,并打印输出结果 340 | customer_list = [["Harrison", "Chase"], 341 | ["Lang", "Chain"], 342 | ["Dolly", "Too"], 343 | ["Elle", "Elem"], 344 | ["Geoff","Fusion"], 345 | ["Trance","Former"], 346 | ["Jen","Ayai"] 347 | ] 348 | 349 | agent.run(f"""Sort these customers by \ 350 | last name and then first name \ 351 | and print the output: {customer_list}""") 352 | 353 | ``` 354 | 355 | ### 使用自定义工具 356 | 357 | 代理的一个优势就是你可以将其连接到你自己的信息来源、API、数据。 358 | 359 | ```python 360 | # 步骤1:定义一个工具,用于获取当前日期 361 | from langchain.agents import tool 362 | from datetime import date 363 | 364 | @tool 365 | def time(text: str) -> str: 366 | """Returns todays date, use this for any \ 367 | questions related to knowing todays date. \ 368 | The input should always be an empty string, \ 369 | and this function will always return todays \ 370 | date - any date mathmatics should occur \ 371 | outside this function.""" 372 | return str(date.today()) 373 | 374 | # 除了函数名称,这里还写了一份详细的注释说明,代理会根据注释中的信息来判断何时应该调用、以及应该如何调用这个工具。 375 | 376 | # 步骤2:初始化代理,将自定义工具加到现有工具列表里 377 | agent= initialize_agent( 378 | tools + [time], 379 | llm, 380 | agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, 381 | handle_parsing_errors=True, 382 | verbose = True) 383 | 384 | # 步骤3:调用代理,获取当前日期 385 | try: 386 | result = agent("whats the date today?") 387 | except: 388 | print("exception on external access") 389 | ``` 390 | 391 | -------------------------------------------------------------------------------- /英文例子/langchain带有记忆的对话.md: -------------------------------------------------------------------------------- 1 | # ConversationBufferMemory 2 | 3 | 存储完整的对话。 4 | 5 | ```python 6 | from langchain.chat_models import ChatOpenAI 7 | from langchain.chains import ConversationChain 8 | from langchain.memory import ConversationBufferMemory 9 | 10 | import openai 11 | openai_api_key = "" 12 | 13 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 14 | from langchain.chat_models import ChatOpenAI 15 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 16 | llm = ChatOpenAI(model_name="gpt-3.5-turbo", 17 | openai_api_key=openai_api_key, 18 | temperature=0.0) 19 | 20 | memory = ConversationBufferMemory() 21 | conversation = ConversationChain( 22 | llm=llm, 23 | memory = memory, 24 | verbose=True 25 | ) 26 | 27 | print(conversation.predict(input="我叫安德鲁")) 28 | print(conversation.predict(input="1+1等于几")) 29 | print(conversation.predict(input="我的名字叫什么")) 30 | 31 | """ 32 | > Entering new chain... 33 | Prompt after formatting: 34 | The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. 35 | 36 | Current conversation: 37 | 38 | Human: 我叫安德鲁 39 | AI: 40 | 41 | > Finished chain. 42 | 你好,安德鲁!很高兴认识你。我是一个AI助手,我可以回答你的问题或提供帮助。有什么我可以帮你的吗? 43 | 44 | 45 | > Entering new chain... 46 | Prompt after formatting: 47 | The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. 48 | 49 | Current conversation: 50 | Human: 我叫安德鲁 51 | AI: 你好,安德鲁!很高兴认识你。我是一个AI助手,我可以回答你的问题或提供帮助。有什么我可以帮你的吗? 52 | Human: 1+1等于几 53 | AI: 54 | 55 | > Finished chain. 56 | 1+1等于2。 57 | 58 | 59 | > Entering new chain... 60 | Prompt after formatting: 61 | The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. 62 | 63 | Current conversation: 64 | Human: 我叫安德鲁 65 | AI: 你好,安德鲁!很高兴认识你。我是一个AI助手,我可以回答你的问题或提供帮助。有什么我可以帮你的吗? 66 | Human: 1+1等于几 67 | AI: 1+1等于2。 68 | Human: 我的名字叫什么 69 | AI: 70 | 71 | > Finished chain. 72 | 你的名字是安德鲁。 73 | """ 74 | ``` 75 | 76 | 我们也可以往里面添加新的信息,并打印: 77 | 78 | ```python 79 | memory.save_context({"input": "Not much, just hanging"}, 80 | {"output": "Cool"}) 81 | 82 | print(memory.buffer) 83 | 84 | """ 85 | > Finished chain. 86 | 你的名字是安德鲁。 87 | Human: 我叫安德鲁 88 | AI: 你好,安德鲁!很高兴认识你。我是一个AI助手,我可以回答你的问题或提供帮助。有什么我可以帮你的吗? 89 | Human: 1+1等于几 90 | AI: 1+1等于2。 91 | Human: 我的名字叫什么 92 | AI: 你的名字是安德鲁。 93 | Human: Not much, just hanging 94 | AI: Cool 95 | """ 96 | ``` 97 | 98 | 其中input是human信息,ouput是ai信息。 99 | 100 | 但随着对话的进行,记忆存储的大小会增加,发送Token的成本也会增加,为此,LangChain提供了另外几种策略。 101 | 102 | # ConversationBufferWindowMemory 103 | 104 | ConversationBufferWindowMemory只保留窗口记忆,也即只保留最后几轮对话。它有一个变量k,表示想记住最后几轮对话。 105 | 106 | 比如,当k等于1时,表示仅记住最后一轮对话。 107 | 108 | 例子如下: 109 | 110 | ```python 111 | from langchain.memory import ConversationBufferWindowMemory 112 | 113 | llm = ChatOpenAI(temperature=0.0) 114 | memory = ConversationBufferWindowMemory(k=1) 115 | conversation = ConversationChain( 116 | llm=llm, 117 | memory = memory, 118 | verbose=False 119 | ) 120 | 121 | conversation.predict(input="Hi, my name is Andrew") 122 | # "Hello Andrew, it's nice to meet you. My name is AI. How can I assist you today?" 123 | 124 | conversation.predict(input="What is 1+1?") 125 | # 'The answer to 1+1 is 2.' 126 | 127 | conversation.predict(input="What is my name?") 128 | # "I'm sorry, I don't have access to that information. Could you please tell me your name?" 129 | 130 | 131 | 132 | 133 | 134 | ``` 135 | 136 | 这时我们会发现,由于窗口记忆的限制,它会丢失了前面有关名字的交流,从而无法说出我的名字。 137 | 138 | 这个功能可以防止记忆存储量随着对话的进行而无限增长。当然在实际使用时,k不会设为1,而是会通常设置为一个较大的数字。 139 | 140 | # ConversationalTokenBufferMemory 141 | 142 | 很多LLM定价是基于Token的,Token调用的数量直接反映了LLM调用的成本。 143 | 144 | 使用ConversationalTokenBufferMemory,可以限制保存在记忆存储的令牌数量。 145 | 146 | 例子如下: 147 | 148 | ```python 149 | from langchain.memory import ConversationTokenBufferMemory 150 | from langchain.llms import OpenAI 151 | 152 | llm = ChatOpenAI(temperature=0.0) 153 | 154 | # 指定LLM和Token限制值 155 | memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=30) 156 | 157 | ``` 158 | 159 | 在插入一些消息之后,我们可以打印其实际保存的历史消息。 160 | 161 | ```python 162 | memory.save_context({"input": "AI is what?!"}, 163 | {"output": "Amazing!"}) 164 | memory.save_context({"input": "Backpropagation is what?"}, 165 | {"output": "Beautiful!"}) 166 | memory.save_context({"input": "Chatbots are what?"}, 167 | {"output": "Charming!"}) 168 | 169 | # 打印历史消息 170 | memory.load_memory_variables({}) 171 | ``` 172 | 173 | 我们会发现,当把Token限制值调得比较高时,它几乎可以包含整个对话。 174 | 175 | **而如果减少值,它会删掉对话最早的那部分消息,只保留最近对话的消息,并且保证总的消息内容长度不超过Token限制值**。 176 | 177 | 另外,之所以还要指定一个LLM参数,是因为不同的LLM使用不同的Token计算方式。 178 | 179 | 这里是告诉它,使用ChatOpenAI LLM使用的计算Token的方法。 180 | 181 | # ConversationSummaryBufferMemory 182 | 183 | ConversationSummaryBufferMemory试图将消息的显性记忆,保持在我们设定的Token限制值之下,也即 184 | 185 | 1. 当Token限制值能覆盖文本长度时,会存储整个对话历史。 186 | 2. 而当Token限制值小于文本长度时,则会为所有历史消息生成摘要,改在记忆中存储历史消息的摘要。 187 | 188 | 以情况2为例: 189 | 190 | ```python 191 | from langchain.memory import ConversationSummaryBufferMemory 192 | 193 | # 创建一个长字符串 194 | schedule = "There is a meeting at 8am with your product team. \ 195 | You will need your powerpoint presentation prepared. \ 196 | 9am-12pm have time to work on your LangChain \ 197 | project which will go quickly because Langchain is such a powerful tool. \ 198 | At Noon, lunch at the italian resturant with a customer who is driving \ 199 | from over an hour away to meet you to understand the latest in AI. \ 200 | Be sure to bring your laptop to show the latest LLM demo." 201 | 202 | memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100) 203 | memory.save_context({"input": "Hello"}, {"output": "What's up"}) 204 | memory.save_context({"input": "Not much, just hanging"}, 205 | {"output": "Cool"}) 206 | memory.save_context({"input": "What is on the schedule today?"}, 207 | {"output": f"{schedule}"}) 208 | 209 | conversation = ConversationChain( 210 | llm=llm, 211 | memory = memory, 212 | verbose=True 213 | ) 214 | conversation.predict(input="What would be a good demo to show?") 215 | 216 | """ 217 | > Entering new ConversationChain chain... 218 | Prompt after formatting: 219 | The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. 220 | Current conversation: 221 | System: The human and AI engage in small talk before discussing the day's schedule. The AI informs the human of a morning meeting with the product team, time to work on the LangChain project, and a lunch meeting with a customer interested in the latest AI developments. 222 | Human: What would be a good demo to show? 223 | AI: 224 | > Finished chain. 225 | "Based on the customer's interest in AI developments, I would suggest showcasing our latest natural language processing capabilities. We could demonstrate how our AI can accurately understand and respond to complex language queries, and even provide personalized recommendations based on the user's preferences. Additionally, we could highlight our AI's ability to learn and adapt over time, making it a valuable tool for businesses looking to improve their customer experience." 226 | 227 | """ 228 | ``` 229 | 230 | 可以看到,由于超过了设定的Token限制值,它为历史会话的生成了一个摘要,并放在系统消息的提示词中。 231 | 232 | # 其它的一些记忆存储策略 233 | 234 | - 向量数据存储:存储文本到向量数据库中并检索最相关的文本。 235 | - 实体记忆:在llm中使用,存储特定实体的细节。 -------------------------------------------------------------------------------- /英文例子/langchain解析结果并格式化输出.md: -------------------------------------------------------------------------------- 1 | ```python 2 | import openai 3 | openai_api_key = "" 4 | 5 | # 导入ChatOpenAI,这是LangChain对ChatGPT API访问的抽象 6 | from langchain.chat_models import ChatOpenAI 7 | # 要控制 LLM 生成文本的随机性和创造性,请使用 temperature = 0.0 8 | chat = ChatOpenAI(model_name="gpt-3.5-turbo", 9 | openai_api_key=openai_api_key, 10 | temperature=0.0) 11 | 12 | from langchain.output_parsers import ResponseSchema 13 | from langchain.output_parsers import StructuredOutputParser 14 | 15 | # 礼物规范 16 | gift_schema = ResponseSchema(name="gift", 17 | description="Was the item purchased\ 18 | as a gift for someone else? \ 19 | Answer True if yes,\ 20 | False if not or unknown.") 21 | # 送货日期规范 22 | delivery_days_schema = ResponseSchema(name="delivery_days", 23 | description="How many days\ 24 | did it take for the product\ 25 | to arrive? If this \ 26 | information is not found,\ 27 | output -1.") 28 | # 价格值规范 29 | price_value_schema = ResponseSchema(name="price_value", 30 | description="Extract any\ 31 | sentences about the value or \ 32 | price, and output them as a \ 33 | comma separated Python list.") 34 | 35 | # 将格式规范放到一个列表里 36 | response_schemas = [gift_schema, 37 | delivery_days_schema, 38 | price_value_schema] 39 | # 构建一个StructuredOutputParser实例 40 | output_parser = StructuredOutputParser.from_response_schemas(response_schemas) 41 | # 获取将发送给LLM的格式指令 42 | format_instructions = output_parser.get_format_instructions() 43 | 44 | print(format_instructions) 45 | 46 | from langchain.prompts.chat import ChatPromptTemplate 47 | 48 | # 提示 49 | review_template_2 = """\ 50 | For the following text, extract the following information: 51 | 52 | gift: Was the item purchased as a gift for someone else? \ 53 | Answer True if yes, False if not or unknown. 54 | 55 | delivery_days: How many days did it take for the product\ 56 | to arrive? If this information is not found, output -1. 57 | 58 | price_value: Extract any sentences about the value or price,\ 59 | and output them as a comma separated Python list. 60 | 61 | text: {text} 62 | 63 | {format_instructions} 64 | """ 65 | 66 | customer_review = "I bought a wallet as a gift, it is worth $20 and is expected to be delivered on January 3, 2020." 67 | 68 | # 构建一个ChatPromptTemplate实例,用于模板的重用 69 | prompt = ChatPromptTemplate.from_template(template=review_template_2) 70 | # 将文本和格式指令作为输入变量传入 71 | messages = prompt.format_messages(text=customer_review, 72 | format_instructions=format_instructions) 73 | response = chat(messages) 74 | print(response.content) 75 | 76 | ```json { "gift": true, "delivery_days": -1, "price_value": ["it is worth $20"] } ` 77 | 78 | # 结果解析为字典 79 | output_dict = output_parser.parse(response.content) 80 | print(output_dict.get('delivery_days'))`` 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /英文例子/openai调用chatgpt例子.md: -------------------------------------------------------------------------------- 1 | ```python 2 | import openai 3 | openai.api_key = "" 4 | 5 | """用于打印模型列表 6 | models = openai.Model.list() 7 | total = len(models.data) 8 | for i in range(total): 9 | print(models.data[i].id) 10 | """ 11 | 12 | # create a chat completion 13 | def get_completion(prompt, model_name="gpt-3.5-turbo"): 14 | messages = [{"role": "user", "content": prompt}] 15 | response = openai.ChatCompletion.create( 16 | model=model_name, 17 | messages=messages, 18 | stream=False, 19 | temperature=0) 20 | print(response) 21 | return response.choices[0].message["content"] 22 | 23 | 24 | text = "I don't know who are you" 25 | 26 | prompt = f"""{text} 翻译为中文。""" 27 | print(get_completion(prompt).encode("utf-8").decode("utf-8")) 28 | ``` 29 | 30 | --------------------------------------------------------------------------------