重新审视 ChatGPT 和 Elasticsearch:第 2 部分 - UI 保持不变

作者:来自 Elastic Jeff Vestal

本博客在第 1 部分的基础上进行了扩展,介绍了基于 RAG 的搜索系统的功能齐全的 Web UI。最后,你将拥有一个将检索、搜索和生成过程结合在一起的工作界面,同时使事情易于调整和探索。

不想读完整个内容?没问题,去克隆应用程序并开始搜索!

在第 1 部分中,我们介绍了如何设置搜索索引、使用 Open Crawler 抓取 Elastic 博客内容、为 LLM 配置推理 API,以及如何在 Kibana 中使用 Elastic 的 Playground 测试我们的 RAG 设置。

现在,在第 2 部分中,我将履行本博客结尾处的承诺,返回一个功能齐全的 Web UI!

本指南将引导你完成:

  • 应用程序的工作原理。
  • 用户可用的关键控制和自定义选项。
  • 改进搜索、检索和响应生成的增强功能。

应用程序的功能概述

从高层次来看,该应用程序接收用户的搜索查询或问题,并按照以下步骤进行:

  1. 使用混合搜索(结合文本匹配和语义搜索)检索相关文档
  2. 显示匹配的文档摘要,并提供指向完整内容的链接。
  3. 使用检索到的文档和预定义的指令构建提示
  4. 从大型语言模型(LLM)生成响应,并提供来自 Elasticsearch 结果的相关文档作为支撑。
  5. 提供控制选项,允许用户修改生成的提示和 LLM 的响应。

探索 UI 控件

该应用程序提供了多种控件来优化搜索和响应生成。以下是主要功能的细分:

  • 1 搜索框

        用户像使用搜索引擎一样输入查询。 查询通过词汇和向量搜索进行处理。

  • 2 生成的响应面板

        显示基于检索文档生成的 LLM 响应。 用于生成响应的来源会列出以供参考。 包括展开/收起切换按钮以调整面板大小。

  • 3 Elasticsearch 结果面板

        展示从Elasticsearch检索的排名靠前的文档。 包括文档标题、高亮部分和指向原始内容的链接。 帮助用户查看哪些文档影响了LLM的响应。

  • 4 源过滤控制

        用户可以在初次搜索后选择使用哪些数据源进行检索。 这允许用户专注于特定领域的内容。

  • 5 源过滤控制

        用户可以选择是否允许 LLM 在生成响应时使用其训练数据,而不依赖于基础上下文。 这为生成超出传递给 LLM 内容的扩展答案提供了可能性。

  • 6 来源数量选择器

        允许用户调整传递给 LLM 的结果数量。 增加来源通常有助于提高响应的基础,但过多的来源可能会导致不必要的 token 费用。

  • 7 块与文档切换按钮

        决定是否使用完整文档或相关的分块内容作为推理的基础。 通过将长文本分解成可管理的部分,分块提高了搜索的粒度。

  • 8 LLM 提示面板

        允许用户查看传递给 LLM 以生成响应的完整提示。 帮助用户更好地理解答案是如何生成的。

应用程序架构

该应用程序是一个 Next.js Web 应用程序,它提供了与基于 RAG 的搜索系统交互的用户界面。

Next.js 支持的 UI 应用程序的端到端数据流

这种架构消除了对单独后端服务的需求,利用 Next.js API 路由实现无缝搜索和 LLM 处理集成。

代码片段

让我们看一下与该应用程序最相关的几个代码段,如果你想修改它以使用不同的数据集,这些代码段可能会很有用。

ES 查询

Elasticsearch 查询非常简单。

/app/api/search/route.ts

const esQuery: any = {
    retriever: {
        // hybrid search retriever using reciprocal rank fusion
        rrf: {
            retrievers: [
                // Standard lexical search across multiple fields. Boosting `body`
                { standard: { query: { multi_match: { query, fields: ["body^2", "title", "meta_description"] } } } },
                // Semantic search on the semantic representation of the body text
                { standard: { query: { semantic: { field: "semantic_body", query } } } },
            ],
            rank_constant: 1, // Controls how aggressively ranking adjusts across retrievers
            rank_window_size: 50, // Number of documents considered for RRF ranking
        },
    },
    _source: false, // Exclude _source to minimize response size
    fields: ["title", "url_path", "url_path_dir1.keyword", "body", "semantic_body"], // Return only necessary fields
    highlight: {
        fields: {
            body: {}, // Return matched text in the `body` 
            semantic_body: { type: "semantic", number_of_fragments: 2, order: "score" }, // Return semantically relevant chunks
        },
    },
    aggs: {
        lab_sources: { terms: { field: "url_path_dir1.keyword" } }, // Aggregation to get the list of blog sites
    },
};

通过使用混合检索器,我们可以支持基于关键词的搜索以及更符合自然语言的问题搜索,而这正逐渐成为人们日常搜索的常态。

你会注意到,在这个查询中我们使用了高亮(highlight)功能。这使我们能够在 Elasticsearch 结果部分轻松提供匹配文档的相关摘要。同时,当我们构建 LLM 的提示词并选择使用匹配片段作为支撑时,高亮功能还能帮助我们利用这些匹配片段进行内容补充。

提取 Elasticsearch 结果

接下来,我们需要从 Elasticsearch 中提取结果

/app/api/search/route.ts

// Extract Hits
const rawHits = result?.body?.hits?.hits || result?.hits?.hits;

if (!rawHits || !Array.isArray(rawHits)) {
    console.error("⚠️ Unexpected Elasticsearch response format:", JSON.stringify(result, null, 2));
    return NextResponse.json(
        {error: "Unexpected response format from Elasticsearch", response: result},
        {status: 500}
    );
}

我们从 Elasticsearch 响应中提取搜索结果(hits),确保它们存在并以预期的数组格式返回。
如果结果缺失或格式不正确,我们将记录错误并返回 500 状态码。

解析搜索结果(hits)

我们已经获取了搜索结果,但需要将其解析为可用于在 UI 中显示给用户的格式,并用于构建 LLM 的提示词。

/app/api/search/route.ts

// Process search hits into frontend-friendly format
const results = rawHits.map((hit: any, index: number) => {
    const main_text = hit.highlight?.semantic_body?.[0] || "No preview available"; // Always for UI display
    const prompt_context = useChunk
        ? hit.highlight?.semantic_body?.[0] || "No chunk content available" // Use highlighted chunk
        : hit.fields?.body?.[0] || "No full document content available"; // Use full document text

    return {
        id: hit._id || `hit-${index}`,
        title: hit.fields?.title?.[0] || "Untitled",
        url_path: hit.fields?.url_path?.[0]?.startsWith("https://www.elastic.co")
            ? hit.fields?.url_path?.[0]
            : `https://www.elastic.co${hit.fields?.url_path?.[0] || "#"}`,
        main_text, // Always uses highlight.semantic_body for UI
        prompt_context, // This will be used in the LLM prompt
        citations: hit.highlight?.body || [],
    };
});

在这段代码中,发生了几个关键操作:

  1. 我们使用 semantic_body 的顶级匹配高亮内容作为每个 Elasticsearch 文档的摘要片段进行显示。
  2. 根据用户的选择,我们将提示上下文存储为 semantic_body(作为片段)或完整的 body(作为正文)。
  3. 提取 title(标题)。
  4. 提取博客的 URL,并确保其格式正确,以便用户可以点击访问博客。

实验来源点击处理

最后的处理步骤是解析聚合值。

/app/api/search/route.ts

// Extract Aggregations (Lab Sources)
console.log("📊 Extracting Aggregation Data...");
const aggregationData = result?.aggregations?.lab_sources?.buckets;

if (!aggregationData || !Array.isArray(aggregationData)) {
    console.warn("⚠️ Aggregation data is missing or not in expected format!");
} else {
    console.log("✅ Raw Aggregation Buckets:", JSON.stringify(aggregationData, null, 2));
}

const labSources = aggregationData?.map((bucket: any, index: number) => ({
    id: index + 1,
    text: bucket.key,
    checked: true,
})) || [];

我们这样做是为了提供一个可点击的 “Labs” 来源列表,方便用户查看搜索结果的来源。这样,用户可以选择他们想要包含的特定来源,并在重新搜索时,已选中的实验室将作为过滤条件。

状态管理

SearchInterface 组件是应用的核心组件。它使用 React 的状态管理钩子来处理所有数据和配置。

/components/SearchInterface.tsx

const [searchResults, setSearchResults] = useState<any[]>([]);
const [generatedResponse, setGeneratedResponse] = useState("");
const [generatedPrompt, setGeneratedPrompt] = useState(""); 
const [numSources, setNumSources] = useState(3);
const [useChunk, setUseChunk] = useState(true);

前面三行用于跟踪 Elasticsearch 的搜索结果、LLM 生成的响应,以及用于指示 LLM 的生成提示词。

最后两行用于跟踪用户在 UI 中的设置,包括:

  • 选择用于 LLM 支撑的来源数量。
  • 决定 LLM 是仅使用匹配片段(chunks)进行支撑,还是使用完整的博客文章。

处理搜索查询

当用户点击提交按钮时,handleSearch 函数接管搜索流程。

/components/SearchInterface.tsx

const handleSearch = async (query: string) => {
    console.log(`🔍 Sending search request for query: "${query}"`);
    setGeneratedResponse("");
    setGeneratedPrompt(""); 

    const selectedSources = labSources.filter((source) => source.checked).map((source) => source.text);

    try {
        const response = await fetch("/api/search", {
            method: "POST",
            headers: {"Content-Type": "application/json"},
            body: JSON.stringify({
                query,
                apiKey,
                apiUrl,
                selectedLabSources: selectedSources,
                numSources,
                useChunk,
            }),
        });

        const data = await response.json();
        setSearchResults(data.results);

该函数将查询发送到 /api/search(如前面的代码片段所示),并包括用户选择的来源、支撑设置以及 API 凭证。响应被解析并存储在状态中,从而触发 UI 更新。

来源提取

在获取到搜索结果后,我们会创建一个 sources 对象。

/components/SearchInterface.tsx

const sources = data.results.slice(0, numSources).map((doc, index) => ({
    id: `Source ${index + 1}`,
    title: doc.title,
    url: doc.url_path,
    content: doc.prompt_context, 
}));

这个 sources 对象稍后将作为提示的一部分传递给 LLM。LLM 会被指示在生成响应时引用它所使用的任何来源。

构建和发送 LLM 提示

提示是基于用户的设置动态生成的,并且包括来自 Elasticsearch 的支撑文档。

/components/SearchInterface.tsx

const promptInstruction = useContextOnly
    ? `ONLY use the provided documents for your response. Do not use any prior knowledge. 
    If the answer is not in the provided documents return "I'm unable to provide an answer using the included context." 
     Cite ONLY the sources you actually reference in your response.`
    : `Prefer using the provided documents, but if they lack sufficient details, you may use prior knowledge. 
    If you do, explicitly state: "[This response includes knowledge beyond the provided context.]" 
    Cite ONLY the sources you actually reference in your response.`;

// ✅ Sources list (for reference) — LLM will decide what to cite
const sourcesList = sources.map((source) => `- **${source.id}**: ${source.title} (${source.url})`).join("\n");

const prompt = `The user has asked a question: ${query}. Use the following documents to answer the question:
  ${formattedDocs}
  
  ${promptInstruction}
  
  Format the response with:
  - Proper Markdown headings (### for sections)
  - Clear bullet points for lists
  - Extra line breaks for readability
  - Paragraph spacing between sections
  - **At the end of the response, include a "*Sources Used*" section listing ONLY the document titles that were actually referenced.**
  
  
  ### Sources Used:
  (Only include sources that were directly referenced in your response.)`;

console.log("📤 Sending prompt to LLM:", prompt);
setGeneratedPrompt(prompt); // ✅ Store the generated prompt for UI display
streamLLMResponse(prompt);

} catch (error) {
  console.error("❌ Error fetching search results:", error);
}

默认情况下,我们指示 LLM 仅使用提供的支撑文档来生成答案。然而,我们也提供了一个设置,允许 LLM 使用其自身的训练 “知识” 来构建更广泛的响应。当允许 LLM 使用其自身的训练时,还会进一步指示它在响应的末尾附加警告。

我们指示 LLM 引用所提供的文档作为来源,但仅限于它实际使用的文档。

我们还提供了一些关于如何格式化响应的指令,以便在 UI 中提高可读性。

最后,我们将其传递给 /api/llm 进行处理。

流式传输 AI 响应

在 Elasticsearch 的文档被解析并立即返回到前端之后,我们调用 LLM 来生成对用户问题的回答。

/components/SearchInterface.tsx

const streamLLMResponse = async (prompt: string) => {
    try {
        const response = await fetch("/api/llm", {
            method: "POST",
            headers: {"Content-Type": "application/json"},
            body: JSON.stringify({prompt, apiKey, apiUrl}),
        });

        if (!response.body) throw new Error("LLM response body is empty");

        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let resultText = "";

        const processStream = async () => {
            while (true) {
                const {value, done} = await reader.read();
                if (done) break;

                const chunk = decoder.decode(value, {stream: true});

                // 🛠️ Parse each event
                chunk.split("\n").forEach((line) => {
                    if (line.startsWith("data:")) {
                        try {
                            const jsonData = JSON.parse(line.replace("data: ", "").trim());

                            if (jsonData.completion) {
                                const deltaText = jsonData.completion.map((c: any) => c.delta).join("");
                                resultText += deltaText;

                                // Update state dynamically
                                setGeneratedResponse((prev) => prev + deltaText);
                            }
                        } catch (error) {
                            console.error("⚠️ Error parsing LLM event stream:", error);
                        }
                    }
                });
            }
        };

        await processStream();
        console.log("✅ LLM Streaming Complete:", resultText);
    } catch (error) {
        console.error("❌ Error streaming LLM response:", error);
    }
};

这部分代码虽然有很多行,但本质上是调用 /api/llm(稍后会详细介绍)并处理流式响应。我们希望 LLM 的响应能够在生成时流式返回到 UI,因此我们在每次返回事件时解析它,允许 UI 动态更新。

我们需要解码流,进行一些清理操作,并用新接收到的文本更新 resultText

调用 LLM

我们通过 Elasticsearch 的推理 API 调用 LLM。这使我们能够将数据的管理集中在 Elasticsearch 中。

/app/api/llm/route.ts

const response = await fetch(`${apiUrl}/_inference/completion/azure_openai_gpt-4o/_stream`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `ApiKey ${apiKey}`,
  },
  body: JSON.stringify({ input: prompt }),
});

这段代码相对简单。我们将请求发送到我们在设置过程中创建的流式推理 API 端点(见下文的 “让系统运行” 部分),然后将流式响应返回。

处理流

我们需要按块读取流数据,逐块接收并处理它。

/app/api/llm/route.ts

const reader = response.body.getReader();
const decoder = new TextDecoder();
const stream = new ReadableStream({
  start(controller) {
    async function push() {
      while (true) {
        const { value, done } = await reader.read();
        if (done) {
          controller.close();
          return;
        }
        controller.enqueue(decoder.decode(value, { stream: true }));
      }
    }
    push();
  },
});

在这里,我们逐块解码流式返回的 LLM 响应,并将每个解码部分实时转发到前端。

让系统运行

现在我们已经审查了代码中的一些关键部分,让我们来安装并启动系统。

Completion Inference API

如果你尚未在 Elasticsearch 中配置完成推理 API,你需要先进行配置,以便能够生成对用户问题的回答。

我使用了 Azure OpenAI 并选择了 gpt-4o 模型,但你应该也能够使用其他服务。关键是它必须是支持流式推理 API 的服务。

PUT _inference/completion/azure_openai_completion
{
    "service": "azureopenai",
    "service_settings": {
        "api_key": "<api_key>",
        "resource_name": "<resource_name>",
        "deployment_id": "<deployment_id>",
        "api_version": "2024-02-01"
    }
}

service_settings 的具体配置取决于你使用的服务和模型。有关更多信息,请参考推理 API 文档。

克隆

如果你已安装并配置了 GitHub CLI,你可以将 UI 仓库克隆到你选择的目录中。

git clone git@github.com:jeffvestal/rag-really-tied-the-app-together.git

你也可以下载 zip 文件,然后解压它。

安装依赖项

按照 repo 中的 readme 文件中的步骤安装依赖项。

启动开发服务器

我们将以开发模式运行。这通过实时重新加载和调试来运行。有一种生产模式,可以运行优化的生产版本以供部署。

要以开发模式启动,请运行:

npm run dev

这将启动服务器,如果没有错误,你将看到类似以下内容:

(rag-ties-the-app-together-frontend)  rag-ties-the-app-together-frontend ❯ npm run dev

> rag-ties-the-app-together-frontend@0.1.0 dev
> next dev

  ▲ Next.js 14.2.16
  - Local:        http://localhost:3000

 ✓ Starting...
 ✓ Ready in 1190ms

如果你正在使用端口 3000 运行其他程序,则你的应用程序将开始使用下一个可用端口。只需查看输出即可了解它使用了哪个端口。如果你希望它在特定端口上运行,比如 4000,你可以运行以下命令:

PORT=4000 npm run dev

进入用户界面

应用程序运行后,你可以尝试不同的配置以查看哪种配置最适合你。

连接设置

使用该应用程序之前你需要做的第一件事是设置你的连接凭据。为此,请单击右上角的齿轮图标⚙️。

当框弹出时,输入你的 API Key 和 Elasticsearch URL。

默认值

要开始,只需提出问题或在搜索栏中输入搜索查询。其余一切保持原样。

该应用程序将查询 Elasticsearch 以查找最相关的文档,在上面有关 rrf 的示例中,使用 rrf!包含简短片段、博客标题和可点击 URL 的文档将返回给用户。

前面提到的前三个片段将与一个提示一起组合,并发送给 LLM。生成的响应将以流式方式返回。

这看起来是一个彻底的回应!

自定义游戏

一旦初始搜索结果和生成的响应显示出来,用户可以进行跟进,并对设置做出一些更改。

实验室来源 - Lab sources

所有在索引中搜索的博客站点将列在 “Lab Sources” 下。如果你在第一部分使用 Open Crawler 创建的索引中添加了额外的站点或来源,它们会显示在这里。

你可以选择仅包含你希望在搜索结果中考虑的来源,并重新点击搜索。随后的搜索将使用选中的来源作为 Elasticsearch 查询的过滤条件。

答案来源 - Anwer source

我们在 RAG(检索增强生成)中提到的一个优势是向 LLM 提供支撑文档。这有助于减少幻觉(虽然没有什么是完美的)。然而,你可能希望允许 LLM 使用其训练和其他 “知识”,超出支撑文档的内容来生成响应。取消勾选 “Context Only” 选项将允许 LLM 自由使用其自身的知识。

LLM 应在响应的末尾提供警告,告诉你是否在支撑文档之外进行了推理。正如许多 LLM 的行为一样,这并不是绝对保证的。因此,对于这些响应,还是要谨慎使用。

来源数量 - Number of sources

我们默认使用三个片段作为 LLM 的支撑信息。增加上下文片段的数量有时能为 LLM 提供更多的信息来生成响应。有时,提供整个关于某个专门主题的博客效果最佳。

这取决于主题的分布。一些主题会在多个博客中以不同的方式进行讨论。因此,提供更多来源可以产生更丰富的回答。对于某些更加深奥的主题,可能只会在一个博客中涉及,额外的片段可能不会有帮助。

片段或文档 - Chunk or doc

关于来源数量,将所有信息都传递给 LLM 通常不是生成答案的最佳方式。虽然大多数博客相比于许多其他文档来源相对较短,比如健康保险政策文件,但将长文档直接传递给 LLM 有几个缺点。首先,如果相关信息只在两段中,然而你提供了二十段,那么你就为十八段无用的内容付费。其次,这些无用信息会减慢 LLM 生成响应的速度。

通常,除非有充分的理由发送整个文档(在本例中是博客),否则最好坚持使用片段。

结语

希望这份操作指南帮助你确保在设置一个能够提供从 Elasticsearch 检索到的语义文档和由 LLM 生成的答案的 UI 时,不会感到陌生。

当然,我们可以添加很多功能,并调整一些设置,以使体验更加完善。但这已经是一个不错的开始。提供代码给社区的好处是,你可以根据自己的需求自由定制和调整。

敬请关注第 3 部分,我们将使用 Open Telemetry 对应用程序进行监控!

Elasticsearch 已经与行业领先的生成 AI 工具和服务提供商进行了原生集成。查看我们关于如何超越 RAG 基础,或如何构建生产就绪应用程序 Elastic 向量数据库的网络研讨会。

要为你的用例构建最佳搜索解决方案,可以开始免费云试用,或立即在本地机器上尝试 Elastic。

原文:ChatGPT and Elasticsearch revisited: Part 2 - The UI Abides - Elasticsearch Labs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/979008.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

点云 PCL 滤波在自动驾驶的用途。

1.直通滤波 2.体素滤波、 2.1 分类&#xff1a;VoxelGrid&#xff08;求体素的重心又称质心点&#xff09;和ApproximateVoxelGrid&#xff08;求体素的中心点&#xff09;两种体素滤波器&#xff0c; 2.2 衍生&#xff1a;此外衍生了改进体素滤波&#xff08;求距离重心最近…

人工智能 pytorch篇

pytorch是一个深度学习框架&#xff0c;他封装了张量&#xff08;Tensor&#xff09;&#xff0c;Pytorch中的张量就是元素为同一种数据类型的多维矩阵。在Pytorch中&#xff0c;张量以类的形式封装起来&#xff0c;对张量的一些运算、处理的方法被封装在类中。 pytorch的安装…

Cherno 游戏引擎笔记(91~111)

好久不见&#xff01; 个人库的地址&#xff1a;&#xff08;GitHub - JJJJJJJustin/Nut: The game_engine which learned from Cherno&#xff09;&#xff0c;可以看到我及时更新的结果。 -------------------------------Saving & Loading scene-----------------------…

DeepSeek行业应用实践报告-智灵动力【112页PPT全】

DeepSeek&#xff08;深度搜索&#xff09;近期引发广泛关注并成为众多企业/开发者争相接入的现象&#xff0c;主要源于其在技术突破、市场需求适配性及生态建设等方面的综合优势。以下是关键原因分析&#xff1a; 一、技术核心优势 开源与低成本 DeepSeek基于开源架构&#xf…

项目8:信用违约预测-集成学习

目录 背景说明 项目介绍 导入模块 数据加载 分析与处理数据 划分数据集 使用随机森林创建并训练模型 通过参数搜索和过采样&#xff0c;缓解标签不平衡问题 小结 背景说明 风险已经成为了今年金融市场的重要主题之一&#xff0c;银行作为贷方&#xff0c;随时都面临着借贷者违约…

一文了解:部署 Deepseek 各版本的硬件要求

很多朋友在咨询关于 DeepSeek 模型部署所需硬件资源的需求&#xff0c;最近自己实践了一部分&#xff0c;部分信息是通过各渠道收集整理&#xff0c;so 仅供参考。 言归正转&#xff0c;大家都知道&#xff0c;DeepSeek 模型的性能在很大程度上取决于它运行的硬件。我们先看一下…

Redis分布式锁故障处理:当Redis不可用时的应对策略

Redis分布式锁故障处理&#xff1a;当Redis不可用时的应对策略 在分布式系统中&#xff0c;Redis因其高性能和丰富的特性常被用于实现分布式锁。但当加锁过程中Redis服务不可用时&#xff0c;系统将面临严重挑战。本文将深入探讨这一问题&#xff0c;并提供多维度解决方案。 目…

GO 进行编译时插桩,实现零码注入

Go 编译时插桩 Go 语言的编译时插桩是一种在编译阶段自动注入监控代码的技术&#xff0c;目的是在不修改业务代码的情况下&#xff0c;实现对应用程序的监控和追踪。 基本原理 Go 编译时插桩的核心思想是通过在编译过程中对源代码进行分析和修改&#xff0c;将监控代码注入到…

vue3中ref和reactive响应式数据、ref模板引用(组合式和选项式区别)、组件ref的使用

目录 Ⅰ.ref 1.基本用法&#xff1a;ref响应式数据 2.ref模板引用 3.ref在v-for中的模板引用 ​4.ref在组件上使用 ​5.TS中ref数据标注类型 Ⅱ.reactive 1.基本用法&#xff1a;reactive响应式数据 2.TS中reactive标注类型 Ⅲ.ref和reactive的使用场景和区别 Ⅳ.小结…

计算机毕业设计SpringBoot+Vue.js视频网站系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

LVS+Keepalived 高可用集群搭建

一、高可用集群&#xff1a; 1.什么是高可用集群&#xff1a; 高可用集群&#xff08;High Availability Cluster&#xff09;是以减少服务中断时间为目地的服务器集群技术它通过保护用户的业务程序对外不间断提供的服务&#xff0c;把因软件、硬件、人为造成的故障对业务的影响…

macos下myslq图形化工具之Sequel Ace

什么是Sequel Ace 官方github&#xff1a;https://github.com/Sequel-Ace/Sequel-Ace Sequel Ace 是一款快速、易于使用的 Mac 数据库管理应用程序&#xff0c;用于处理 MySQL 和 MariaDB 数据库。 Sequel Ace 是一款开源项目&#xff0c;采用 MIT 许可证。用户可以通过 Ope…

lvgl运行机制分析

lv_timer_handler() 是 LVGL 的“心脏”&#xff1a;这个函数会依次做以下事情&#xff1a; 处理定时器&#xff08;如动画、延迟回调&#xff09;。 读取输入设备&#xff08;如触摸屏、按键的状态&#xff09;。 刷新脏区域&#xff08;仅重绘屏幕上发生变化的区域&#xf…

C++ | 高级教程 | 文件和流

&#x1f47b; 概念 文件流输出使用标准库 fstream&#xff0c;定义三个新的数据类型&#xff1a; 数据类型描述ofstream输出文件流&#xff0c;用于创建文件并向文件写入信息。ifstream输入文件流&#xff0c;用于从文件读取信息。fstream文件流&#xff0c;且同时具有 ofst…

Linux:Shell环境变量与命令行参数

目录 Shell的变量功能 什么是变量 变数的可变性与方便性 影响bash环境操作的变量 脚本程序设计&#xff08;shell script&#xff09;的好帮手 变量的使用&#xff1a;echo 变量的使用&#xff1a;HOME 环境变量相关命令 获取环境变量 环境变量和本地变量 命令行…

Halcon 学习之路 set_grayval 算子

gen_imag_const 创建灰度图像 gen_image_const(Image&#xff0c;Type&#xff0c;Width&#xff0c;Height) 算子gen_image_const创建指定大小的图像&#xff0c;图像的宽度和高度由Width和Height决定 Type 像素类型 byte :每像素1字节&#xff0c;无符号&#xff08;0-255&…

基于springboot学生管理系统

目录 项目介绍 图片展示 运行环境 项目介绍 管理员 学生信息管理&#xff1a;查询、添加、删除、修改学生信息 班级信息管理&#xff1a;查询、添加、删除、修改班级信息 教师信息管理&#xff1a;查询、添加、删除、修改教师信息 课程信息管理&…

wav格式的音频压缩,WAV 转 MP3 VBR 体积缩减比为 13.5%、多个 MP3 格式音频合并为一个、文件夹存在则删除重建,不存在则直接建立

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 问题一&#xff1a;wav格式的音频压缩为哪些格式&#xff0c;网络传输给用户播放…

JavaWeb-Servlet对象生命周期

文章目录 关于Servlet对象的生命周期创建和销毁Servlet对象的流程测试先后顺序在服务器启动时就创建实例tip: init和无参构造的作用差不多, 为什么定义的规范是init() 关于Servlet对象的生命周期 我们都知道, 我们开发一个Servlet程序的时候, 每一个类都要实现Servlet接口, 然…

在docker容器中运行Ollama部署deepseek-r1大模型

# 启动ollama容器 docker run -itd --gpusall -v /app/ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:0.5.12# 进入容器 docker exec -it ollama bash ## 拉取大模型&#xff08;7B为例&#xff09; ollama pull deepseek-r1:7b## 修改监听地址和端口 expo…