本地大模型编程实战(19)RAG(Retrieval Augmented Generation,检索增强生成)(3)
文章目录
- 准备
- 创建矢量数据库对象
- 创建 LangGraph 链
- 将检索步骤转化为工具
- 定义节点
- 构建图
- 见证效果
- `qwen2.5`
- `llama3.1`
- `MFDoom/deepseek-r1-tool-calling:7b`
- 总结
- 代码
- 参考
上一篇文章我们演练了一个 用 langgraph
实现的 RAG(Retrieval Augmented Generation,检索增强生成)
系统。本文将要创建的系统将更加智能:如果在知识库中未找到靠谱的文档,则用 LLM(大语言模型)
自身的能力弥补。
另外,检索和生成部分增加了更多的细节控制。本次构建的 LangGraph
链如下图:
如上图,query_or_respond 是一个条件节点,它通过能否根据用户的问题生成 工具调用(tool_calls) ,来判断是否需要检索矢量知识库:如果 工具调用 为空,则直接由大语言模型处理;否则通过 工具调用 调用 tools 进行检索。
使用
qwen2.5
、deepseek
以及llama3.1
做实验,用shaw/dmeta-embedding-zh
做中文嵌入和检索。
准备
在正式开始撸代码之前,需要准备一下编程环境。
-
计算机
本文涉及的所有代码可以在没有显存的环境中执行。 我使用的机器配置为:- CPU: Intel i5-8400 2.80GHz
- 内存: 16GB
-
Visual Studio Code 和 venv
这是很受欢迎的开发工具,相关文章的代码可以在Visual Studio Code
中开发和调试。 我们用python
的venv
创建虚拟环境, 详见:
在Visual Studio Code中配置venv。 -
Ollama
在Ollama
平台上部署本地大模型非常方便,基于此平台,我们可以让langchain
使用llama3.1
、qwen2.5
、deepseek
等各种本地大模型。详见:
在langchian中使用本地部署的llama3.1大模型 。
创建矢量数据库对象
我们直接使用之前使用 chroma
创建好的本地嵌入数据库,它的数据源是一个 csv 文件,每一行包含了一种动物的信息,例如:
名称,学名,特点,作用
狗,Canis lupus familiaris,忠诚、聪明、社交性强,看家护院、导盲、搜救、警务、情感陪伴
猫,Felis catus,独立、高冷、善于捕鼠,消灭害鼠、陪伴、缓解压力
详细的创建过程可参见:本地大模型编程实战(14)初探智能体Agent(1)
embed_model_name = "shaw/dmeta-embedding-zh"
vector_store = Chroma(persist_directory=get_persist_directory(embed_model_name),embedding_function=OllamaEmbeddings(model=embed_model_name))
创建 LangGraph 链
在上一篇文章 RAG(Retrieval Augmented Generation,检索增强生成)(2) 中,我们将用户输入、检索到的上下文和生成的答案表示为状态对象中的单独键。
除了来自用户和AI的消息之外,还可以通过工具消息将检索到的文档内容合并到消息序列中,这样我们可以使用一系列消息来表示 RAG
应用程序的状态。具体来说,我们将:
- 把用户输入作为 HumanMessage;
- 把向量存储查询作为带有工具调用的 AIMessage;
- 把检索到的文档作为 ToolMessage;
- 最终响应作为 AIMessage。
这种状态模型非常通用,因此 LangGraph 提供了一个内置版本以方便使用:
from langgraph.graph import MessagesState, StateGraph
graph_builder = StateGraph(MessagesState)
将检索步骤转化为工具
利用工具调用与检索步骤交互还有另一个好处,即检索查询由我们的大语言模型生成。这在对话环境中尤其重要,因为用户查询可能需要基于聊天记录进行情境化。例如,考虑以下交流:
Human:“什么是任务分解?”
AI:“任务分解涉及将复杂任务分解为更小、更简单的步骤,以使代理或模型更易于管理。”
Human:“常见的做法是什么?”
在这种情况下,模型可以生成查询,例如:任务分解的常见方法。
由于大语言模型在生成工具调用时会智能的生成查询,那么它将会明显提升查询的准确性。它还可以生成不涉及检索步骤的直接响应(例如,响应用户的一般问候),也可以自动进行指代消解:根据上下文自动修改问题,把问题中的代词替换成上下文中的内容。
下面我们基于 检索 生成一个 工具:
@tool(response_format="content_and_artifact",parse_docstring=True) # docstring的内容对agent自动推理影响比较大
def retrieve(query: str):"""检索与 query参数内容 相关的信息Args:query: 要搜索的字符串。 """print(f"start retrieve:{query}")# 定义相似度阈值。因为这种相似性检索并不考虑相似性大小,如果不限制可能会返回相似性不大的文档, 可能会影响问答效果。similarity_threshold = 0.8retrieved_docs = vector_store.similarity_search_with_score(query, k=3)# 根据相似度分数过滤结果filtered_docs = [doc for doc, score in retrieved_docs if score <= similarity_threshold]serialized = "\n\n".join((f"Source: {doc.metadata}\n" f"Content: {doc.page_content}")for doc in filtered_docs)if not serialized:return "抱歉,我找不到任何相关信息。", Noneelse:return serialized, filtered_docs
上述检索工具完善了以下几个细节:
- 定义了相似度阈值:过滤掉相似度太低的文档,防止差的结果产生负面影响;
- 如果未找到靠谱的文档,返回:“抱歉,我找不到任何相关信息。”:这个结果将会在 生成 阶段被添加到提示词中,有助于大语言模型自行推理更合理的回答。
定义节点
我们的链将由三个节点组成:
- 一个处理用户输入的节点:要么为检索器生成查询,要么直接响应;
- 一个用于执行检索步骤的检索器工具的节点;
- 一个使用检索到的上下文生成最终响应的节点。
我们在下面构建它们。
请注意,我们利用另一个预先构建的LangGraph
组件 ToolNode,它执行该工具并将结果作为 ToolMessage 添加到状态(state)。
# 1: 生成可能包含工具调用(tool_call)的 AIMessage。
def query_or_respond(state: MessagesState):"""生成用于检索或响应的工具调用。"""llm_with_tools = llm.bind_tools([retrieve])response = llm_with_tools.invoke(state["messages"])"""这里会自动进行指代消解:根据上下文自动修改问题,把问题中的代词替换成上下文中的内容"""# MessagesState 将消息附加到 state 而不是覆盖return {"messages": [response]}# 2: 执行检索
tools = ToolNode([retrieve])# 3: 使用检索到的内容生成响应。
def generate(state: MessagesState):"""生成回答。"""# 获取生成的 ToolMessagesrecent_tool_messages = []for message in reversed(state["messages"]):if message.type == "tool":recent_tool_messages.append(message)else:breaktool_messages = recent_tool_messages[::-1]# 获取 ToolMessages 的内容,并格式化为提示词docs_content = "\n\n".join(doc.content for doc in tool_messages)system_message_content = ("你是一个负责答疑任务的助手。 ""使用以下检索到的上下文来回答问题。 ""如果你不知道答案,就说你不知道。 ""最多使用三句话并保持答案简洁。 ""\n\n"f"{docs_content}")conversation_messages = [messagefor message in state["messages"]if message.type in ("human", "system")or (message.type == "ai" and not message.tool_calls)]prompt = [SystemMessage(system_message_content)] + conversation_messages# 执行response = llm.invoke(prompt)# MessagesState 将消息附加到 state 而不是覆盖return {"messages": [response]}
构建图
现在,我们将节点连接起来,将应用程序编译成单个 LangGraph
对象。
第一个节点 query_or_respond 步骤可以“短路”并直接响应用户(如果它不生成工具调用)。这使我们的应用程序能够支持对话体验。 例如,响应可能不需要检索步骤的通用问候语。
def build_graph(llm_model_name):"""构建 langgraph 链"""llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True)# 串联节点和边,构建图graph_builder = StateGraph(MessagesState)graph_builder.add_node(query_or_respond)graph_builder.add_node(tools)graph_builder.add_node(generate)graph_builder.set_entry_point("query_or_respond")graph_builder.add_conditional_edges("query_or_respond",tools_condition,{END: END, "tools": "tools"},)graph_builder.add_edge("tools", "generate")graph_builder.add_edge("generate", END)graph = graph_builder.compile()return graph
见证效果
现在可以定义测试方法:
def ask(llm_model_name,question):"""提问"""graph = build_graph(llm_model_name)for step in graph.stream({"messages": [{"role": "user", "content": question}]},stream_mode="values",):step["messages"][-1].pretty_print()
我们准备两个问题:
query1 = "马的学名是什么?它有什么用途?"
query2 = "中国有多少个省份?"
现在用各大模型试一下。
qwen2.5
- query1 = “马的学名是什么?它有什么用途?”
================================ Human Message =================================马的学名是什么?它有什么用途?
================================== Ai Message ==================================
Tool Calls:retrieve (729acad9-b8ae-4ed3-aa12-f48dd431d8ed)Call ID: 729acad9-b8ae-4ed3-aa12-f48dd431d8edArgs:query: 马 学名
start retrieve:马 学名
================================= Tool Message =================================
Name: retrieveSource: {'row': 2, 'source': 'D:\\project\\programming-with-local-large-language-model\\server\\services\\practice\\assert/animals.csv'}
Content: 名称: 马
学名: Equus ferus caballus
特点: 速度快、耐力强、与人类有深厚的合作关系
作用: 交通、战马、农业、体育竞技
================================== Ai Message ==================================马的学名是Equus ferus caballus。马的用途包括交通、作为战马、用于农业劳动以及参与体育竞技。
在生成工具调用(Tool Calls),调用工具检索步骤都很稳妥,最终结果也没问题。
- query2 = “中国有多少个省份?”
================================ Human Message =================================中国有多少个省份?
================================== Ai Message ==================================中国的行政区划中,通常指的是34个省级行政区,包括23个省、5个自治区、4个直辖市以及香港特别行政区和澳门特别行政区。如果您需要更详细的信息或者有其他问题,请告诉我!
这次生成的工具调用(Tool Calls)为空,所以调过了检索和生成步骤,直接由大语言模型返回结果了。
值得一提的是:虽然大语言模型 在 query_or_respond 函数中生成的 response 对象的工具调用(tool_calls)为空,但是 content 是有正确内容的,这说明:它还可以生成不涉及检索步骤的直接响应。
很棒!
llama3.1
- query1 = “马的学名是什么?它有什么用途?”
================================ Human Message =================================马的学名是什么?它有什么用途?
================================== Ai Message ==================================
Tool Calls:retrieve (c811874c-528f-4f17-878b-774452af14d8)Call ID: c811874c-528f-4f17-878b-774452af14d8Args:query: 王的学名我是,常用给为为
start retrieve:王的学名我是,常用给为为
================================= Tool Message =================================
Name: retrieve抱歉,我找不到任何相关信息。
================================== Ai Message ==================================马的学名是Equus caballus。马主要用于运输、耕作和骑乘等方面。
显然,它生成的工具调用(tool_calls)是错误的, retrieve 返回错误信息:“抱歉,我找不到任何相关信息。”,大模型最终还是利用自己的能力回答了问题。
- query2 = “中国有多少个省份?”
================================ Human Message =================================中国有多少个省份?
================================== Ai Message ==================================
Tool Calls:retrieve (95b0f1ca-9292-4df6-b670-64a267ef1a60)Call ID: 95b0f1ca-9292-4df6-b670-64a267ef1a60Args:query: 中国有有二市综名
start retrieve:中国有有二市综名
================================= Tool Message =================================
Name: retrieve抱歉,我找不到任何相关信息。
================================== Ai Message ==================================中国共有34个省份。
按照常理来讲,这次不应该工具调用(tool_calls)了,结果 llama3.1
还是生成了一个不靠谱的 工具调用(tool_calls)。
看起来 llama3.1
生成中文的工具调用(tool_calls)有问题。
MFDoom/deepseek-r1-tool-calling:7b
- query1 = “马的学名是什么?它有什么用途?”
================================ Human Message =================================马的学名是什么?它有什么用途?
================================== Ai Message ==================================
Tool Calls:retrieve (9fcc67a2-e78e-489c-81d3-ed50688c4422)Call ID: 9fcc67a2-e78e-489c-81d3-ed50688c4422Args:query: horse's scientific name
start retrieve: horse's scientific name
================================= Tool Message =================================
Name: retrieveSource: {'row': 2, 'source': 'D:\\project\\programming-with-local-large-language-model\\server\\services\\practice\\assert/animals.csv'}
Content: 名称: 马
学名: Equus ferus caballus
特点: 速度快、耐力强、与人类有深厚的合作关系
作用: 交通、战马、农业、体育竞技
================================== Ai Message ==================================<think>
好的,我现在需要回答用户的问题:“马的学名是什么?它有什么用途?”首先,我查看提供的上下文内容。内容中提到“名称: 马”和“学名: Equus ferus caballus”。所以,马的学名应该是Equus ferus caballus。接下来,关于用途部分,内容里列出了几点:交通、战马、农业、体育竞技。这些都是马被广泛使用的领域。因此,我需要将这些信息整合成一个简洁的回答。最后,确保回答不超过三句话,并且保持简洁明了。这样用户就能清楚地得到学名和用途的信息。
</think>马的学名是Equus ferus caballus。它主要用作交通工具、军事马、农业动物以及体育竞技中的赛马。
不知为什么生成的 工具调用(Tool Calls)中的查询是英文,当然,最终结果不错。
- query2 = “中国有多少个省份?”
================================ Human Message =================================中国有多少个省份?
================================== Ai Message ==================================
Tool Calls:retrieve (3ce0bbec-2612-4e79-85b8-d7f9cbe0d422)Call ID: 3ce0bbec-2612-4e79-85b8-d7f9cbe0d422Args:query: 中国有多少个省份?
start retrieve:中国有多少个省份?
================================= Tool Message =================================
Name: retrieve抱歉,我找不到任何相关信息。
================================== Ai Message ==================================<think>
嗯,用户问的是中国有多少个省份。我记得中国有34个省份,对吧?不过有时候也会加上特别行政区和台湾地区,这样就是36个地方了。但通常在正式统计中,不包括香港和澳门,所以应该是34个省份。我应该确认一下这个数字是否正确 ,确保回答准确。
</think>中国有34个省份。
显然,针对这个问题,大语言模型不应该生成工具调用。最终检索工具未检索出靠谱的文档,大语言模型凭借自己的能力回答了问题。
总结
这次我们实验了一个 通过 是否可以生成工具调用(tool_calls)作为条件来判断是否检索知识库的 RAG(Retrieval Augmented Generation,检索增强生成)
系统。
经过3个大模型的对比实验,我们发现,在处理中文工具生成这方面:qwen2.5
非常稳健。
如果您想自己实现一个包含前端和后端的
RAG
系统,从零搭建langchain+本地大模型+本地矢量数据库的RAG系统 可能对您入门有帮助。
代码
本文涉及的所有代码以及相关资源都已经共享,参见:
- github
- gitee
为便于找到代码,程序文件名称最前面的编号与本系列文章的文档编号相同。
参考
- Build a Retrieval Augmented Generation (RAG) App
🪐感谢您观看,祝好运🪐
相关文章:

本地大模型编程实战(19)RAG(Retrieval Augmented Generation,检索增强生成)(3)
文章目录 准备创建矢量数据库对象创建 LangGraph 链将检索步骤转化为工具定义节点构建图 见证效果qwen2.5llama3.1MFDoom/deepseek-r1-tool-calling:7b 总结代码参考 上一篇文章我们演练了一个 用 langgraph 实现的 RAG(Retrieval Augmented Generation,检索增强生成) 系统。本…...
DeepSeek与ChatGPT:AI语言模型的全面对决
DeepSeek与ChatGPT:AI语言模型的全面对决 引言:AI 语言模型的时代浪潮一、认识 DeepSeek 与 ChatGPT(一)DeepSeek:国产新星的崛起(二)ChatGPT:AI 界的开拓者 二、DeepSeek 与 ChatGP…...
2024年年终总结
2024年终于过去了,这绝对是我人生中最惨痛的一年!被小人欺骗、被庸人耽误、被自己蠢到!不由的让我想起了22年那次算命,算命先生说我十年低谷期,如果从15年进创业公司开始,24年是最后一年,果然应…...
利用 Valgrind 检测 C++ 内存泄露
Valgrind 是一款运行在 Linux 系统上的编程工具集,主要用于调试和分析程序的性能、内存使用等问题。其中最常用的工具是 Memcheck,它可以帮助检测 C 和 C 程序中的内存管理错误,如内存泄漏、使用未初始化的内存、越界访问等。 安装 这里我以…...
Python中的HTTP客户端库:httpx与request | python小知识
Python中的HTTP客户端库:httpx与request | python小知识 在Python中,发送HTTP请求和处理响应是网络编程的基础。requests和httpx是两个常用的HTTP库,它们都提供了简洁易用的API来发送HTTP请求。然而,httpx作为新一代的HTTP客户端…...

【Python】Python入门基础——环境搭建
学习Python,首先需要搭建一个本地开发环境,或是使用线上开发环境(各类练习网站),这里主要记录本地开发环境的配置。 目录: 一、下载和安装python解释器 官网下载地址:Download Python | Pytho…...

2025 pwn_A_childs_dream
文章目录 fc/sfc mesen下载和使用推荐 fc/sfc https://www.mesen.ca/docs/ mesen2安装,vscode安装zg 任天堂yyds w d 左右移动 u结束游戏 i崩溃或者卡死了 L暂停 D658地方有个flag 发现DEEE会使用他。且只有这个地方,maybe会输出flag,应…...
面试题整理:操作系统
文章目录 操作系统操作系统基础1. 操作系统的功能?2. 什么是用户态和内核态? 进程和线程1. 是什么?区别?2. ⭐线程间的同步的方式有哪些?3. PCB 是什么?包含哪些信息?4. 进程的状态有哪些&#…...

构建未来教育的基石:智慧校园与信息的重要性
随着科技的迅猛发展,教育领域正经历一场深刻的变革。在这个过程中,“智慧校园”作为教育信息化的重要实践,扮演着不可或缺的角色。智慧校园不仅仅是硬件设施的升级,更是一种全新的教育理念,强调利用信息技术优化教育资…...
C# 控制台相关 API 与随机数API
C# 控制台相关 API 与随机数API 控制台输入输出 功能说明 Console.WriteLine(string): 输出字符串并换行Console.Write(string, string): 输出字符串不换行Console.ReadLine(): 等待用户输入并返回字符串Console.ReadKey(bool).KeyChar: 读取按键,指定是否显示输…...

【踩坑】⭐️MyBatis的Mapper接口中不建议使用重载方法
目录 🍸前言 🍻一、背景 🍹二、问题处理 💞️三、处理方法 🍸前言 小伙伴们大家好,很久没有水..不是,写文章了,都收到系统的消息了;我算下时间,上周是单休…...
CSS Grid 网格布局,以及 Flexbox 弹性盒布局模型,它们的适用场景是什么?
CSS Grid网格布局和Flexbox弹性盒布局模型都是现代CSS布局的重要工具,它们各自具有独特的优势和适用场景。 作为前端开发工程师,理解这些布局模型的差异和适用场景对于编写高效、可维护的代码至关重要。 CSS Grid网格布局 适用场景: 复杂…...

HDFS体系结构
HDFS 支持主从结 构 , 主节 点 称为 NameNode ,从节点称为 DataNode HDFS中还包含一个 SecondaryNameNode 进程,只要辅助主节点 公司BOSS:NameNode (NN) 秘书:SecondaryNameNode (2NN) 员工&a…...
AI大模型的技术突破与传媒行业变革
性能与成本:AI大模型的“双轮驱动” 过去几年,AI大模型的发展经历了从实验室到产业化的关键转折。2025年初,以DeepSeek R1为代表的模型在数学推理、代码生成等任务中表现超越国际头部产品,而训练成本仅为传统模型的几十分之一。这…...

vscode/cursor+godot C#中使用socketIO
在 Visual Studio Code(VS Code)中安装 NuGet 包(例如SocketIOClient),你可以通过以下几种方法: 方法 1:使用dotnet cli 打开终端:在 VS Code 中按下Ctrl 或者通过菜单View -> Terminal打开终端。 导…...
分段线性插值
分段线性插值 分段线性插值,就是将插值点用折线段连接起来逼近f(x)。设已知节点 a x 0 < x 1 < ⋅ ⋅ ⋅ < x n b ax_0<x_1<<x_nb ax0<x1<⋅⋅⋅<xnb上的函数值 f 0 , f 1 , . . . , f n f_0,f_1,...,f_n f0,f1,...,fn&a…...

制作一个项目用于研究elementUI的源码
需求:修改el-tooltip的颜色,发现传递参数等方法都不太好用,也可以使用打断点的方式,但也有点麻烦,因此打算直接修改源码,把组件逻辑给修改了 第一步下载源码 源码地址 GitHub - ElemeFE/element: A Vue.j…...

[AI]从零开始的llama.cpp部署与DeepSeek格式转换、量化、运行教程
一、前言 在上一次的DeepSeek的部署教程中,我们使用Ollama与LM Studio很轻松的部署了DeepSeek并且也完成了相关API的调用,如果还有不会的小伙伴请看下面的教程: DeepSeek本地部署:[AI]从零开始的DeepSeek本地部署及本地API调用教…...
vLLM专题(二):安装-CPU
vLLM 是一个 Python 库,支持以下 CPU 变体。选择您的 CPU 类型以查看供应商特定的说明: Intel/AMD x86 vLLM 最初支持在 x86 CPU 平台上进行基本模型推理和服务,支持的数据类型包括 FP32、FP16 和 BF16。 注意 此设备没有预构建的 wheel 包或镜像,因此您必须从源代码构建 v…...

JVM 底层探秘:对象创建的详细流程、内存分配机制解析以及线程安全保障策略
文章目录 1. 类加载检查2. 内存分配① 指针碰撞② 空闲列表线程安全问题: 3. 内存空间初始化4. 对象头设置5. 对象初始化 当Java虚拟机遇到一条 new指令时,会执行以下步骤来创建对象: 1. 类加载检查 首先检查new指令的参数是否能在常量池中…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
CppCon 2015 学习:Reactive Stream Processing in Industrial IoT using DDS and Rx
“Reactive Stream Processing in Industrial IoT using DDS and Rx” 是指在工业物联网(IIoT)场景中,结合 DDS(Data Distribution Service) 和 Rx(Reactive Extensions) 技术,实现 …...

SQL注入篇-sqlmap的配置和使用
在之前的皮卡丘靶场第五期SQL注入的内容中我们谈到了sqlmap,但是由于很多朋友看不了解命令行格式,所以是纯手动获取数据库信息的 接下来我们就用sqlmap来进行皮卡丘靶场的sql注入学习,链接:https://wwhc.lanzoue.com/ifJY32ybh6vc…...

【技巧】dify前端源代码修改第一弹-增加tab页
回到目录 【技巧】dify前端源代码修改第一弹-增加tab页 尝试修改dify的前端源代码,在知识库增加一个tab页"HELLO WORLD",完成后的效果如下 [gif01] 1. 前端代码进入调试模式 参考 【部署】win10的wsl环境下启动dify的web前端服务 启动调试…...

篇章一 论坛系统——前置知识
目录 1.软件开发 1.1 软件的生命周期 1.2 面向对象 1.3 CS、BS架构 1.CS架构编辑 2.BS架构 1.4 软件需求 1.需求分类 2.需求获取 1.5 需求分析 1. 工作内容 1.6 面向对象分析 1.OOA的任务 2.统一建模语言UML 3. 用例模型 3.1 用例图的元素 3.2 建立用例模型 …...