| name | hai-usage-ai |
| description | Use when: using @h-ai/ai, LLM calls, chat completion, tool calling, function calling, MCP server, streaming, memory management, context compression, summarization, token estimation, RAG, knowledge base, AI client, embeddings, reasoning, rerank, file parsing, speech recognition ASR, speech synthesis TTS, audio, A2A agent-to-agent. 使用 @h-ai/ai 进行 LLM 调用、工具定义、MCP 服务器、流式处理、记忆管理、上下文压缩、知识库、推理引擎、Rerank、文件解析、语音识别与合成、A2A 与会话持久化。 |
hai-usage-ai — @h-ai/ai 快速指南
@h-ai/ai 统一提供 LLM、工具、MCP、Embedding、Memory、Retrieval/RAG、Knowledge、Context、File、Rerank、语音(ASR/TTS)与 A2A 能力。
详细 API 表、错误码与长示例见同目录 reference.md。只有需要完整契约或边界用例时再读取,避免把长参考塞进上下文。
能力契约
| 项目 | 契约 |
|---|
| 能力 | LLM/流式生成、结构化 Tool、MCP、Embedding、Memory、RAG/Knowledge、Context、Audio、A2A |
| 适用场景 | 服务端 AI 对话、Agent 工具链、知识问答、长期记忆、语音交互和 Agent-to-Agent;浏览器通过 client/API 代理 |
| 输入 | AIConfigInput、消息/文本/音频、Zod Tool schema、objectId/sessionId/scope、AbortSignal |
| 输出 | 领域操作返回 HaiResult<T>;流式 API 返回 AsyncIterable;Tool 返回 ToolMessage;客户端传输可能抛异常 |
| 限制 | 框架不替代业务授权、沙箱、人工确认和网络出口策略;临时 Store 不持久化;Prompt、检索内容和模型 Tool Call 均视为不可信输入 |
使用边界
ai.tools、ai.stream 是纯函数能力,无需 ai.init()。
ai.llm、ai.embedding、ai.memory、ai.retrieval、ai.rag、ai.knowledge、ai.context、ai.audio、ai.a2a 需要先 await ai.init(...)。
- 未初始化 reldb/vecdb 时,
ai.init() 使用进程内临时 Store,适合 LLM-only 与本地原型;数据在 ai.close() 或进程退出后丢失。
- reldb 与 vecdb 均已初始化时自动使用持久化 DB Provider;生产 Memory/Context/Persona/Knowledge 应使用该路径或自定义
AIStoreProvider。
- 浏览器端不要直接调用 Node-only 能力;通过
@h-ai/api-client 或应用自定义 API/SSE endpoint 代理。
- 公共 API 返回
HaiResult<T> 时按 if (!result.success) return result 处理;不要用 try/catch 包裹正常业务错误。
初始化与关闭
import { core } from '@h-ai/core'
import { ai } from '@h-ai/ai'
const init = await ai.init(core.config.get('ai'))
if (!init.success) return init
await ai.close()
需要持久化时先初始化 DB:
import { reldb } from '@h-ai/reldb'
import { vecdb } from '@h-ai/vecdb'
await reldb.init(core.config.get('db'))
await vecdb.init(core.config.get('vecdb'))
await ai.init(core.config.get('ai'))
自定义存储:
await ai.init(core.config.get('ai'), { storeProvider: myProvider })
await ai.close()
配置要点
llm:
apiKey: ${HAI_AI_LLM_API_KEY:}
baseUrl: ${HAI_AI_LLM_BASE_URL:https://api.openai.com/v1}
model: ${HAI_AI_LLM_MODEL:gpt-4o-mini}
api: chat
timeout: 60000
tempModelCacheTtl: 600000
models:
- { id: claude, model: claude-3-5-sonnet-latest, api: anthropic }
scenarios:
chat: fast
reasoning: strong
embedding: embed
embedding:
dimensions: 1536
batchSize: 100
knowledge:
collection: hai_ai_knowledge
dimension: 1536
chunkOptions:
mode: markdown
maxSize: 1500
overlap: 200
memory:
provider: mem0
defaultTopK: 10
ai.config 是脱敏快照;apiKey、privateKey、URL 内嵌凭证会被隐藏。
常用 API
| 场景 | 首选 API | 备注 |
|---|
| 非流式对话 | ai.llm.chat({ messages }) | OpenAI 兼容 Chat Completion |
| 流式对话 | ai.llm.chatStream({ messages }) | 返回 AsyncIterable |
| 多协议 | 模型 api: chat/responses/anthropic | 底层走 Chat Completions / Responses / Anthropic,公共形状不变 |
| 请求取消 | ai.llm.chat({ messages, signal }) | 传 AbortSignal,abort() 立即停上游生成 |
| 临时模型 | ai.llm.chat({ messages, tempModel }) | 单次请求级临时端点,客户端按 TTL 缓存 |
| 工具定义 | ai.tools.define(...) | Zod schema 转 JSON Schema;handler(input, ctx) 第二参为执行上下文 |
| 工具注册 | registry.register/replace | register 默认拒绝同名并返回 HaiResult;只有 replace 可显式替换 |
| 工具执行 | registry.execute(tc, { signal, objectId, sessionId, deadline?, timeoutMs?, authorize? }) / executeAll(...) | Registry/单次授权必须同时通过;默认串行,支持取消/超时;只有确认无副作用时才 parallel: true |
| MCP 服务 | createMcpServer(...) | 按需连接 HTTP/SSE/Stdio transport |
| Embedding | ai.embedding.embedText(text) | 批量用 embedBatch |
| 记忆 | ai.memory.extract/recall/injectMemories | extract(..., { signal }) 可取消整条提取链;用 objectId 做主体隔离,scope 做业务作用域隔离;scope 隔离越细,candidateMultiplier 调大以防漏召回 |
| 记忆作用域 | ai.memory.scoped({ objectId, scope }) | 多租户推荐入口:所有操作自动绑定 objectId/scope(含归属校验);clear 仅清作用域内 |
| 记忆管理 | ai.memory.admin.clearAll({ confirm: true }) | 唯一的全局清空入口(需显式确认);ai.memory.clear() 空过滤会被拒绝,防误清全局 |
| 角色人格 | ai.persona.save/get/compose | 定义 AI 身份(systemPrompt + traits),compose 组合系统提示词;按 objectId 隔离多租户(不传默认 system 平台角色),scope: { personaId } 关联长期记忆 |
| Retrieval/RAG | ai.retrieval.retrieve / ai.rag.query | Retrieval source 先注册或由配置预置 |
| Knowledge | ai.knowledge.setup/ingest/ask | 入库前会调用 datapipe 清洗分块 |
| Context | ai.context.createManager | 编排 LLM + Memory + RAG + 压缩;manager.consolidate() 把会话固化为长期记忆 |
| Context 并发 | createManager({ concurrency: 'reject' | 'queue' }) | 默认单活动生成防消息乱序;活动生成期间新 chat 返回 CONTEXT_BUSY(reject)或排队(queue) |
| Context 重置 | await manager.reset({ preserveSystemPrompt?, cancelActiveTurn?, waitForMemoryTasks? }) | 异步:终止活动生成、清空消息/摘要/轮次,默认保留系统提示词 |
| 语音识别 | ai.audio.transcribe / transcribeStream | 完整音频返回 HaiResult;持续音频输入用 AudioInputStream 流式返回临时文本 |
| 语音合成 | ai.audio.synthesize / synthesizeStream | text 可为字符串或 AsyncIterable<string>(可接 LLM 文本流边生成边合成) |
| A2A | ai.a2a.registerExecutor/handleRequest | 延迟初始化 SDK handler |
LLM + 工具调用
import { z } from 'zod'
const registry = ai.tools.createRegistry({
authorize: ({ toolName, context }) => canExecuteTool(context.objectId, toolName),
})
const registered = registry.register(ai.tools.define({
name: 'get_weather',
description: '获取天气',
parameters: z.object({ city: z.string() }),
handler: async ({ city }) => ({ city, temperature: 20 }),
}))
if (!registered.success) return registered
const messages: ChatMessage[] = [{ role: 'user', content: '北京天气?' }]
const objectId = 'user-001'
let result = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
while (result.success && result.data.choices[0]?.finish_reason === 'tool_calls') {
const assistant = result.data.choices[0].message
const toolCalls = assistant.tool_calls ?? []
messages.push(assistant)
const toolMessages = await registry.executeAll(toolCalls, { objectId })
if (!toolMessages.success) return toolMessages
messages.push(...toolMessages.data)
result = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
}
流式处理
const processor = ai.stream.createProcessor()
for await (const chunk of ai.llm.chatStream({ messages })) {
const delta = processor.process(chunk)
if (delta?.content) process.stdout.write(delta.content)
}
const message = processor.toAssistantMessage()
临时模型(tempModel)
单次请求级别绕过配置注册模型,直接指定端点与凭据;适用于运行时动态切换模型、多租户各带凭据。
chat / chatStream / ask / askStream 均支持。未指定字段回退全局 LLM 配置 / 环境变量;
请求显式 max_tokens / temperature 优先于 tempModel.maxTokens / temperature。
临时客户端按 apiKey+baseUrl+timeout 缓存,TTL 默认 10 分钟(llm.tempModelCacheTtl 毫秒,可配置),与常驻模型客户端隔离。
const result = await ai.llm.chat({
messages: [{ role: 'user', content: '你好' }],
tempModel: {
model: 'claude-3-5-sonnet',
apiKey: 'sk-temp-xxx',
baseUrl: 'https://temp.endpoint/v1',
maxTokens: 2048,
temperature: 0.3,
timeout: 30000,
},
})
await ai.llm.ask('翻译这段话', { tempModel: { model: 'gpt-4o', apiKey: 'sk-xxx' } })
MCP 服务器
import { createMcpServer, StreamableHTTPServerTransport } from '@h-ai/ai'
const server = createMcpServer({ name: 'my-server', version: '1.0.0' })
server.registerTool('search', {
description: '搜索',
inputSchema: { query: z.string() },
}, async ({ query }) => ({
content: [{ type: 'text', text: `Results for ${query}` }],
}))
await server.connect(new StreamableHTTPServerTransport({ sessionIdGenerator: crypto.randomUUID }))
记忆、RAG 与知识库
memory.provider 默认为 native(推荐):HAI 原生引擎,复用 vecdb/reldb/LLM/Embedding,extract 采用 Mem0 式批量合并(一次 LLM 调用对整批事实与相关既有记忆做 ADD/UPDATE/DELETE/NONE,支持 category)。mem0 则直接使用 mem0ai/oss 的 Memory 引擎:LLM/Embedder 从 llm 配置提取(OpenAI 兼容),向量库从底层 vecdb 后端提取(qdrant/pgvector 复用)。mem0 无法映射 lancedb/chroma 等后端时默认 fail-fast(初始化失败,避免重启后记忆静默丢失);只有显式 memory.allowEphemeralFallback: true 才退回 mem0 自带 in-memory 存储。两者对外 ai.memory.* API 完全一致。
const enriched = await ai.memory.injectMemories(messages, { objectId: 'user-001', topK: 5 })
if (!enriched.success) return enriched
const compressed = await ai.compress.tryCompress(enriched.data, { strategy: 'hybrid', maxTokens: 4000 })
if (!compressed.success) return compressed
const answer = await ai.rag.query('核心架构是什么?', { sources: ['docs'], topK: 5 })
知识库流程:
const setup = await ai.knowledge.setup()
if (!setup.success) return setup
const ingest = await ai.knowledge.ingest({
documentId: 'doc-001',
content: markdownText,
title: '产品手册',
enableEntityExtraction: true,
})
if (!ingest.success) return ingest
const answer = await ai.knowledge.ask('核心架构是什么?')
Context 管理器
const managerResult = ai.context.createManager({
scope: { objectId: 'user-001', sessionId: 'sess-001' },
systemPrompt: '你是一个友好的助手。',
compress: { auto: true, strategy: 'hybrid', maxTokens: 8000 },
memory: { enable: true, enableExtract: true },
})
if (!managerResult.success) return managerResult
const reply = await managerResult.data.chat('你好')
await managerResult.data.save()
真实对话状态(Conversation Commit Layer)
turnCommit: 'manual' 时,chat / chatStream 不自动写入生成文本,只返回 turnId;由调用方提交实际发生的内容(TTS 播放、被打断等场景)。chatStream 在调用上游模型前就产出 turn_started(事件序列 turn_started → delta* → done,中途取消 → cancelled),取消时保留 turnId 与已生成文本:
const m = ai.context.createManager({ turnCommit: 'manual', scope }).data
const controller = new AbortController()
for await (const ev of m.chatStream('展开讲讲', { signal: controller.signal })) {
if (ev.type === 'delta') feedTts(ev.text)
else if (ev.type === 'done') await m.commitTurn(ev.turnId)
else if (ev.type === 'cancelled') await m.interruptTurn(ev.turnId, { text: spokenSoFar })
}
- 只有
committed 的内容进入上下文与记忆;getTurns() 可观测 generated / committed / status。
- 主持人抢话时可在
turn_started / 迭代过程中随时 interruptTurn;轮次进入终态后即便上游流恰好正常结束,chatStream 也不会再产出 done,业务层不会误判为正常完成。
语音(Audio)
ai.audio 提供 ASR/TTS,Provider(OpenAI / MiMo / Qwen / 豆包)作为内部实现,凭据未配置时回退平台环境变量。调用方只表达「音频还是文本、完整还是流式」,不感知 WebSocket / SSE / 厂商事件。
audio:
models:
- { id: asr, provider: qwen, model: qwen3-asr-flash-realtime, operations: [transcribe] }
- { id: tts, provider: qwen, model: qwen3-tts-flash-realtime, operations: [synthesize] }
transcribeModel: asr
synthesizeModel: tts
const t = await ai.audio.transcribe({ audio: { data: wavBytes, format: 'wav' }, language: 'zh' })
if (!t.success) return t
const s = await ai.audio.synthesize({ text: '欢迎', voice: 'Cherry', format: 'pcm16' })
for await (const chunk of ai.audio.transcribeStream({ audio: { chunks: micChunks, format: 'pcm16', sampleRate: 16000 } })) {
updateTranscript(chunk.text, chunk.final)
}
const controller = new AbortController()
for await (const event of ai.audio.synthesizeStream({ text: { id: 'seg-1', text: '欢迎。' }, voice: 'Cherry', signal: controller.signal })) {
if (event.type === 'segment_started')
prepareDecoder(event.format, event.sampleRate, event.channels)
else if (event.type === 'audio')
await player.write(event.data)
}
- 流式方法是
AsyncIterable,迭代期间的连接/协议/上游错误会终止迭代(抛出),不返回 HaiResult。
segment_started 携带服务端解析 Provider 后的真实输出参数(format / sampleRate? / channels?);播放器据此解码,不按请求参数猜测格式。
- 平台不支持的输入方式(如 OpenAI/MiMo 的持续音频输入)会抛
AUDIO_UNSUPPORTED_INPUT,不伪装成实时。
- 浏览器/移动端经
@h-ai/serv 统一语音 WebSocket 入口访问;客户端用已登录 HTTP 请求获取短期一次性 ticket,@h-ai/ai/client 通过 audio.getTicket 建连,不把 IAM access token 放入 URL。浏览器客户端区分正常结束、取消(AUDIO_CANCELLED)与 end 前异常断连(AUDIO_CONNECTION_FAILED);服务端 error 帧保留领域错误码,synthesize 不返回未完成的部分音频。
SvelteKit API 端点模式
export const POST = kit.handler(async ({ request }) => {
const data = await kit.validate.body(request, ChatSchema)
const result = await ai.llm.chat({ messages: data.messages })
if (!result.success) return kit.response.error(result.error.code, result.error.message)
return kit.response.ok(result.data)
})
质量与安全检查
- API Key 只从配置 / 环境变量读取,禁止写死。
- 工具 handler 必须校验输入;用户可见错误走 i18n/错误码。
- 模型输出、Prompt 和检索内容均不可信;不要把用户内容拼入不可覆盖的系统规则。Zod 只保证参数形状,高权限工具还必须在 handler 内校验身份、租户、资源归属和配额。
- 只向模型注册允许自动执行的工具;写操作默认要求业务侧确认或幂等键,不能把“模型请求调用”当作授权。
- 批量导入、批量 embedding 用批量 API,避免 await-in-loop。
- 文件解析/OCR 失败按
HaiAIError.FILE_* 返回,不抛业务异常。
- A2A 认证失败返回
A2A_AUTH_FAILED;callRemoteAgent 只依赖 ai.init(),无需注册本地 executor。远端 URL 仅接受 HTTP(S) 且不得内嵌凭据,应用仍须配置 origin 白名单与出口网络策略。不要记录 token、apiKey、私钥、完整 headers 或带 query 的 URL。