news 2026/8/31 22:35:09

Rust实现RAG:为AI Agent打造本地知识库问答最小原型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rust实现RAG:为AI Agent打造本地知识库问答最小原型

这次我们来看 Rust AI Agent 系列的第 8 篇:RAG 简介。前几篇文章已经把 Agent 的动作闭环搭起来了:模型能理解任务、能调用工具、能处理结果。但很多实际场景里,Agent 面对的问题不是“不会调用工具”,而是“缺少领域知识”。比如你问它某个内部项目的部署步骤,它没读过你的项目文档,只能靠训练数据里的泛化经验去猜。猜得对不对,完全看运气。

RAG(Retrieval-Augmented Generation,检索增强生成)就是解决这类问题的标准做法。核心思路很直接:让大模型在回答之前,先去外部知识库检索相关素材,再把素材拼进提示词,让模型基于素材回答。不需要重新微调模型,也能把私有文档、最新资料、企业内部知识接进 Agent。

这篇文章我会用 Rust 把一个最小 RAG 链路讲清楚:文档加载、文本切块、向量化、相似度检索、上下文拼接、生成回答,最后补一个 HTTP 接口示例和批量任务思路。目标读者是已经在做 Rust AI Agent、想让 Agent 具备“查资料”能力的开发者。看完这篇,你应该能自己搭出一个能跑的本地知识库问答最小原型。

1. RAG 核心能力速览

先把 RAG 在 Rust AI Agent 里的定位和核心要素列出来,方便快速判断这篇文章讲的内容适不适合你。

能力项说明
解决的问题大模型不知道私有知识、知识更新滞后、容易产生幻觉
核心链路文档加载 -> 文本切块 -> 向量化 -> 向量存储 -> 相似度检索 -> 上下文拼接 -> 生成回答
关键组件嵌入模型、向量存储、生成模型、Rust 服务层
Rust 侧承担的任务文档解析、文本切块、HTTP 调用模型服务、余弦相似度计算、接口服务暴露
模型来源本地模型服务(Ollama / llama.cpp 等)或 OpenAI 兼容接口
部署形式Rust 二进制 + 模型服务 + 向量数据库
是否支持 API可以,用 axum / actix-web 暴露 HTTP 接口
是否支持批量任务可以,按目录批量切块入库,也可以循环问题列表做离线批量问答
显存与内存门槛取决于嵌入模型和生成模型的量化版本;只跑嵌入模型资源需求低,同时跑 7B 级生成模型需要按实际量化格式评估
适用场景私有知识库问答、文档摘要、客服辅助、Agent 长期记忆、代码库问答

关于硬件门槛,这一篇不写死具体数字。原因是同一个模型在不同量化等级、不同上下文长度下,资源占用差异非常大。更稳妥的判断是:先选小模型跑通链路,再根据效果和资源换大模型。

2. 为什么 AI Agent 需要 RAG

2.1 大模型的“不知道”和“乱编造”

大模型的知识来自训练阶段,训练结束后,知识就固定了。你问它最近三个月的新文档内容,它大概率不知道;你硬问,它可能用看起来很合理的语气编一段答案。这就是幻觉问题。

RAG 不是改变模型本身,而是改变模型的输入。模型回答问题前,先从一个可控的外部知识库里检索到相关段落,把段落作为上下文塞进提示词。这样模型回答时至少是“基于你提供的资料”在回答,而不是凭空发挥。

2.2 让 Agent 拥有“可更新”的知识

Agent 的工具调用解决的是“我能执行什么操作”,RAG 解决的是“我能在回答中引用哪些事实”。两者是互补的。

举个例子:一个运维 Agent 被问到某个服务的重启流程。它可以通过工具去查 API,也可以通过 RAG 去检索内部运维手册。手册更新了,RAG 的检索结果就跟着更新,不需要重新训练模型。这就是 RAG 比微调更适合动态知识的原因之一。

2.3 RAG 不擅长什么

RAG 不是万能的。如果问题需要多步推理、需要从多个文档里综合信息,简单的“检索一段 + 拼接上下文”效果会打折扣。另外,如果文档本身质量很差、切块策略不合理,检索出来的内容可能相关性很低。这时候需要引入重排序(Rerank)、多路召回、甚至把 RAG 升级成 Agentic RAG,让 Agent 自己决定搜什么关键词、搜几次、读哪些片段。

从材料里也能看到,Agentic RAG 是当前非常热的方向。这一篇先打基础,把 RAG 的链路跑通,后面再讨论如何让 Agent 控制检索过程。

3. RAG 基础流程:从文档到回答的六步链路

RAG 的完整链路可以拆成六个环节。先看全貌,再逐个说明。

  1. 文档加载:读取 TXT、Markdown、PDF、Word、HTML 等格式。
  2. 文本切块:按固定长度或语义边界把长文档切成片段。
  3. 向量化:用嵌入模型把每个片段转成向量。
  4. 向量存储:把向量和原始文本存入内存、SQLite 或专用向量数据库。
  5. 相似度检索:把用户问题转成向量,用余弦相似度等距离度量找到 Top-K 片段。
  6. 生成回答:把 Top-K 片段拼成上下文,连同问题一起发给大模型。

3.1 文档加载与解析

TXT 和 Markdown 最简单,Rust 里直接读字符串即可。PDF 和 Word 需要引入专门库,例如pdf-extractdocx-rs等。刚开始做原型时,建议先用纯文本文件跑通链路,再逐步加格式解析。

3.2 文本切块策略

切块是 RAG 里影响效果最大的环节之一。

  • 按固定字符数切:简单,但可能在句子中间截断。
  • 带重叠窗口切:相邻块保留一部分重叠内容,能缓解边界信息丢失。
  • 按段落或句子切:语义更完整,但实现复杂度略高。
  • 按 Markdown 标题切:适合结构化文档。

选择切块大小要平衡检索精度和上下文占用:块太小,检索时缺少上下文;块太大,信息变杂,生成时占用的 token 也更多。常见的做法是先按段落切,再对过长段落做二次切分。原型阶段可以用 300 到 800 字符、重叠 50 到 100 字符的配置,再按效果调整。

3.3 向量化与相似度计算

嵌入模型把文本映射成高维向量。语义相近的文本,向量在空间里距离更近。典型做法是用余弦相似度衡量两个向量的接近程度。

3.4 生成回答的提示词设计

检索到的片段不能直接全塞给模型。需要设计一个系统提示词,告诉模型“只能基于资料回答,资料不足就说明不知道”,避免模型把检索到的片段和训练记忆混在一起。

4. 环境准备与前置条件

这一篇的示例代码不依赖重型框架,主要需要三部分:Rust 工具链、模型服务、可选的向量存储。

4.1 Rust 工具链

如果机器上还没有 Rust,用官方脚本安装 rustup。已经装过的可以直接检查版本。

# 安装 rustup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 加载环境变量 source "$HOME/.cargo/env" # 检查版本 rustc --version cargo --version

从网络热词里可以看到,很多人在 Windows 上使用 Rust 时也在关注 Cargo 更新源和 MSVC 工具链问题。Windows 用户安装完成后,建议先编译一个最简单项目确认工具链可用,再继续后面的步骤。

4.2 本地模型服务

RAG 链路里有两个模型:嵌入模型和生成模型。最省事的本地部署方式是使用 Ollama 或 llama.cpp 启动本地服务。这里以 Ollama 为例,模型名需要按你本机实际拉取的结果调整:

# 启动 Ollama 服务 ollama serve # 拉取生成模型(按本机配置选择模型大小) ollama pull qwen2.5:7b # 拉取嵌入模型 ollama pull bge-m3

如果你不想用 Ollama,也可以使用 llama.cpp 的 server 模式。不同服务端的接口路径不完全一样,示例代码里的base_url和请求体结构需要按实际服务端调整。

4.3 向量存储选型

原型阶段,直接把向量存在内存Vec里就够了。数据量变大后,可以换成:

  • Qdrant:有官方 Rust 客户端,适合独立向量数据库。
  • pgvector:产品里已经在用 PostgreSQL 时可以少引入一个组件。
  • sqlite-vec:轻量,适合单机小规模部署。
  • 本地文件序列化:把向量落到磁盘,重启后重新加载。

这一篇示例先用内存保存,逻辑最简单,方便理解 RAG 本身。

4.4 项目目录结构

建议在开始编码前先规划好目录:

minimal_rag/ ├── Cargo.toml ├── src/ │ ├── main.rs │ ├── chunk.rs │ ├── embed.rs │ ├── retrieve.rs │ └── generate.rs ├── data/ │ ├── knowledge.txt │ └── output/ └── questions.txt

data/knowledge.txt放测试文档,questions.txt放批量测试问题,output放问答结果。这样的结构后面扩展批量任务时不需要大改。

5. 用 Rust 实现最小 RAG 流程

这一节给出一个可运行的简化示例。注意:依赖版本、模型名称、接口路径需要按实际项目调整。为了让例子聚焦在 RAG 链路上,我刻意把 HTTP 调用写得很直白,没有封装成复杂 trait。

5.1 Cargo.toml 配置

[package] name = "minimal_rag" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1" reqwest = { version = "0.12", features = ["json"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

代码里用reqwest发 HTTP 请求,serde_json解析响应,tokio支撑异步运行时。如果你要接 Qdrant,再加入qdrant-client;如果用 axum 暴露接口,再加入axum

5.2 文本切块模块

先实现一个简单的按字符切块函数,带重叠窗口:

// src/chunk.rs pub fn split_text(text: &str, chunk_size: usize, overlap: usize) -> Vec<String> { let mut chunks = Vec::new(); let chars: Vec<char> = text.chars().collect(); let total = chars.len(); let mut start = 0; while start < total { let end = (start + chunk_size).min(total); let chunk: String = chars[start..end].iter().collect(); chunks.push(chunk); if end == total { break; } if chunk_size <= overlap { break; } start += chunk_size - overlap; } chunks }

这个实现按字符偏移切分,处理中文时不会把一个字符拆成两个 UTF-8 字节片段。后面可以考虑按句子或 Markdown 标题切,但作为最小原型已经够用了。

5.3 嵌入调用模块

这里用 OpenAI 兼容接口调用 Ollama,所以base_url需要带/v1。如果你的模型服务接口不一样,需要按实际路径修改。

// src/embed.rs use anyhow::Result; use serde_json::{json, Value}; pub async fn embed_text( client: &reqwest::Client, base_url: &str, model: &str, text: &str, ) -> Result<Vec<f32>> { let body = json!({ "model": model, "input": text, }); let resp = client .post(format!("{}/embeddings", base_url)) .json(&body) .send() .await?; let data: Value = resp.json().await?; let embedding = data["data"][0]["embedding"] .as_array() .ok_or_else(|| anyhow::anyhow!("embedding not found"))?; let vec = embedding .iter() .filter_map(|v| v.as_f64().map(|x| x as f32)) .collect::<Vec<f32>>(); Ok(vec) }

注意:Ollama 的 OpenAI 兼容接口和原生接口返回结构不完全一样。这段代码以 OpenAI 兼容接口为例,使用/v1/embeddings路径。如果你用的是其他模型服务,一定要先看它的接口文档,确认返回字段,再解析。

5.4 相似度计算模块

RAG 原型阶段用余弦相似度就够了。向量维度很高,但计算量相比模型推理可以忽略。

// src/retrieve.rs pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 { if a.len() != b.len() { return 0.0; } let mut dot = 0.0f32; let mut norm_a = 0.0f32; let mut norm_b = 0.0f32; for (x, y) in a.iter().zip(b.iter()) { dot += x * y; norm_a += x * x; norm_b += y * y; } if norm_a == 0.0 || norm_b == 0.0 { 0.0 } else { dot / (norm_a.sqrt() * norm_b.sqrt()) } } pub fn top_k_scores<'a>( query_vec: &[f32], items: &'a [(String, Vec<f32>)], k: usize, ) -> Vec<(f32, &'a str)> { let mut scored: Vec<(f32, &'a str)> = items .iter() .map(|(text, vec)| (cosine_similarity(query_vec, vec), text.as_str())) .collect(); scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal)); scored.truncate(k); scored }

5.5 生成回答模块

生成回答部分调用chat/completions接口。提示词设计直接影响回答质量,这里示例强调“只基于资料回答”。

// src/generate.rs use anyhow::Result; use serde_json::{json, Value}; pub async fn generate_answer( client: &reqwest::Client, base_url: &str, model: &str, system_prompt: &str, user_prompt: &str, ) -> Result<String> { let body = json!({ "model": model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "stream": false }); let resp = client .post(format!("{}/chat/completions", base_url)) .json(&body) .send() .await?; let data: Value = resp.json().await?; let answer = data["choices"][0]["message"]["content"] .as_str() .unwrap_or("") .to_string(); Ok(answer) }

5.6 主流程链路

主流程把切块、嵌入、检索、生成串起来。先读入知识文件,切块后批量嵌入,再对问题做检索。

mod chunk; mod embed; mod generate; mod retrieve; use anyhow::Result; #[tokio::main] async fn main() -> Result<()> { // 本地模型服务地址,按实际环境调整 let base_url = "http://127.0.0.1:11434/v1"; let embed_model = "bge-m3"; let chat_model = "qwen2.5:7b"; let client = reqwest::Client::new(); // 1. 加载文档 let doc = std::fs::read_to_string("data/knowledge.txt")?; // 2. 切块 let chunks = chunk::split_text(&doc, 512, 64); println!("document chunks: {}", chunks.len()); // 3. 批量嵌入,保存在内存 let mut items = Vec::new(); for c in &chunks { let vec = embed::embed_text(&client, &base_url, embed_model, c).await?; items.push((c.clone(), vec)); } println!("indexed chunks: {}", items.len()); // 4. 问题嵌入 let question = "Rust 里怎么做 RAG?"; let q_vec = embed::embed_text(&client, &base_url, embed_model, question).await?; // 5. 检索 Top-3 let top = retrieve::top_k_scores(&q_vec, &items, 3); let context = top .iter() .map(|(_, text)| *text) .collect::<Vec<_>>() .join("\n\n"); // 6. 生成回答 let system_prompt = "你是一个知识库问答助手。请严格根据给定的资料回答问题。如果资料中没有答案,请直接说明资料不足,不要编造。"; let user_prompt = format!("资料:\n{}\n\n问题:{}", context, question); let answer = generate::generate_answer(&client, &base_url, chat_model, system_prompt, &user_prompt).await?; println!("answer: {}", answer); Ok(()) }

这段代码是完整可读的流程,但要注意几个点:

  • 文档文件不存在时会直接报错,原型里用了?传播错误。
  • 批量嵌入是循环串行的,文档多时速度较慢,后续可以改成并发控制。
  • 所有向量都放在内存里,重启进程后需要重新嵌入。

跑通这段代码后,你就已经有一个“最小 RAG 原型”了。可以换不同文档、不同问题看效果差异。

6. 用 axum 暴露 RAG 接口

原型跑通后,下一步通常是服务化。把 RAG 封装成 HTTP 接口后,可以让 Web 前端、聊天机器人、自动化脚本统一调用。网络热词里反复出现 actix-web,说明 Rust 社区在 Web API 服务上确实有很强的实践需求。这里用 axum 写一个最小示例,逻辑和 actix-web 是相通的。

6.1 添加 axum 依赖

[dependencies] axum = "0.7"

注意:axum 0.7 和 0.8 的接口写法有差异,请以你实际使用的版本为准。

6.2 最小接口服务

use axum::{extract::State, routing::post, Json, Router}; use serde_json::{json, Value}; #[derive(Clone)] struct AppState { client: reqwest::Client, base_url: String, embed_model: String, chat_model: String, // 生产环境这里应该换成真正的向量索引结构 chunks: Vec<String>, chunk_vectors: Vec<Vec<f32>>, } async fn ask_handler( State(state): State<AppState>, Json(payload): Json<Value>, ) -> Json<Value> { let question = payload["question"].as_str().unwrap_or("").to_string(); // 这里省略:调用 embed_text、top_k_scores、generate_answer // 返回值先给一个占位 Json(json!({ "question": question, "answer": "placeholder", })) } #[tokio::main] async fn main() { let state = AppState { client: reqwest::Client::new(), base_url: "http://127.0.0.1:11434/v1".to_string(), embed_model: "bge-m3".to_string(), chat_model: "qwen2.5:7b".to_string(), chunks: Vec::new(), chunk_vectors: Vec::new(), }; let app = Router::new() .route("/ask", post(ask_handler)) .with_state(state); // 监听地址按需设置,生产环境建议不要直接绑定 0.0.0.0 let listener = tokio::net::TcpListener::bind("127.0.0.1:8080") .await .unwrap(); axum::serve(listener, app).await.unwrap(); }

这个示例只展示了接口骨架,没有把检索和生成逻辑放进去,避免代码过长。你可以把第 5 节的调用链嵌入ask_handler,把参数从 JSON 里取出来,返回回答结果。

6.3 curl 调用测试

服务启动后,用 curl 发一个请求验证:

curl -X POST http://127.0.0.1:8080/ask \ -H "Content-Type: application/json" \ -d '{"question": "Rust 里怎么做 RAG?"}'

正常响应结构类似:

{ "question": "Rust 里怎么做 RAG?", "answer": "在 Rust 里做 RAG 需要先切块、向量化,再检索相似片段并拼接上下文发给大模型。" }

6.4 批量任务设计

服务化之后,批量任务就容易做了。两种常见批量场景:

  • 批量入库:遍历一个目录下的所有文档,逐个切块、嵌入、保存到向量存储。
  • 批量问答:从一个questions.txt文件里逐行读问题,对每个问题走一遍 RAG 流程,结果写入output/answers.txt

批量任务要注意失败恢复。建议每条记录写入后立即落盘,并用序号标记进度,避免中途失败后从头再来。如果用 rayon 做并行检索,要注意向量库连接的线程安全性。

7. 资源占用与性能观察

7.1 三个主要资源瓶颈

RAG 链路里资源占用最大的三个环节是嵌入模型推理、生成模型推理、向量检索。

  • 嵌入阶段:文档多时,嵌入调用次数多,耗时和文档总长度成正比。这一阶段主要是 CPU 或 GPU 的密集计算。
  • 生成阶段:单次回答耗时主要由生成模型大小、上下文长度和输出长度决定。
  • 检索阶段:内存向量库在数据量小的时候基本无感,数据量到几十万条后要观察内存占用和排序耗时。

7.2 如何观察资源占用

本地调试时,可以用系统工具查看:

# Linux / macOS top -o %MEM # 只看特定进程 ps aux | grep minimal_rag

如果模型跑在 GPU 上,用nvidia-smi观察显存占用。显存占用会随上下文长度波动,批量任务期间尤其要留意显存是否被长时间占满。

更稳妥的判断是:先用小模型、小文档跑通流程,再用真实数据量压测。不要一开始就上长文档大全量索引,否则问题定位会变得很困难。

7.3 降低资源占用的方向

  • 切块长度设小一点,减少单次嵌入和生成时的 token 数量。
  • 使用量化版本更小的模型。
  • 批量嵌入时限制并发数,避免内存暴涨。
  • 向量存储开启持久化后,重启进程就不需要重新嵌入全部文档。

8. 常见问题与排查方法

Rust RAG 原型阶段最容易遇到的问题集中在模型服务、接口路径、文档路径和内存管理几个方面。

问题现象可能原因排查方式解决方案
启动后连接模型服务失败模型服务未启动或地址端口错误检查ollama serve是否运行,curl 测试模型服务地址启动模型服务,修正base_url
嵌入结果为空嵌入模型名错误或接口响应结构变化打印完整响应内容确认嵌入模型已拉取,修正 JSON 字段路径
生成回答为空生成模型未拉取或聊天接口路径错误用 curl 直接调用chat/completions拉取模型,检查请求体结构
检索结果完全不相关切块太大、文档太杂或嵌入模型效果差打印检索到的 Top-K 文本内容调整切块策略,增加文档预处理,换更强的嵌入模型
中文切块出现乱码按字节切分导致 UTF-8 字符被截断检查切块函数是否按字符边界切使用chars()按字符处理
批量任务中途报错某条数据格式异常或网络抖动加日志,打印当前处理序号逐条落盘,支持断点续跑
接口端口被占用本地已有其他服务监听同一端口netstatlsof查看端口占用更换监听端口
内存占用持续上涨向量全部驻留内存且没有释放观察任务结束后内存是否回落使用向量数据库或文件持久化,避免单进程持有全量向量

9. 最佳实践与使用建议

9.1 先小参数跑通,再扩大规模

第一次验证 RAG 时,建议用一篇 1000 字左右的文档、切块 256 字符左右、Top-2 检索。这样能快速确认链路通不通。链路通了之后再调参数、加文档、设计批量任务。

9.2 切块策略要按文档类型设计

通用固定长度切块只适合原型。真实项目里,Markdown 文档按标题切、代码文档按代码块边界切、表格数据按行切,效果会比固定长度好很多。切块时最好保留元数据,比如来源文件名、章节标题,方便检索结果溯源。

9.3 用重排序提升检索质量

简单余弦相似度检索可能把语义相近但不精确的内容放在前面。数据量上来后,可以加一个重排序模型,把 Top-K 候选重新排序。Rust 侧可以调用独立的 rerank 服务,或者在向量数据库的检索结果上再做一层过滤。

9.4 检索质量需要持续评估

RAG 不是“搭完就完事”的。要准备一组标准问题,定期跑一遍,观察回答是否能命中文档关键信息。发现问题后,先判断是检索问题还是生成问题:打印检索到的 Top-K 文本,看内容是否相关。如果检索到的内容不相关,问题在检索侧;如果检索内容相关但回答不对,问题在提示词或生成模型。

9.5 合规与安全边界

使用 RAG 时要特别注意知识库来源的合法性和隐私边界。

  • 只能索引你有权使用的文档,涉及企业机密、个人隐私的数据必须评估访问权限。
  • 本地部署模型可以降低数据外泄风险,但不等于绝对安全,服务端的访问控制仍然要做。
  • 生产环境暴露 HTTP 接口时,不要直接绑定公网地址,至少加一层 API Key 或认证。
  • 如果文档中包含人脸、声音、版权素材,商用前必须确认授权情况。

10. 总结与下一步

这一篇把 RAG 的最小闭环讲清楚了:从文档切块、向量化、相似度检索,到上下文拼接、生成回答,再到 HTTP 接口和批量任务思路。Rust 在这个链路里的角色不是替代模型服务,而是把文档处理、检索逻辑和接口编排做成一个可靠、可部署的服务。用 Rust 做 Agent 的好处是,最后交付的是一个单一二进制文件,资源占用可控,部署时不用在一台机器上装一堆运行时依赖。

最容易踩的坑有两个:一是模型服务的接口路径和响应结构与代码假设不一致;二是切块太随意导致检索结果相关性差。第一个坑通过打印响应日志就能解决,第二个坑需要持续调切块参数并检查检索结果。

下一步值得继续做的方向有三个:把内存向量存储替换成 Qdrant 或 pgvector,实现数据持久化;加入重排序模型,提升检索精度;把检索回路交给 Agent 控制,做成 Agentic RAG,让 Agent 能自己决定检索词和阅读深度。这些内容我会在 Rust AI Agent 系列的后续文章中展开。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 22:34:28

基于TrustZone的LTDC安全显示配置:从原理到实践

最近在调一块带显示功能的工业HMI板卡&#xff0c;主控是带TrustZone的Cortex-A7/M4组合。项目进行到中期&#xff0c;客户的系统架构师突然提了个需求&#xff1a;屏幕上的安全告警画面“绝对不允许被非安全世界改掉一个字”。换句话说&#xff0c;显示控制器LTDC必须被配置为…

作者头像 李华
网站建设 2026/8/31 22:31:30

PW6606平芯微代理商,内置自动降级机制,目标电压不可用时回退低档

PW6606 PD快充和QC快充协议电压诱骗控制器介绍 摘要&#xff1a; PW6606 是一款高度集成的USB电源传输接收&#xff08;SINK&#xff09;端控制器芯片&#xff0c;支持PD快充和QC快充协议&#xff0c;能够从PD/QC适配器电源请求设定的电压。该芯片内置PD通讯模块和QC通讯模块&a…

作者头像 李华
网站建设 2026/8/31 22:31:20

浏览器投屏轻量实现:WebRTC与getDisplayMedia从原理到代码

很多人一说“投屏”&#xff0c;第一反应就是买硬件盒子、装视频会议软件、或者让接收端电脑提前安装一个专用客户端。但如果你只是想把当前屏幕、某个应用窗口或浏览器标签页&#xff0c;实时投到另一台电脑的浏览器里&#xff0c;这套方案其实太重了。浏览器本身就是最好的投…

作者头像 李华
网站建设 2026/8/31 22:30:20

快手工程A卷复盘:大厂后端笔试考点与备考策略

“工程A卷”这四个字&#xff0c;对我来说记忆太深了。2020年秋招那会儿&#xff0c;我投了快手后端开发岗&#xff0c;点开笔试链接看到试卷标题的瞬间&#xff0c;心跳直接加速。那套题不算特别难&#xff0c;但它的考察覆盖面、题目类型和埋坑方式&#xff0c;基本代表了当年…

作者头像 李华
网站建设 2026/8/31 22:28:20

Qoder AI编程工具深度解析:安装、功能、实战与对比

之前用 AI 编程工具时&#xff0c;经常要同时开着好几个网页和插件&#xff0c;来回切换很影响效率。最近了解到阿里推出了一款叫 Qoder 的 AI 编程工具&#xff0c;把模型对话、代码生成、IDE 插件、Agent 自动执行这些能力整合到了一起&#xff0c;身边不少同事都在尝试。“编…

作者头像 李华