ITPub博客

首页 > 人工智能 > 人工智能 > 技术解读:HarmonyOS 7 Agent 开发实战 04:云端 RAG 搭企业私有知识库问答

技术解读:HarmonyOS 7 Agent 开发实战 04:云端 RAG 搭企业私有知识库问答

人工智能 作者:字节领主 时间:2026-10-20 13:50:36 0 删除 编辑

前三篇把云端大模型接进了 HarmonyOS 7(API 26),搭好了对话脚手架,也让模型学会了用 Tool Calling 查订单查库存。但企业落地还有一大块硬需求没解决: 员工问"年假怎么申请""报销单十五日内交还是十日内",模型要么答不上,要么凭训练语料编一个"像那么回事"的答案 ——制度是你们公司定的,模型根本没见过,它答得越流畅越危险。

把公司制度文档做成企业私有知识库,让 AI 只凭库里的内容作答、答不出就明说,这就是 RAG(Retrieval-Augmented Generation,检索增强生成)要解决的问题。本篇在一个可开箱即跑的 DevEco 工程里把整条流水线落地: 切分 → 向量化入库 → 余弦检索 top-k → 组装增强提示词 → 调云端大模型生成 ,纯逻辑全部配了不联网就能跑的 Hypium 单测。

一、RAG 到底解决了什么问题

没有 RAG 时,想让 AI 答对企业制度,常见两条歪路:

  • 整本文档塞进提示词 :每轮对话把制度全文拼进上下文。几十页文档轻轻松松顶爆上下文窗口,就算塞得下,token 费用也按轮次翻倍,而且绝大部分内容和当前问题毫无关系。

  • 微调模型 :拿制度文档去 fine-tune。成本高、更新慢,制度一改就得重训,而且微调不保证逐字准确——它改变的是"说话风格",不是"知识记忆"。

RAG 的思路完全不同: 把"知识"放在模型外面 。文档先切成小块向量化入库;用户提问时,先把问题也向量化,用相似度从库里捞回最相关的几段,把它们和问题一起组装成提示词,再让大模型 只依据这几段 生成回答。

这样模型每次只看"最相关的几段",窗口小、费用低,而且答案可追溯——页面上能直接展示"AI 依据了哪几段"。制度更新时只需重建索引,不动模型。

架构图

二、切块与向量化:splitIntoChunks 与 Embedder

第一步是把文档变成可检索的粒度。长文档切成 带重叠的滑动窗口 :窗口保证单块不超上下文预算,重叠保证跨界的句子不会被拦腰截断。

splitIntoChunks
(
text
:
string
):
string
[] {
const
clean
:
string
= text.
replace
(
/\s+/g
,
' '
).
trim
();
if
(clean.
length
===
0
) {
return
[];
  }
if
(clean.
length
<=
this
.
window
) {
return
[clean];
  }
const
chunks
:
string
[] = [];
let
start =
0
;
while
(start < clean.
length
) {
const
end
:
number
=
Math
.
min
(start +
this
.
window
, clean.
length
);
    chunks.
push
(clean.
substring
(start, end));
if
(end === clean.
length
) {
break
;
    }
    start = end -
this
.
overlap
;
}
return
chunks;
}

然后是向量化。生产环境应调用云端 Embedding 接口(盘古 / OpenAI 兼容网关的 /embeddings ),本工程为了 不联网就能跑通检索逻辑 ,内置了一个确定性向量化器:用语料的去重字符表当维度,统计字符词频再做 L2 归一化。

export
class
VocabularyEmbedder
implements
Embedder
{
private
readonly
vocab
:
string
[];
readonly
dim
:
number
;
constructor
(
vocab:
string
[]
) {
this
.
vocab
= vocab;
this
.
dim
= vocab.
length
;
  }
embed
(
text
:
string
):
number
[] {
const
counts
:
number
[] =
new
Array
(
this
.
dim
).
fill
(
0
);
for
(
let
i =
0
; i < text.
length
; i++) {
const
idx
:
number
=
this
.
vocab
.
indexOf
(text[i]);
if
(idx >=
0
) {
        counts[idx] = counts[idx] +
1
;
      }
    }
let
norm =
0
;
for
(
const
c
of
counts) { norm = norm + c * c; }
    norm =
Math
.
sqrt
(norm);
if
(norm ===
0
) {
return
counts;
    }
const
out
:
number
[] =
new
Array
(
this
.
dim
);
for
(
let
i =
0
; i < counts.
length
; i++) {
      out[i] = counts[i] / norm;
    }
return
out;
  }
}

它当然比不了真 Embedding 模型的语义能力,但 接口形态( embed(text): number[] )与云端实现完全一致 ——单测和检索编排先在它上面跑绿,上生产时换掉实现即可,编排代码零改动。

三、向量库与余弦检索:top-k 怎么捞

相似度用 余弦相似度 :两个向量的夹角越小越相似,取值范围 [-1, 1],越接近 1 越相关。检索就是"问题向量和每个分块向量算一次余弦,按分数降序取前 k 个":

static
cosine
(
a
:
number
[],
b
:
number
[]):
number
{
if
(a.
length
!== b.
length
|| a.
length
===
0
) {
return
0
;
  }
let
dot =
0
;
let
na =
0
;
let
nb =
0
;
for
(
let
i =
0
; i < a.
length
; i++) {
    dot = dot + a[i] * b[i];
    na = na + a[i] * a[i];
    nb = nb + b[i] * b[i];
  }
const
denom =
Math
.
sqrt
(na) *
Math
.
sqrt
(nb);
return
denom ===
0
?
0
: dot / denom;
}
search
(
queryVec
:
number
[],
topK
:
number
):
RetrievedChunk
[] {
const
scored
:
RetrievedChunk
[] = [];
for
(
let
i =
0
; i <
this
.
chunks
.
length
; i++) {
const
score
:
number
=
InMemoryVectorStore
.
cosine
(queryVec,
this
.
chunks
[i].
vector
);
    scored.
push
(
new
RetrievedChunk
(
this
.
chunks
[i], score));
  }
  scored.
sort
((
x
:
RetrievedChunk
,
y
:
RetrievedChunk
):
number
=>
y.
score
- x.
score
);
const
k
:
number
=
Math
.
min
(topK, scored.
length
);
const
out
:
RetrievedChunk
[] = [];
for
(
let
i =
0
; i < k; i++) {
    out.
push
(scored[i]);
  }
return
out;
}

演示工程用内存实现。量级上来后换成云端向量检索服务或 Milvus / pgvector,方法签名保持一致, RagAgent 不用动。

四、编排成管线:RagAgent 三阶段

RagAgent 把三个阶段串起来: 索引、检索、增强生成 。

indexDocuments
(
docs
:
RagDoc
[]):
void
{
for
(
let
d =
0
; d < docs.
length
; d++) {
const
parts
:
string
[] =
this
.
splitIntoChunks
(docs[d].
text
);
for
(
let
p =
0
; p < parts.
length
; p++) {
const
vec
:
number
[] =
this
.
embedder
.
embed
(parts[p]);
this
.
store
.
add
(
new
Chunk
(docs[d].
id
+
'-'
+ d +
'-'
+ p, parts[p], vec));
    }
  }
}
retrieve
(
query
:
string
):
RetrievedChunk
[] {
return
this
.
store
.
search
(
this
.
embedder
.
embed
(query),
this
.
topK
);
}
async
answer
(
query
:
string
):
Promise
 {
const
hits
:
RetrievedChunk
[] =
this
.
retrieve
(query);
const
context
:
string
= hits.
map
((
h
:
RetrievedChunk
):
string
=>
h.
chunk
.
text
).
join
(
'\n---\n'
);
const
prompt
:
string
=
'【知识库】\n'
+ context +
'\n\n【问题】\n'
+ query;
const
resp
:
ChatResponse
=
await
this
.
client
.
chat
({
model
:
this
.
model
,
messages
: [
      {
role
:
Role
.
SYSTEM
,
content
:
this
.
systemPrompt
},
      {
role
:
Role
.
USER
,
content
: prompt }
    ]
  });
return
{
text
: resp.
text
, hits };
}

系统提示词是 RAG 可靠性的第一道闸门,默认值里写死了硬约束:" 只能依据【知识库】中的内容作答,找不到相关内容就如实说明,严禁编造 "。没有这句,模型很容易把检索结果当参考、"自由发挥"着补全答案——检索做得再准也白搭。

另一个细节: answer 把 hits (命中的分块和分数)一并返回,UI 层直接展示"AI 依据了哪几段知识"。可追溯是 RAG 相对微调最大的工程优势,别把它浪费掉。

五、页面串联与单测

演示页内置三条企业制度语料(年假、报销、IT 密码),初始化时建好索引。每次提问,先以橙色提示条显示检索到的分块,再显示模型回答——用户能直接看见"答案从哪来",这比纯文字答案更能建立信任:

const
result =
await
this
.
agent
.
answer
(text);
const
ctxTip
:
string
=
'检索到 '
+ result.
hits
.
length
+
' 段知识:'
+
  result.
hits
.
map
((h):
string
=>
h.
chunk
.
text
.
substring
(
0
,
16
) +
'…'
).
join
(
' | '
);
this
.
messages
= [...
this
.
messages
,
new
ChatUIMessage
(
BubbleRole
.
SYSTEM
, ctxTip,
MsgStatus
.
DONE
),
new
ChatUIMessage
(
BubbleRole
.
ASSISTANT
, result.
text
,
MsgStatus
.
DONE
)];

单测覆盖四层:向量化器的确定性(同文本同向量、词表外字符忽略)、余弦相似度的数学性质(同向量为 1、正交为 0)、top-k 召回的正确性(相关分块必须排第一)、以及 RAG 全链路——用 Mock 客户端拦截请求,断言 发给模型的用户消息里确实带上了命中知识片段和【知识库】标记 ,证明增强提示词组装无误。加上切分窗口、底座复用等用例,工程共 27 条断言,全部不发起网络请求。

六、跑起来:三步

  • DevEco Studio 26 打开工程,右键 ohosTest → Run ,确认 27 条断言全绿(也可先 node verify-node.cjs 在命令行验证纯逻辑)。

  • 打开 pages/Index.ets ,把 buildAgent() 里的 baseUrl / apiKey / model 换成自己的网关配置; docs 换成你们的制度文档。

  • 连真机运行,输入"年假怎么申请?",先看橙色提示条里检索到了什么,再看回答是否只依据这些片段。实际对话表现以真机实测为准。

七、五个高频坑(接 RAG 前先看)

  • 直接把整本文档塞进提示词 :先切分入库、按需检索 top-k,别让上下文窗口和 token 费用一起爆炸。

  • 切块太大或太碎 :太大检索粒度粗、太碎语义割裂,用滑动窗口加重叠(工程默认 200/40)取中间值。

  • 检索与生成用了不同向量表 :索引和提问必须走同一套 Embedder、同一词汇表,否则维度对不上、相似度全是噪声。

  • 模型超出知识库就瞎编 :系统提示词必须写死"找不到就说未找到、严禁编造",并要求回答引用依据。

  • 把私库明文传第三方公有云 :敏感制度文档走企业内网 Embedding 服务和私有向量库,守住数据边界。

避坑清单

参考与出处

  • 上篇《HarmonyOS 7 Agent 开发实战 03:Tool Calling 让 AI 查实时订单与库存》(本工程在其底座上扩展)

  • 华为开发者官网 · NetworkKit(@ohos.net.http)、应用测试框架 Hypium

  • OpenAI Embeddings API 规范(/embeddings 接口形态,盘古等兼容网关同构)

最后一句 :RAG 不是给模型塞知识,而是教它先查再答——答案可以生成,事实必须检索。


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:Juejin HarmonyOS

原创地址:https://juejin.cn/post/7697147679491047466

原文作者:威哥爱编程;如有侵权,请联系管理员删除。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2019-03-03

  • 博文量
    2
  • 访问量
    222