Speckly:基于Speckle文档的RAG智能问答机器人

机器学习工程师福音:超好用的Streamlit简介! 全文共4706字,预计学习时长9分钟 本文将介绍一个机器学习工程师专用的应用程序框架Streamlit。 用300行Python代码中的实时神经网络推理生成语义搜索引擎。 根据自身经验,每一个非平凡机器学习项目最终都布满漏洞,其内部工具均难以维护。这些工具通常是由Jupyter Notebook和Flask应用程序拼凑而成,故很难部署,需要对主从式架构进行推理,并且不能很好地与Tenso... 阅读详情

前言

Speckly 是一个基于 检索增强生成 (RAG) 技术的智能问答机器人,它能像一位经验丰富的工程师,理解你的问题,并从 Speckle 文档中精准地找到答案。更厉害的是,它甚至可以帮你生成代码片段!🚀

本文将详细介绍 Speckly 的完整开发流程,涵盖从创建图管道到搭建服务器,再到设计用户界面的所有环节,最终实现一个可交互的智能问答系统。

您将学习如何:

  • 构建处理用户提问和文档信息的核心逻辑(图管道)。
  • 搭建本地服务器,模拟 Speckly 的线上运行环境。
  • 使用 Streamlit 和 Gradio 设计用户友好的交互界面。

通过学习本项目,您将掌握在部署模型到生产环境之前进行本地测试的方法,并了解如何构建简洁易用的用户界面。

步骤 1:导入 API 密钥

首先,我们从 .env 文件中导入 API 密钥。另外,我们还可以使用 LangSmith 设置跟踪。

import osfrom dotenv import load_dotenv, find_dotenvload_dotenv(find_dotenv()) 重要提示:如果无法加载 API 密钥,请检查此行Getting the api keys from the .env fileos.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY')os.environ['LANGCHAIN_API_KEY'] = os.getenv('LANGCHAIN_API_KEY')Langsmith Tracingos.environ['LANGCHAIN_TRACING_V2'] = os.getenv('LANGCHAIN_TRACING_V2')os.environ['LANGCHAIN_ENDPOINT'] = os.getenv('LANGCHAIN_ENDPOINT')os.environ['LANGCHAIN_PROJECT'] = os.getenv('LANGCHAIN_PROJECT')Fire Crawl APIos.environ['FIRE_API_KEY']=os.getenv('FIRE_API_KEY')

以下是一个示例 .env 文件。请获取您的 API 密钥(如果您没有),并将它们粘贴在字符串之间。我在第一篇博文中详细描述了这一点。

OPENAI_API_KEY=''
LANGCHAIN_API_KEY=''
LANGCHAIN_TRACING_V2='true'
LANGCHAIN_ENDPOINT='https://api.smith.langchain.com'
LANGCHAIN_PROJECT=''

步骤 2:加载文档

我们将使用 Mendable.ai 创建的名为 FireCrawl 的产品,它可以将网站转换为对大语言模型友好的文档。这正是我们想要的。我们将抓取 Speckle 的开发者文档,并将所有页面和子页面转换为文档列表。您需要一个 API 密钥才能在加载器函数中使用。

我创建了 DocumentLoader 类,它将 API 密钥作为字符串输入,并使用 get_docs 函数,该函数将 URL 作为输入,并提供一个文档列表(包括元数据)作为输出。

from typing import List
from langchain_community.document_loaders import FireCrawlLoader
from document import Document

class DocumentLoader:
    def __init__(self, api_key: str):
        self.api_key = api_key

    def get_docs(self, url: str) -> List[Document]:
        """
        使用 FireCrawlLoader 从指定的 URL 检索文档。

        Args:
            url (str): 要抓取文档的 URL。

        Returns:
            List[Document]: 包含检索到的内容的 Document 对象列表。
        """
        loader = FireCrawlLoader(
            api_key=self.api_key, url=url, mode="crawl"
        )

        raw_docs = loader.load()
        docs = [Document(page_content=doc.page_content, metadata=doc.metadata) for doc in raw_docs]

        return docs

就我而言,我已经抓取了文档,并将文档保存在本地,这样我就不会重复这个过程并浪费我的积分了。第一次使用时,您可以使用 get_docs 函数;之后,您可以直接加载文档。

import pickle

# 从本地文件加载已抓取并保存的文档
with open("crawled_docs/saved_docs.pkl", "rb") as f:
    saved_docs = pickle.load(f)

步骤 3:创建向量存储和检索器

现在我们有了文档,我们希望将它们分成更小的部分,并将嵌入存储在一个开源向量存储中以供检索。我们将依赖 OpenAI 嵌入模型和 FAISS 向量存储。您还可以选择提供一个路径,以便将向量存储保存在本地。

from typing import List, Optional
from langchain_openai import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter

def create_vector_store(docs, store_path: Optional[str] = None) -> FAISS:
    """
    从文档列表创建 FAISS 向量存储。

    Args:
        docs (List[Document]): 包含要存储的内容的 Document 对象列表。
        store_path (Optional[str]): 用于在本地存储向量存储的路径。如果为 None,则不会存储向量存储。

    Returns:
        FAISS: 包含文档的 FAISS 向量存储。
    """
    # 创建文本拆分器
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
    )

    texts = text_splitter.split_documents(docs)

    # 嵌入对象
    embedding_model = OpenAIEmbeddings()

    # 创建 FAISS 向量存储
    store = FAISS.from_documents(texts, embedding_model)

    # 如果提供了路径,则将向量存储保存在本地
    if store_path:
        store.save_local(store_path)

    return store


# 创建向量存储
store = create_vector_store(saved_docs)

# 创建检索器
retriever = store.as_retriever()

步骤 4:创建用于响应生成的检索链

现在,我们将创建 create_generate_chain 函数来创建一个响应生成链。该函数首先使用一个 generate_template 来提供有关该过程的详细说明。这个模板有两个占位符:{context} 用于存储相关信息,{input} 用于存储问题。然后,使用 LangChain 中的 PromptTemplate 模块,它接受两个变量:template = generate_template 和 input_variables = ["context", "input"]。最后一步是使用 generate_prompt、大语言模型和 StrOutputParser() 创建 generate_chain。

# generate_chain.py
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

def create_generate_chain(llm):
    """
    创建一个用于回答代码相关问题的生成链。

    Args:
        llm (LLM): 用于生成响应的语言模型。

    Returns:
        一个可调用函数,它接受上下文和问题作为输入,并返回字符串响应。
    """
    generate_template = """
    你是一个名为 Speckly 的乐于助人的代码助手。用户向你提供了一个与代码相关的问题,其内容由以下上下文部分表示(由 <context></context> 分隔)。
    使用这些来回答最后的问题。
    这些文件涉及 Speckle 开发者文档。你可以假设用户是土木工程师、建筑师或软件开发人员。
    如果你不知道答案,就说你不知道。不要试图编造答案。
    如果问题与上下文无关,请礼貌地回复说你只回答与上下文相关的问题。
    提供尽可能详细的答案,并使用 Python(默认)生成代码,除非用户在问题中特别说明。

    <context>
    {context}
    </context>

    <question>
    {input}
    </question>
    """

    generate_prompt = PromptTemplate(template=generate_template, input_variables=["context", "input"])

    # 创建生成链
    generate_chain = generate_prompt | llm | StrOutputParser()

    return generate_chain


# 创建生成链
generate_chain = create_generate_chain(llm)

这里需要注意的是,StrOutputParser() 用于从大语言模型获取字符串输出。否则,输出可能很复杂,例如 JSON 或结构化消息对象,这些对象无法直接用于进一步处理或向用户显示。没有 StrOutputParser() 的输出可能如下所示:

{
    "content": "This is the response from the LLM.",
    "metadata": {
        "confidence": 0.8,
        "response_time": 0.5
    }
}

而使用 StrOutputParser(),输出如下所示:

This is the response from the LLM.

步骤 5:创建评分器

在这一步中,我们将创建不同的评分器,用于评估检索到的文档与用户问题的相关性、评估生成的答案、检查答案是否合理,以及在没有获得相关文档时重新编写查询。

检索评分器img

首先,我们创建一个检索评分器,以评估检索到的文档与用户问题的相关性。为此,我们定义一个 create_retrieval_grader 函数,该函数接受一个带有新指令的提示模板 grade_prompt。该函数指示评分器在文档中查找与用户问题相关的关键字。如果存在此类关键字,则该文档被视为相关。评分器应该提供一个二进制分数,“yes” 或 “no”,表示该文档是否与问题相关,并以 JSON 格式提供结果,其中包含一个键“score”。

def create_retrieval_grader(model):
    """
    创建一个检索评分器,用于评估检索到的文档与用户问题的相关性。

    Returns:
        一个可调用函数,它接受文档和问题作为输入,并返回一个 JSON 对象,其中包含一个二进制分数,表示该文档是否与问题相关。
    """
    grade_prompt = PromptTemplate(
        template="""
        <|begin_of_text|><|start_header_id|>system<|end_header_id|>
        你是一个评分器,负责评估检索到的文档与用户问题的相关性。如果文档包含与用户问题相关的关键字,则将其评级为相关。它不需要是一个严格的测试。目标是过滤掉错误的检索结果。
        给出一个二进制分数“yes”或“no”,表示该文档是否与问题相关。
        以 JSON 格式提供二进制分数,其中包含一个键“score”,并且没有前言或解释。
        <|eot_id|>
        <|start_header_id|>user<|end_header_id|>

        以下是检索到的文档: \n\n {document} \n\n
        以下是用户问题: {input} \n
        <|eot_id|>
        <|start_header_id|>assistant<|end_header_id|>
        """,
        input_variables=["document", "input"],
    )

    # 创建检索器链
    retriever_grader = grade_prompt | model | JsonOutputParser()

    return retriever_grader

以下是一个示例:

model = ... 在此处提供您的 llm
grader = create_retrieval_grader(model)
document = "France is a country in Europe. Paris is the capital of France."
question = "What is the capital of France?"
score = grader(document, question)
print(score)  输出: {"score": "yes"}

幻觉评分器img

接下来,我们定义一个幻觉评分器,用于评估从大语言模型获得的答案是否基于一组事实。该评分器提供一个二进制分数(“yes”或“no”),表示答案是否合理。提示模板将包括事实 ({documents}) 和答案 ({generation}) 的占位符,这些占位符将在使用提示时填充。

def create_hallucination_grader(self):
    """
    创建一个幻觉评分器,用于评估答案是否基于/得到一组事实的支持。

    Returns:
        一个可调用函数,它接受生成(答案)和文档列表(事实)作为输入,并返回一个 JSON 对象,其中包含一个二进制分数,表示答案是否基于/得到事实的支持。
    """
    hallucination_prompt = PromptTemplate(
        template="""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
        你是一个评分器,负责评估答案是否基于/得到一组事实的支持。给出一个二进制分数“yes”或“no”,表示答案是否基于/得到一组事实的支持。以 JSON 格式提供二进制分数,其中包含一个键“score”,并且没有前言或解释。
        <|eot_id|>
        <|start_header_id|>user<|end_header_id|>
        以下是事实:
        \n ------- \n
        {documents}
        \n ------- \n
        以下是答案: {generation}
        <|eot_id|>
        <|start_header_id|>assistant<|end_header_id|>""",
        input_variables=["generation", "documents"],
    )

    hallucination_grader = hallucination_prompt | self.model | JsonOutputParser()

    return hallucination_grader

以下是一个示例:

from langchain_openai import ChatOpenAI

## LLM model
model = ChatOpenAI(model="gpt-4o", temperature=0)
## Grader
grader = create_hallucination_grader(model)
answer = "The capital of France is Paris."
facts = ["France is a country in Europe.", "Paris is the capital of France."]
score = grader(answer, facts)
print(score)  # 输出: {"score": "yes"}

代码评估器img

接下来,我们定义一个 create_code_evaluator 函数,该函数创建一个代码评估器,以评估生成的代码是否正确以及是否与给定问题相关。它使用 PromptTemplate 指示评估器提供一个带有二进制分数和反馈的 JSON 响应。评估器接受生成(代码)、问题和文档列表作为输入,并返回一个 JSON 对象,其中包含一个分数(表示代码是否正确和相关)以及对评估的简要说明。

def create_code_evaluator(self):
    """
    创建一个代码评估器,用于评估生成的代码是否正确以及是否与给定问题相关。

    Returns:
        一个可调用函数,它接受生成(代码)、问题和文档列表作为输入,并返回一个带有二进制分数和反馈的 JSON 对象。
    """
    eval_template = PromptTemplate(
        template="""<|begin_of_text|><|start_header_id|>system<|end_header_id|> 你是一个代码评估器,负责评估生成的代码是否正确以及是否与给定问题相关。
        提供一个带有以下键的 JSON 响应:

        “score”:一个二进制分数“yes”或“no”,表示代码是否正确和相关。
        “feedback”:对你的评估的简要说明,包括任何问题或需要改进的地方。

        <|eot_id|><|start_header_id|>user<|end_header_id|>
        以下是生成的代码:
        \n ------- \n
        {generation}
        \n ------- \n
        以下是问题: {input}
        \n ------- \n
        以下是相关文档: {documents}
        <|eot_id|><|start_header_id|>assistant<|end_header_id|>""",
        input_variables=["generation", "input", "documents"],
    )

    code_evaluator = eval_template | self.model | JsonOutputParser()

    return code_evaluator

以下是一个使用示例:

model = ...  # 初始化一个语言模型

code_evaluator = create_code_evaluator(model)

code = "def greet(name): return f'Hello, {name}!'"
question = "Write a function to greet someone by name."
documents = ["A function should take a name as input and return a greeting message."]

result = code_evaluator(code, question, documents)
print(result)  # 输出: {"score": "yes", "feedback": "The code is correct and relevant to the question."}

最后,我们创建 create_question_rewriter 函数,该函数构建一个重写器链,用于优化给定问题,以增强其清晰度和相关性。此函数返回一个可调用函数,该函数接受一个问题作为输入,并输出重写后的问题作为字符串。

def create_question_rewriter(model):
    """
    创建一个问题重写器链,用于重写给定问题以提高其清晰度和相关性。

    Returns:
        一个可调用函数,它接受一个问题作为输入,并返回重写后的问题作为字符串。
    """
    re_write_prompt = hub.pull("efriis/self-rag-question-rewriter")
    question_rewriter = re_write_prompt | self.model | StrOutputParser()

    return question_rewriter

以下是一个使用示例:

rewriter = create_question_rewriter()
original_question = "how to use speckle's python sdk?"
rewritten_question = rewriter(original_question)
print(rewritten_question)  # 输出: "How to install speckle's python sdk?"

现在我们已经定义了所有组件,我们可以创建一个名为 GraderUtils 的类来包含所有这些函数。然后,我们可以使用我们的 LLM 模型作为唯一必要的输入来初始化这个类的一个实例。

from langchain_openai import ChatOpenAI

class GraderUtils:
    def __init__(self, model):
        self.model = model

    def create_retrieval_grader(self):
          ...
        
    def create_hallucination_grader(self):
          ...
        
    def create_code_evaluator(self):
          ... 

    def create_question_rewriter(self):
          ...

## LLM model
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 创建 GraderUtils 类的一个实例
grader = GraderUtils(llm)

# 获取检索评分器
retrieval_grader = grader.create_retrieval_grader()

# 获取幻觉评分器
hallucination_grader = grader.create_hallucination_grader()

# 获取代码评估器
code_evaluator = grader.create_code_evaluator()

# 获取问题重写器
question_rewriter = grader.create_question_rewriter()

想要了解更多信息,您可以参考 langchain-ai 仓库中的 RAG 笔记本。

步骤 6:创建图

现在我们已经拥有了所有组件,接下来我们将使用 LangGraph 创建图。

定义图的状态img

首先,我们定义一个 GraphState 类来表示图的状态,该状态包含三个关键属性:input、generation 和 documents。其中,input 属性存储用户输入的问题,generation 属性存储大语言模型根据输入生成的答案,documents 属性存储相关文档列表。

from typing_extensions import TypedDict
from typing import List

class GraphState(TypedDict):
    """
    表示图的状态。

    Attributes:
        question: 问题
        generation: LLM 生成
        documents: 文档列表
    """

    input: str
    generation: str
    documents: str #List[str]

GraphState 中定义的状态在整个图中全局可访问,并且这些属性是节点函数可以修改的唯一变量。

节点img

接下来,我们定义节点。节点是 Python 函数,它们接收图的状态,执行一些操作,并修改状态变量。我们定义一个名为 GraphNodes 的类来包含所有节点函数。

from document import Document
from utils.generate_chain import create_generate_chain

class GraphNodes:
    def __init__(self, llm, retriever, retrieval_grader, hallucination_grader, code_evaluator, question_rewriter):
        self.llm = llm
        self.retriever = retriever
        self.retrieval_grader = retrieval_grader
        self.hallucination_grader = hallucination_grader
        self.code_evaluator = code_evaluator
        self.question_rewriter = question_rewriter
        self.generate_chain = create_generate_chain(llm)

    def retrieve(self, state):
        """
        检索文档

        Args:
            state (dict): 当前图状态

        Returns:
            state (dict): 添加到状态的新键,文档,其中包含检索到的文档
        """
        print("---RETRIEVE---")
        question = state["input"]

        # 检索
        documents = self.retriever.invoke(question)
        return {"documents": documents, "input": question}

    def generate(self, state):
        """
        生成答案

        Args:
            state (dict): 当前图状态

        Returns:
            state (dict): 添加到状态的新键,生成,其中包含 LLM 生成
        """
        print("---GENERATE---")
        question = state["input"]
        documents = state["documents"]

        # RAG 生成
        generation = self.generate_chain.invoke({"context": documents, "input": question})
        return {"documents": documents, "input": question, "generation": generation}

    def grade_documents(self, state):
        """
        确定检索到的文档是否与问题相关。

        Args:
            state (dict): 当前图状态

        Returns:
            state (dict): 使用仅过滤后的相关文档更新文档键
        """
        print("---CHECK DOCUMENT RELEVANCE TO QUESTION---")
        question = state["input"]
        documents = state["documents"]

        # 对每个文档进行评分
        filtered_docs = []

        for d in documents:
            score = self.retrieval_grader.invoke({"input": question, "document": d.page_content})
            grade = score["score"]
            if grade == "yes":
                print("---GRADE: DOCUMENT RELEVANT---")
                filtered_docs.append(d)
            else:
                print("---GRADE: DOCUMENT IR-RELEVANT---")
                continue

        return {"documents": filtered_docs, "input": question}

    def transform_query(self, state):
        """
        转换查询以生成更好的问题。

        Args:
            state (dict): 当前图状态

        Returns:
            state (dict): 使用重新表述的问题更新问题键
        """
        print("---TRANSFORM QUERY---")
        question = state["input"]
        documents = state["documents"]

        # 重新编写问题
        better_question = self.question_rewriter.invoke({"input": question})
        return {"documents": documents, "input": better_question}
  • retrieve:根据输入问题检索文档,并将它们添加到图状态中。
  • generate:使用输入问题和检索到的文档生成答案,并将生成添加到图状态中。
  • grade_documents:根据检索到的文档与输入问题的相关性对其进行过滤,仅使用相关文档更新图状态。
  • transform_query:重新表述输入问题以提高其清晰度和相关性,使用转换后的问题更新图状态。

边img

边函数引导图处理流程,根据当前状态和各种节点函数的结果做出决策。

class EdgeGraph:
    def __init__(self, hallucination_grader, code_evaluator):
        self.hallucination_grader = hallucination_grader
        self.code_evaluator = code_evaluator

    def decide_to_generate(self, state):
        """
        确定是生成答案还是重新生成问题。

        Args:
            state (dict): 当前图状态

        Returns:
            str: 对要调用的下一个节点的二进制决策
        """
        print("---ASSESS GRADED DOCUMENTS---")
        question = state["input"]
        filtered_documents = state["documents"]

        if not filtered_documents:
            # 所有文档都已过滤 check_relevance
            # 我们将重新生成一个新查询
            print("---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---")
            return "transform_query"  # "retrieve_from_community_page", "transform_query"
        else:
            # 我们有相关文档,因此生成答案
            print("---DECISION: GENERATE---")
            return "generate"

    def grade_generation_v_documents_and_question(self, state):
        """
        确定生成是否基于文档并回答问题。

        Args:
            state (dict): 当前图状态

        Returns:
            str: 对要调用的下一个节点的决策
        """
        print("---CHECK HALLUCINATIONS---")
        question = state["input"]
        documents = state["documents"]
        generation = state["generation"]

        score = self.hallucination_grader.invoke({"documents": documents, "generation": generation})
        grade = score["score"]

        # 检查幻觉
        if grade == "yes":
            print("---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---")
            # 检查问答
            print("---GRADE GENERATION vs QUESTION---")
            score = self.code_evaluator.invoke({"input": question, "generation": generation, "documents": documents})
            grade = score["score"]
            if grade == "yes":
                print("---DECISION: GENERATION ADDRESSES QUESTION---")
                return "useful"
            else:
                print("---DECISION: GENERATION DOES NOT ADDRESS QUESTION---")
                return "not useful"
        else:
            print("---DECISION: GENERATIONS ARE HALLUCINATED, RE-TRY---")
            return "not supported"

EdgeGraph 类定义了以下边函数:

  • decide_to_generate:根据过滤后的文档与输入问题的相关性,决定是生成答案还是重新生成问题。
  • grade_generation_v_documents_and_question:根据生成的答案是否基于文档及其回答问题的能力来评估生成的答案。

构建图img

现在我们已经定义了图状态、节点和边函数,我们可以开始构建图了。

# 初始化图
workflow = StateGraph(GraphState)
# 创建 GraphNodes 类的一个实例
graph_nodes = GraphNodes(llm, retriever, retrieval_grader, hallucination_grader, code_evaluator, question_rewriter)
# 创建 EdgeGraph 类的一个实例
edge_graph = EdgeGraph(hallucination_grader, code_evaluator)
# 定义节点
workflow.add_node("retrieve", graph_nodes.retrieve) # 检索文档
workflow.add_node("grade_documents", graph_nodes.grade_documents)  # 对文档进行评分
workflow.add_node("generate", graph_nodes.generate) # 生成答案
workflow.add_node("transform_query", graph_nodes.transform_query)  # 转换查询
# 构建图
workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "grade_documents")
workflow.add_conditional_edges(
    "grade_documents",
    edge_graph.decide_to_generate,
    {
        "transform_query": "transform_query", # "transform_query": "transform_query",
        "generate": "generate",
    },
)
workflow.add_edge("transform_query", "retrieve")
workflow.add_conditional_edges(
    "generate",
    edge_graph.grade_generation_v_documents_and_question,
    {
        "not supported": "generate",
        "useful": END,
        "not useful": "transform_query", # "transform_query"
    },
)
# 编译
chain = workflow.compile()

首先,我们使用 StateGraph 类初始化图。然后,我们创建 GraphNodes 和 EdgeGraph 类的实例。接下来,我们添加已经定义函数的节点:

  • retrieve:根据输入问题检索相关文档。
  • grade_documents:根据检索到的文档与问题的相关性对其进行过滤。
  • generate:根据过滤后的文档生成答案。
  • transform_query:转换输入问题以提高其清晰度和相关性。

图的起始节点是 retrieve 节点。retrieve 节点和 grade_documents 节点之间有一条普通边连接。在 grade_documents 节点之后,工作流到达一个条件边。此时,会调用 edge_graph.decide_to_generate 函数来决定工作流的下一步。该函数评估已评分的文档,并决定是转换查询还是生成答案。如果函数返回 transform_query,则工作流将移动到 transform_query 节点,该节点转换输入问题以提高其清晰度和相关性。如果函数返回 generate,则工作流将移动到 generate 节点,该节点根据过滤后的文档生成答案。

transform_query 和 retrieve 之间也有一条普通边连接。这是因为在转换查询之后,工作流会移回 retrieve 节点,以根据转换后的查询检索新文档。

生成答案后,工作流到达一个条件边。此时,会调用 edge_graph.grade_generation_v_documents_and_question 函数,根据生成的答案是否基于文档及其回答问题的能力来评估生成的答案。如果函数返回 not supported,则工作流将移回 generate 节点以重新生成答案。此步骤对于确保工作流生成受文档支持的答案是必需的。如果函数返回 useful,则工作流将结束,表示已生成有用的答案。如果函数返回 not useful,则工作流将移动到 transform_query 节点以再次转换查询。

最后,我们将编译图以将其转换为可执行链。

在这里插入图片描述

步骤 7:使用 FastAPI 启动服务器

现在,我们将使用 FastAPI 启动服务器。

首先,我们创建一个 FastAPI 应用程序。

app = FastAPI(
    title="Speckle服务器",
    version="1.0",
    description="用于回答有关Speckle Developer Docs的问题的API服务器"
)

接下来,我们为根 URL (/) 定义一个路由,该路由重定向到文档 URL (/docs)。

@app.get("/")
async def redirect_root_to_docs():
    return RedirectResponse("/docs")

然后,我们使用 Pydantic 的 BaseModel 定义两个模型:Input 和 Ou``tput。这些模型将用于定义 API 的输入和输出数据的结构。

class Input(BaseModel):
    input: str

class Output(BaseModel):
    output: dict

接下来,我们使用 add_routes 函数向应用程序添加路由。

add_routes(
    app,
    chain.with_types(input_type=Input, output_type=Output),
    path="/speckle_chat",
)

最后,我们使用 Uvicorn 运行服务器。

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8000)

现在,我们已经创建了一个 FastAPI 应用程序,并启动了一个可以从 http://localhost:8000 访问的服务器。

步骤 8:使用 Streamlit/Gradio 创建客户端

现在,我们将创建客户端来与服务器进行交互。

使用 Streamlit 创建 UIimg

首先,我们使用 Python 中的 Streamlit 库创建一个客户端。

import streamlit as st
from langserve import RemoteRunnable
from pprint import pprint

st.title('Welcome to Speckle Server')
input_text = st.text_input('ask speckle related question here')

if input_text:
    with st.spinner("Processing..."):
        try:
            app = RemoteRunnable("http://localhost:8000/speckle_chat/")
            for output in app.stream({"input": input_text}):
                for key, value in output.items():
                    # 节点
                    pprint(f"Node '{key}':")
                    # 可选:在每个节点打印完整状态
                    # pprint.pprint(value["keys"], indent=2, width=80, depth=None)
                pprint("\n---\n")
            output = value['generation']  
            st.write(output)
        
        except Exception as e:
            st.error(f"Error: {e}")

我们首先设置 Streamlit 应用程序,包括一个标题和一个文本输入字段,供用户输入问题。当用户输入任何文本时,应用程序会显示一个微调器,表示正在处理输入。然后,应用程序使用 langserve 中的 RemoteRunnable 模块和服务器 URL 连接到服务器。它使用 stream 命令流式传输来自大语言模型的响应,同时打印图工作流中触发的节点。最后,我们从存储在值字典中的 generation 键中检索最终输出。如果在处理过程中出现错误,它将显示错误消息。

在这里插入图片描述

使用 Gradio 创建 UIimg

我们还可以使用 Gradio 来创建客户端 UI。Gradio 是一个开源 Python 库,用于为机器学习模型、API 和任意 Python 函数创建交互式基于 Web 的用户界面。

首先,我们创建一个函数,该函数将允许从大语言模型中获取最终响应。

def get_response(input_text):
    app = RemoteRunnable("http://localhost:8000/speckle_chat/")
    for output in app.stream({"input": input_text}):
        for key, value in output.items():
            # 节点
            pprint(f"Node '{key}':")
            # 可选:在每个节点打印完整状态
            # pprint.pprint(value["keys"], indent=2, width=80, depth=None)
        pprint("\n---\n")
    output = value['generation']
    return output

然后,我们创建一个简单的 Gradio UI。

import gradio as gr
from langserve import RemoteRunnable
from pprint import pprint

# 在 Gradio 中创建 UI
iface = gr.Interface(fn=get_response, 
          inputs=gr.Textbox(
          value="Enter your question"), 
          outputs="textbox",  
          title="Q&A over Speckle's developer docs",
          description="Ask a question about Speckle's developer docs and get an answer from the code assistant. This assistant looks up relevant documents and answers your code-related question.",
          examples=[["How do I install Speckle's python sdk?"], 
                  ["How to commit and retrieve an object from Speckle?"],
                  ],
          theme=gr.themes.Soft(),
          allow_flagging="never",)

iface.launch(share=True) # 将 share 设置为 True 以获取公共 URL

在 launch 函数中设置 share=True 可以获取公共 URL。

总结

今天,我们探讨了如何为包含高级 RAG(检索增强生成)概念的图工作流开发服务器-客户端架构。我们创建了一个服务器组件,该组件涵盖了一个全面的管道,包括对检索到的文档进行评分、对响应进行评分、检查幻觉和查询重写。为了与此本地服务器交互,我们创建了两个客户端应用程序,一个使用 Streamlit,另一个使用 Gradio。这两个 UI 都为用户提供了一个友好的界面,让他们可以输入查询并实时接收服务器的响应。这是一个端到端的项目,允许开发人员在将应用程序部署到生产环境之前构建应用程序并在本地对其进行测试。

​

如何学习AI大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

政府科技管理者如何精准识别符合本地产业需求的重点科技成果?.docx 政府科技管理者如何精准识别符合本地产业需求的重点科技成果? 立即下载

相关推荐

高校技术转移办公室如何高效对接企业技术需求?.docx

高校技术转移办公室如何高效对接企业技术需求?

streamlit+neo4j+分词实现智能医疗问答

知识图谱(Knowledge Graph, KG),在图书情报界成为知识域可视化或者知识领域映射地图,是显示知识发展进程与结构关系的一系列各种不同的图像,用可视化技术描述知识资源及其载体,挖掘、分析、构建、绘制和显示知识及他们之间的相互关系。简而言之,知识图谱就是将不同类型的知识通过他们之间的关联性变成一个网状的结构只是,这个结构结构知识在搜索、问答、NLP、推荐系统的应用中作为基石存在。知识图谱的建设,一般可以简述为如下的几个步骤:数据获取、实体识别、关系抽取、数据存储、图谱应用这几个方面。

qq_44747847的博客 2590

PandasAI 数据对话实战包:零 Key 离线问 CSV + 通义千问可选(Python 源码)

自写可跑的 PandasAI 风格数据对话实战包。内置 Mock 引擎,无 API Key 可离线冒烟问 CSV;可选通义千问。含样例数据、中文 README、smoke 脚本。

高斯混合概率假设密度滤波器(GM-PHD)研究(Matlab代码实现)

内容概要:本文介绍了名为“Plecs全桥LLC谐振变换器仿真模型”的技术资源,该模型基于Plecs仿真平台构建,专门用于模拟和分析全桥LLC谐振变换器的工作特性。LLC谐振变换器作为一种高效率、高功率密度的DC-DC变换拓扑,广泛应用于现代电力电子电源系统中。该仿真模型完整集成了变换器的核心电路结构与控制逻辑,能够精确仿真在不同谐振参数、开关频率及负载条件下的系统动态响应,有效支持对软开关实现机制、电压增益特性、效率优化等关键技术问题的深入研究与分析。; 适合人群:电力电子、电气工程及其自动化等相关专业的高校师生、科研人员,以及从事开关电源、新能源变换器设计的工程技术人员。; 使用场景及目标:①用于课堂教学与实验设计,帮助学生掌握LLC谐振变换器的工作原理与仿真建模方法;②作为科研项目前期的理论验证工具,辅助研究人员进行参数优化与控制策略设计;③应用于企业产品研发,加速电源系统的设计迭代过程,提升产品性能与可靠性。; 阅读建议:使用者应具备扎实的电力电子电路基础知识与一定的仿真软件操作经验,建议结合经典电力电子教材与实际硬件测试,对比仿真结果与实验数据,以深化对LLC变换器运行机理的理解,并在此基础上开展创新性研究与优化设计。

政府科技管理者如何提升科技成果转化项目的政策匹配精准度?.docx

政府科技管理者如何提升科技成果转化项目的政策匹配精准度?

高校技术转移办公室人员如何提升科技成果的市场推介效率?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

【顶级EI复现】新扩展移相角下的双有源桥DC-DC 变换器优化控制策略(Simulink仿真实现)

内容概要:本文围绕“新扩展移相角下的双有源桥DC-DC变换器优化控制策略”展开,深入研究了在Simulink仿真环境中对该类变换器进行建模与控制优化的方法。通过引入新型扩展移相角(EPS)调制策略,系统在实现软开关、降低器件应力及提升功率传输效率方面表现出显著优势。文章详细阐述了控制策略的核心原理,包括EPS调制机制的设计、闭环控制系统架构的构建以及关键参数的优化整定,并通过系统的仿真实验验证了该方法在减少开关损耗、增强系统动态响应能力和运行稳定性方面的有效性。同时,文档完整呈现了Simulink仿真模型的搭建流程与仿真结果分析过程,体现了该技术在电力电子高频高效变换领域的先进性与工程应用价值。; 适合人群:具备一定电力电子技术与自动控制理论基础,从事新能源发电、电动汽车、微电网、工业电源等方向研究的研发工程师及高校研究生。; 使用场景及目标:①深入理解双有源桥(DAB)变换器的工作机理与建模范式;②掌握基于Simulink的高级电力电子系统建模与控制策略开发技能;③服务于高水平科研项目复现、学位论文撰写或实际工程原型开发,全面提升对复杂电力电子系统优化控制策略的设计与实践能力。; 阅读建议:建议结合文中提供的Simulink仿真模型与配套资源,边操作边学习,重点关注控制策略的实现逻辑、调制波形生成方式及参数调节对系统性能的影响,同时可参考“荔枝科研社”发布的系列相关资源以深化知识体系与技术应用能力。

【扩展卡尔曼滤波器EKF】基于UWB雷达的SLAM能够在室内环境内映射自然点地标,并改善机器人定位(Matlab代码实现)

内容概要:本文档提供了名为“Plecs全桥LLC谐振变换器仿真模型”的资源,主要用于电力电子领域中LLC谐振变换器的建模与仿真研究。该模型基于Plecs软件平台构建,聚焦于全桥LLC拓扑结构,涵盖其工作原理、关键参数设计及动态性能仿真,帮助用户深入理解LLC谐振变换器在软开关、高效率功率转换方面的特性与实现方法。文档内容包括系统建模、主电路参数计算、控制策略实现以及仿真结果分析等部分,适用于高频高效电源变换器的研发与教学实践。; 适合人群:具备电力电子与电力系统基础知识,从事电源变换器设计、仿真与优化工作的工程师及高校研究生。; 使用场景及目标:① 掌握LLC谐振变换器的工作机理与Plecs仿真建模流程;② 学习全桥LLC拓扑的参数设计与软开关实现方法;③ 为高频高效DC-DC变换器的科研、课程设计或工程项目提供仿真基础和技术参考。; 阅读建议:建议结合电力电子技术教材与Plecs官方文档同步学习,重点理解谐振腔参数(如Lr、Cr、Lm)对增益特性与软开关的影响,并通过调整仿真参数验证不同工况下的系统响应,以加深对LLC变换器设计规律的理解。

改进-基于新扩展移相角下的双有源桥DAB变换器仿真(EPS调制、软开关、最小应力优化)(Simulink仿真实现)

内容概要:本文提出一种基于新扩展移相角控制策略的双有源桥(DAB)直流变换器仿真模型,重点实现了EPS调制方式下的软开关技术与最小应力优化控制。通过Simulink平台构建系统仿真模型,深入优化DAB变换器在双向能量传输过程中的效率与可靠性,有效降低开关器件的电压和电流应力,提升整体系统性能。该方法结合先进控制算法,实现了对功率流动的精确调控,具有良好的动态响应特性与稳态精度,适用于高频隔离、高效率要求的电力电子应用场景,如新能源并网、电动汽车充放电系统及储能系统等。; 适合人群:电力电子、电气工程及其自动化等相关专业的研究生、科研人员以及从事电力变换系统开发的工程师。; 使用场景及目标:①研究DAB变换器在不同负载与输入条件下的软开关实现机制与零电压切换特性;②优化变换器的开关损耗与器件应力分布,提升系统效率与可靠性;③掌握基于Simulink的高级移相控制策略建模与仿真方法,服务于高水平科研项目或实际工程设计。; 阅读建议:建议结合文中所述EPS调制原理与扩展移相角控制逻辑,深入理解其对软开关实现与应力最小化的耦合影响,配合仿真模型进行参数扫描、波形观测与性能对比分析,以全面掌握DAB变换器的动态行为与控制规律。

半桥LLC谐振DCDC变换器的设计与仿真(Simulink仿真实现)

内容概要:本文系统阐述了半桥LLC谐振DCDC变换器的设计与仿真方法,聚焦于其在Simulink环境下的建模、关键参数设计及闭环控制策略的实现。内容深入剖析了LLC谐振变换器的工作原理,包括谐振腔元件(Lr、Cr、Lm)的精确计算与优化方法,详细探讨了实现零电压开通(ZVS)等软开关技术的关键条件,并介绍了变频控制与恒压恒流双闭环控制逻辑的设计思路。通过构建完整的Simulink仿真模型,文章验证了系统在不同负载工况下的动态响应能力、高效率特性和良好的稳态性能,为理论分析提供了强有力的实践支撑。; 适合人群:具备电力电子技术基础理论知识和Simulink软件操作经验的电气工程、自动化及相关专业的本科高年级学生、研究生,以及从事开关电源、新能源变换器等电力电子设备研发的工程师。; 使用场景及目标:①深入理解LLC谐振变换器的核心工作机理与谐振参数的设计准则;②掌握在Simulink中搭建高保真度电力电子变换器仿真模型并实现复杂闭环控制的技术;③为高效率、高功率密度电源产品的研发提供理论指导、设计范例与仿真验证平台。; 阅读建议:建议读者结合文中的理论推导与仿真模型进行同步实践,重点动手调整谐振频率、观察ZVS实现过程,并反复调试PI控制器参数以优化系统的动态与稳态响应,从而深刻领悟LLC变换器的非线性动态特性和先进控制策略的精髓。

状态估计【卡尔曼滤波kalman】UKF、EKF、粒子滤波PF学习与研究(Matlab代码实现)

内容概要:本文系统性地探讨了状态估计领域中的三种关键非线性滤波算法——无迹卡尔曼滤波(UKF)、扩展卡尔曼滤波(EKF)和粒子滤波(PF),深入剖析其理论基础、数学推导及在非线性、非高斯系统环境下的适用性差异。通过Matlab编程实现,详尽展示了各算法的核心步骤与仿真流程,并结合无人机姿态估计、轨迹跟踪等典型应用场景进行性能对比分析,验证了不同滤波方法在精度、稳定性和计算复杂度方面的表现。文中提供完整的代码实例与技术解析,涵盖状态预测、协方差更新、观测修正等关键环节,强化了理论与实践的结合,具有较高的科研参考价值与工程指导意义。; 适合人群:具备信号处理、控制理论或状态估计基础知识的研究生、科研人员,以及从事导航、自动化、机器人、航空航天等相关领域的工程技术人员。; 使用场景及目标:① 深入理解UKF、EKF与PF的算法原理及其在非线性系统中的实现机制;② 在目标跟踪、多传感器融合、惯性导航等实际项目中开展算法选型、仿真验证与性能优化;③ 支持科研论文复现、算法改进及工程原型开发。; 阅读建议:建议结合Matlab代码同步运行与调试,重点理解各滤波器的状态更新逻辑与误差协方差传播机制,尝试在不同噪声水平与系统非线性程度下对比算法鲁棒性,以深化对滤波器内在特性的掌握。

科技中介服务机构在科技成果转化过程中,如何提升服务效率与专业性?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

人工智能基于RAG与LangGraph的企业级客服系统优化:大模型推理加速与多Agent协同决策技术实现

内容概要:本文档是一份企业级AI项目面试总结,涵盖多家公司在大模型、RAG、LangGraph、Agent系统、知识图谱、NL2SQL等前沿技术方向的实操性问题与解答。重点包括RAG全流程优化(切分、存储、召回、推理、缓存)、大模型推理加速(vLLM、TensorRT-LLM、量化、PD分离)、Agent架构设计(状态管理、多智能体协作、记忆机制、安全控制)、LangGraph与LangChain的应用差异、AB测试与模型评估方法、以及在客服、问数、数字人等场景中的落地实践。同时涉及Python基础、系统设计、部署运维、国产化适配等综合能力考察。; 适合人群:具备1-3年工作经验,从事大模型应用开发、AI系统研发、NLP工程师等相关岗位的研发人员;熟悉Python、深度学习框架,有RAG、Agent或NL2SQL项目经验者更佳。; 使用场景及目标:①准备AI方向的技术面试,尤其是B2B企业、金融科技、智能客服、工业智能化领域的岗位;②深入理解RAG与Agent系统的工程实现细节,掌握性能优化、系统稳定性、安全性设计等关键技术点;③学习如何将理论模型应用于复杂业务场景,提升系统级设计与问题解决能力。; 阅读建议:此资源以真实面试问答形式呈现,建议结合自身项目经验对照学习,重点关注高频考点如vLLM原理、RAG优化策略、LangGraph状态机制、多Agent协作与回滚设计等内容,并动手实践相关技术点如FastAPI异步服务、Tracker状态持久化、分层chunk策略等,以达到融会贯通的效果。

上一篇: 大模型书籍李开复周鸿祎力荐《实战AI大模型》!NUS尤洋教授首发新书深入浅出热门AI大模型,新手到专家的必备指南
下一篇: 大模型面经:大模型训练中出现的问题及详解
爱喝白开水a
博客等级 码龄3年 6793粉丝 763原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值