@h-ai/ai
v0.1.0-alpha.10
Published
Hai Framework AI module (LLM, MCP, tools, and clients).
Downloads
388
Readme
@h-ai/ai
AI 能力模块,提供统一的 ai 对象访问大模型、MCP 服务和工具调用功能。
功能特性
| 子系统 | 说明 | 依赖子系统 | | ---------- | ------------------------------------------------------------------- | ------------------------------------------------- | | Tools | 工具定义(Zod → JSON Schema)、注册表管理、批量执行 | —(纯函数) | | Stream | 流式块处理、SSE 编解码、流收集 | —(纯函数) | | MCP Server | MCP HTTP 服务器、多传输层支持 | —(独立创建) | | Client | 前端轻量客户端(流式响应、SSE 解析、记忆/会话查询) | —(独立创建,依赖 api-client) | | LLM | 大模型调用(非流式 / 流式)、简易问答、对话历史 | LLM Provider | | Token | Token 估算(文本 / 消息列表) | LLM | | MCP | 工具注册与调用、资源读取、提示词管理 | LLM | | Embedding | 文本向量化、单条 / 批量嵌入 | LLM Provider | | Summary | 消息摘要生成(支持增量摘要) | LLM | | Rerank | 重排序(查询-文档相关性排序) | LLM | | File | 文件解析(PDF / DOCX / HTML / OCR) | LLM(OCR 需 Vision 模型) | | Compress | 上下文压缩(sliding-window / summary / hybrid) | LLM + Summary | | Memory | 对话记忆提取、存储、检索、注入(支持 objectId 隔离) | LLM + Embedding + Store | | Reasoning | 推理引擎(CoT / ReAct / Plan-Execute) | LLM + Tools | | Retrieval | 多来源向量检索、分数过滤 | Embedding + Store | | RAG | 检索增强问答(Retrieval + LLM) | LLM + Retrieval | | Knowledge | 知识库管理(入库、检索、问答、实体提取) | LLM + Embedding + VecDB + RelDB + Datapipe | | Context | 有状态上下文管理器(压缩 + LLM/Memory/RAG/Reasoning 编排 + 持久化) | LLM + Memory + Compress + RAG + Reasoning + Store | | A2A | Agent-to-Agent 协议(代理间通信与任务委派) | LLM + A2A 配置(延迟初始化) | | Store | 统一存储抽象(对话记录、会话、检索源、记忆、上下文持久化) | AIStoreProvider(默认实现基于 RelDB + VecDB) |
安装
pnpm add @h-ai/ai依赖
@h-ai/datapipe— 数据管道(知识库文档切分,内部使用)
存储层通过 AIStoreProvider 抽象,不再硬性依赖 reldb / vecdb:
- 默认 DB Provider(未传入自定义 Provider 时自动使用):需
@h-ai/reldb+@h-ai/vecdb,且在ai.init()前完成初始化 - 自定义 Provider:实现
AIStoreProvider接口后通过ai.init(config, { storeProvider })注入,无需 reldb / vecdb
ai.tools和ai.stream为纯函数子系统,无需 init 即可使用。
快速开始
方式一:默认 DB Provider(reldb + vecdb)
import { ai } from '@h-ai/ai'
import { reldb } from '@h-ai/reldb'
import { vecdb } from '@h-ai/vecdb'
// 1. 初始化存储依赖
await reldb.init({ type: 'sqlite', database: './ai.db' })
await vecdb.init({ type: 'lancedb', path: './ai-vec.db' })
// 2. 初始化 AI 模块(自动使用已初始化的 reldb + vecdb 单例)
await ai.init({
llm: {
model: 'gpt-4o-mini',
apiKey: process.env.HAI_AI_LLM_API_KEY,
},
})
// 3. 对话
const result = await ai.llm.chat({
messages: [
{ role: 'system', content: '你是一个有帮助的助手' },
{ role: 'user', content: '你好!' },
],
})
if (result.success) {
const reply = result.data.choices[0].message.content
}
// 4. 关闭
ai.close()方式二:自定义 StoreProvider
import type { AIStoreProvider } from '@h-ai/ai'
import { ai } from '@h-ai/ai'
// 实现自定义 Provider(对接 SaaS API、其他数据库等)
const myProvider: AIStoreProvider = { /* ... */ }
await ai.init(
{ llm: { model: 'gpt-4o-mini', apiKey: process.env.HAI_AI_LLM_API_KEY } },
{ storeProvider: myProvider }, // 无需 reldb / vecdb
)
// 使用方式与默认 Provider 完全一致
const result = await ai.llm.chat({ messages: [{ role: 'user', content: '你好' }] })
ai.close()初始化与配置
完整配置
await ai.init({
// LLM 配置
llm: {
apiKey: 'sk-xxx',
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-4o-mini', // 默认模型
maxTokens: 4096,
temperature: 0.7,
timeout: 60000, // 请求超时(毫秒)
// 多模型注册
models: [
{ id: 'fast', model: 'gpt-4o-mini', temperature: 0.3 },
{ id: 'strong', model: 'gpt-4o', maxTokens: 8192 },
{ id: 'embed', model: 'text-embedding-3-small', baseUrl: 'https://...' },
],
scenarios: {
chat: 'fast',
reasoning: 'strong',
},
},
// Embedding 配置
embedding: {
dimensions: 1536,
batchSize: 100,
},
// Knowledge 配置
knowledge: {
collection: 'hai_ai_knowledge',
dimension: 1536,
enableEntityExtraction: true,
cleanOptions: {
removeHtml: true,
normalizeWhitespace: true,
},
chunkOptions: {
mode: 'markdown', // sentence | paragraph | markdown | page | custom
maxSize: 1500,
overlap: 200,
},
entityBoostWeight: 0.15, // 实体命中加权系数 [0, 1]
},
// Memory 配置
memory: {
maxEntries: 1000,
embeddingEnabled: true, // 启用向量检索(关闭则仅关键词匹配)
recencyDecay: 0.95, // 时间衰减系数
defaultTopK: 10,
},
// Token 配置
token: {
tokenRatio: 0.25, // Token 估算系数(4 字符 ≈ 1 token)
},
// Summary 配置
summary: {
systemPrompt: '...', // 可选,覆盖内置摘要提示词
},
// Compress 配置
compress: {
defaultStrategy: 'hybrid', // summary | sliding-window | hybrid
defaultMaxTokens: 0, // 0 = 模型 maxTokens × 80%
preserveLastN: 4, // 保留最近 N 条不压缩
},
// MCP 配置
mcp: {
server: {
name: 'my-app',
version: '1.0.0',
},
},
})配合 core 配置文件使用
import { ai } from '@h-ai/ai'
import { core } from '@h-ai/core'
await core.init({ configDir: './config' })
await ai.init(core.config.get('ai'))
// ...
ai.close()
await core.close()初始化状态检测
ai.isInitialized // boolean
ai.config // 当前配置对象,未初始化时为 nullLLM 对话 — ai.llm
非流式对话
const result = await ai.llm.chat({
messages: [
{ role: 'system', content: '你是一个翻译助手' },
{ role: 'user', content: '将以下内容翻译为英文:你好世界' },
],
temperature: 0.3,
max_tokens: 1000,
})
if (result.success) {
const response = result.data
const content = response.choices[0].message.content
const usage = response.usage // { prompt_tokens, completion_tokens, total_tokens }
}指定模型
// 使用 init 时注册的模型 ID 或直接传模型名
const result = await ai.llm.chat({
model: 'gpt-4o',
messages: [{ role: 'user', content: '复杂推理问题' }],
})流式对话
const messages = [{ role: 'user' as const, content: '写一首关于编程的诗' }]
for await (const chunk of ai.llm.chatStream({ messages })) {
const delta = chunk.choices[0]?.delta?.content
if (delta) {
process.stdout.write(delta) // 逐字输出
}
}多模态输入(图片)
const result = await ai.llm.chat({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
{ type: 'text', text: '描述这张图片' },
{ type: 'image_url', image_url: { url: 'https://example.com/photo.jpg', detail: 'high' } },
],
}],
})获取可用模型列表
const models = await ai.llm.listModels()
if (models.success) {
// models.data: string[]
}简易问答
// 非流式(返回纯文本回复)
const result = await ai.llm.ask('什么是 TypeScript?')
if (result.success) {
console.warn(result.data) // string
}
// 流式
for await (const text of ai.llm.askStream('讲个笑话')) {
process.stdout.write(text)
}对话历史
// 获取某对象的聊天记录(需在 chat 请求中传 objectId + enablePersist)
const history = await ai.llm.getHistory('user-001', { sessionId: 'sess-001', limit: 50 })
if (history.success) {
// history.data: ChatRecord[]
}
// 列出某对象的所有会话
const sessions = await ai.llm.listSessions('user-001')
if (sessions.success) {
// sessions.data: SessionInfo[]
}工具调用 — ai.tools
ai.tools为纯函数操作,不需要初始化即可使用。
定义工具
import { ai } from '@h-ai/ai'
import { z } from 'zod'
const weatherTool = ai.tools.define({
name: 'get_weather',
description: '获取指定城市的天气信息',
parameters: z.object({
city: z.string().describe('城市名称'),
unit: z.enum(['celsius', 'fahrenheit']).optional().describe('温度单位'),
}),
handler: async ({ city, unit }) => {
// 调用天气 API
return { temperature: 20, city, unit: unit ?? 'celsius' }
},
})注册表管理
const registry = ai.tools.createRegistry()
// 注册单个 / 批量注册
registry.register(weatherTool)
registry.registerMany([tool1, tool2, tool3])
// 查询
registry.has('get_weather') // true
registry.get('get_weather') // Tool 对象
registry.getNames() // ['get_weather', ...]
registry.size // 注册数量
// 获取 OpenAI 工具定义(传递给 LLM)
const definitions = registry.getDefinitions() // ToolDefinition[]
// 执行工具调用
const toolMessage = await registry.execute(toolCall) // HaiResult<ToolMessage>
// 批量执行(支持并行)
const toolMessages = await registry.executeAll(toolCalls, { parallel: true })
// 取消注册 / 清空
registry.unregister('get_weather')
registry.clear()工具集成 LLM 调用
const result = await ai.llm.chat({
messages,
tools: registry.getDefinitions(),
tool_choice: 'auto', // 'auto' | 'none' | { type: 'function', function: { name: '...' } }
})
if (result.success) {
const choice = result.data.choices[0]
// 检查是否有工具调用
if (choice.finish_reason === 'tool_calls' && choice.message.tool_calls) {
// 执行所有工具调用并获得 ToolMessage[]
const toolResults = await registry.executeAll(choice.message.tool_calls, { parallel: true })
if (toolResults.success) {
// 将工具结果追加到消息继续对话
messages.push(choice.message) // assistant 消息(含 tool_calls)
messages.push(...toolResults.data) // tool 结果消息
}
}
}流处理 — ai.stream
ai.stream为纯函数操作,不需要初始化即可使用。
逐块处理
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 result = processor.getResult()
// { content: string, toolCalls: ToolCall[], finishReason: string | null }
// 转为 assistant 消息(可直接 push 到 messages 数组)
const assistantMsg = processor.toAssistantMessage()
// { role: 'assistant', content: '...', tool_calls?: [...] }
// 重置(复用 processor)
processor.reset()快捷收集
// 一次性收集全部内容(不做逐块处理)
const result = await ai.stream.collect(ai.llm.chatStream({ messages }))
// { content: string, toolCalls: ToolCall[], finishReason: string | null }流式工具调用
流式响应中 LLM 触发工具调用时,processor 会同样累积 tool_calls,通过 finishReason 判断后执行工具并继续对话:
const registry = ai.tools.createRegistry()
registry.registerMany([weatherTool, searchTool])
const messages: ChatMessage[] = [{ role: 'user', content: '北京今天天气怎么样?' }]
let continueLoop = true
while (continueLoop) {
const processor = ai.stream.createProcessor()
for await (const chunk of ai.llm.chatStream({ messages, tools: registry.getDefinitions() })) {
const delta = processor.process(chunk)
if (delta?.content) {
process.stdout.write(delta.content)
}
}
const result = processor.getResult()
if (result.finishReason === 'tool_calls' && result.toolCalls.length > 0) {
// 将 assistant 消息(含 tool_calls)追加到对话
messages.push(processor.toAssistantMessage())
// 执行所有工具调用
const toolResults = await registry.executeAll(result.toolCalls, { parallel: true })
if (toolResults.success) {
messages.push(...toolResults.data) // 追加 tool 结果消息
}
// 继续循环,让 LLM 消费工具结果
}
else {
// finish_reason 为 'stop',对话结束
messages.push(processor.toAssistantMessage())
continueLoop = false
}
}SSE 编解码
SSE 编解码适用于自建 HTTP 流式接口(如 SvelteKit GET 路由推送 AI 内容到前端):
// 服务端:将 LLM 流式 chunk 编码为 SSE 事件推送给客户端
async function* streamToSSE(messages: ChatMessage[]): AsyncGenerator<string> {
for await (const chunk of ai.llm.chatStream({ messages })) {
const delta = chunk.choices[0]?.delta?.content
if (delta) {
yield ai.stream.encodeSSE({ event: 'delta', data: JSON.stringify({ text: delta }) })
}
}
yield ai.stream.encodeSSE({ event: 'done', data: '' })
}
// 客户端:解码 SSE 分片(rawText 可能是不完整的多行数据)
const decoder = ai.stream.createSSEDecoder()
for await (const rawChunk of response.body) {
for (const event of decoder.decode(rawChunk)) {
if (event.event === 'delta') {
const { text } = JSON.parse(event.data!)
process.stdout.write(text)
}
else if (event.event === 'done') {
break
}
}
}
// 重置解码器以复用(如下一次请求)
decoder.reset()
createSSEDecoder内部维护缓冲区,自动拼接跨分片的不完整 SSE 帧,无需手动处理粘包。
Embedding — ai.embedding
单条文本嵌入
const result = await ai.embedding.embedText('这是一段需要向量化的文本')
if (result.success) {
const vector = result.data // number[](如 1536 维向量)
}批量文本嵌入
const result = await ai.embedding.embedBatch([
'第一段文本',
'第二段文本',
'第三段文本',
])
if (result.success) {
const vectors = result.data // number[][]
}完整嵌入请求
const result = await ai.embedding.embed({
input: ['文本1', '文本2'],
model: 'text-embedding-3-small',
dimensions: 1536,
})
if (result.success) {
const response = result.data
// response.data: EmbeddingItem[]({ index, embedding })
// response.usage: { prompt_tokens, total_tokens }
}推理引擎 — ai.reasoning
支持三种推理策略:
| 策略 | 说明 | 适用场景 |
| -------------- | -------------------------- | ------------------ |
| cot | 思维链(Chain of Thought) | 分步推理 |
| react | 思考-行动-观察循环 | 需要工具调用的推理 |
| plan-execute | 先规划后执行 | 复杂多步任务 |
基本推理
const result = await ai.reasoning.run('如何计算圆的面积?', {
strategy: 'cot', // 默认 'react'
})
if (result.success) {
const { answer, steps, rounds } = result.data
// answer: 最终回答
// steps: ReasoningStep[](每步包含 type, content)
// type: 'thought' | 'action' | 'observation' | 'plan' | 'answer'
// rounds: 执行轮数
}带工具的推理
const registry = ai.tools.createRegistry()
registry.register(calculatorTool)
registry.register(searchTool)
const result = await ai.reasoning.run('北京到上海的距离是多少公里?', {
strategy: 'react',
tools: registry,
maxRounds: 5, // 最大轮次(默认 10)
model: 'gpt-4o',
temperature: 0.2,
systemPrompt: '你是一个地理知识专家',
})检索 — ai.retrieval
注册检索源
检索源可以在 ai.init() 中通过 retrieval.sources 预配置,也可以在初始化后动态注册。
方式一:初始化时预配置
await ai.init({
llm: { apiKey: 'sk-xxx', model: 'gpt-4o-mini' },
retrieval: {
sources: [
{ id: 'docs', collection: 'documentation', name: '产品文档', topK: 5, minScore: 0.7 },
{ id: 'faq', collection: 'faq', name: '常见问题' },
],
},
})方式二:动态注册
// 添加检索源(持久化到 DB)
await ai.retrieval.addSource({
id: 'docs',
collection: 'documentation',
name: '产品文档',
topK: 5,
minScore: 0.7,
})
await ai.retrieval.addSource({
id: 'faq',
collection: 'faq',
name: '常见问题',
})
// 查看已注册的源(从 DB 读取)
const sources = await ai.retrieval.listSources() // RetrievalSource[]执行检索
const result = await ai.retrieval.retrieve({
query: '如何配置数据库连接?',
sources: ['docs', 'faq'], // 指定源(不传则查所有源)
topK: 10,
minScore: 0.6,
})
if (result.success) {
for (const item of result.data.items) {
// item.content — 匹配的文本内容
// item.score — 相似度分数
// item.sourceId — 来自哪个检索源
// item.citation — 引用信息(documentId, title, url 等)
}
}移除检索源
await ai.retrieval.removeSource('docs')RAG 问答 — ai.rag
基于 Retrieval + LLM 的检索增强问答。
const result = await ai.rag.query('项目的部署流程是什么?', {
sources: ['docs'],
topK: 5,
minScore: 0.6,
model: 'gpt-4o-mini',
temperature: 0.3,
systemPrompt: '根据检索到的文档内容回答问题,注明信息来源。',
})
if (result.success) {
const { answer, citations, context } = result.data
// answer: LLM 生成的回答
// citations: Citation[](引用来源列表)
// context: RagContextItem[](使用的上下文片段)
}自定义上下文格式
const result = await ai.rag.query('问题', {
formatContext: (items) => {
return items
.map((item, i) => `[来源${i + 1}] ${item.content}`)
.join('\n\n')
},
})多轮 RAG 对话
const result = await ai.rag.query('更详细地解释第二点', {
messages: [
{ role: 'user', content: '部署流程是什么?' },
{ role: 'assistant', content: '部署分为三步:1. 构建镜像 2. 推送仓库 3. 更新集群' },
],
})知识库 — ai.knowledge
完整的知识库管理:文档入库(自动分块 + 向量化 + 实体提取)→ 语义检索 → 知识问答。
初始化知识库
await ai.knowledge.setup({
collection: 'my-knowledge',
dimension: 1536,
})文档入库
const result = await ai.knowledge.ingest({
documentId: 'doc-001',
content: markdownContent,
title: '产品使用手册',
url: 'https://docs.example.com/manual',
enableEntityExtraction: true, // 自动提取人物、项目、概念等实体
cleanOptions: {
removeHtml: true,
},
chunkOptions: {
mode: 'markdown', // sentence | paragraph | markdown | page | custom
maxSize: 1500,
overlap: 200,
},
metadata: { category: 'manual', version: '2.0' },
})
if (result.success) {
const { chunkCount, entities, duration } = result.data
// chunkCount: 分块数量
// entities: KnowledgeEntity[](提取到的实体)
// duration: 耗时(毫秒)
}知识检索
const result = await ai.knowledge.retrieve('数据库配置方法', {
topK: 10,
minScore: 0.6,
enableEntityBoost: true, // 实体命中时加权(提高相关性)
collection: 'my-knowledge',
})
if (result.success) {
for (const item of result.data.items) {
// item.content — 匹配的文本
// item.score — 综合分数
// item.citation — 引用(documentId, title, url)
// item.matchedEntities — 命中的实体名称
}
}知识问答
const result = await ai.knowledge.ask('如何配置 Redis 缓存?', {
topK: 5,
model: 'gpt-4o-mini',
temperature: 0.3,
systemPrompt: '基于提供的知识回答,标注信息来源。',
})
if (result.success) {
const { answer, citations, usage } = result.data
// answer: LLM 生成的回答
// citations: Citation[](引用列表)
// usage: { prompt_tokens, completion_tokens, total_tokens }
}多轮知识问答
const result = await ai.knowledge.ask('请详细解释第二步', {
messages: [
{ role: 'user', content: 'Redis 配置流程?' },
{ role: 'assistant', content: '分三步:1. 安装 2. 配置 3. 连接测试' },
],
})实体管理
// 列出所有实体
const entities = await ai.knowledge.listEntities({
type: 'person', // person | project | concept | organization | location | event | other
keyword: '张',
limit: 50,
})
// 按实体查询关联文档
const result = await ai.knowledge.findByEntity('Redis', {
collection: 'my-knowledge',
type: 'concept',
})
if (result.success) {
// result.data: EntityDocumentResult[]
}记忆管理 — ai.memory
从对话中自动提取关键信息(事实、偏好、事件等),存储并在后续对话中检索注入。支持 objectId 隔离不同对象(用户/智能体)的记忆空间。
记忆类型
| 类型 | 说明 |
| ------------- | -------------------- |
| fact | 事实信息 |
| preference | 用户偏好、习惯 |
| event | 重要事件、截止日期 |
| entity | 关键人物、组织、项目 |
| instruction | 用户的明确指令或规则 |
自动提取记忆
const messages = [
{ role: 'user' as const, content: '我叫张三,我更喜欢用中文回复,我的项目叫 HAI Framework' },
{ role: 'assistant' as const, content: '好的张三,我记住了你的偏好。' },
]
const result = await ai.memory.extract(messages, {
types: ['preference', 'entity'], // 只提取指定类型(可选)
minImportance: 0.5, // 过滤低重要性条目(可选)
objectId: 'user-001', // 关联到对象标识(可选,不传为全局)
model: 'gpt-4o-mini', // 指定提取模型(可选)
systemPrompt: 'Only extract durable user preferences and explicit long-term instructions.', // 自定义提取提示词(可选)
})
if (result.success) {
// result.data: MemoryEntry[](已自动存储到内存)
// 每条包含:id, content, type, importance, vector, createdAt 等
}手动添加记忆
const result = await ai.memory.add({
content: '用户是后端开发工程师,使用 TypeScript',
type: 'fact',
importance: 0.8, // [0, 1],默认 0.5
objectId: 'user-001', // 可选,不传为全局
metadata: { tags: ['tech', 'profile'] },
})
if (result.success) {
const entry = result.data // MemoryEntry(含自动生成的 id 和 vector)
}更新记忆
const result = await ai.memory.update('mem_1709712345_abc12345', {
content: '用户是全栈开发工程师',
importance: 0.9,
})
if (result.success) {
// result.data: MemoryEntry(更新后的条目)
}检索记忆
const result = await ai.memory.recall('用户的编程语言偏好', {
topK: 5, // 返回数量(默认 10)
types: ['preference'], // 按类型过滤
minImportance: 0.3, // 最低重要性
recencyWeight: 0.2, // 时间权重 [0, 1](0 = 纯相似度,1 = 纯时间排序)
objectId: 'user-001', // 按对象过滤(可选)
})
if (result.success) {
for (const memory of result.data) {
// memory.content — 记忆内容
// memory.type — 类型
// memory.importance — 重要性
// memory.createdAt — 创建时间
// memory.accessCount — 被检索次数
}
}注入记忆到对话
const messages = [
{ role: 'system' as const, content: '你是一个编程助手' },
{ role: 'user' as const, content: '帮我写一个排序函数' },
]
const result = await ai.memory.injectMemories(messages, {
topK: 5, // 注入记忆数量
maxTokens: 500, // 记忆最大 Token 预算
position: 'system', // 'system' = 追加到 system 末尾,'before-last' = 插入最后一条用户消息之前
objectId: 'user-001', // 按对象过滤(可选)
})
if (result.success) {
// result.data: ChatMessage[](已注入记忆的消息列表)
// system 消息末尾会追加:
// --- Relevant Memories ---
// [1] (preference) 用户偏好使用 TypeScript
// [2] (fact) 用户是后端工程师
// --- End Memories ---
const response = await ai.llm.chat({ messages: result.data })
}列表、删除与清空
// 列出记忆(支持过滤)
const list = await ai.memory.list({
types: ['fact', 'preference'],
objectId: 'user-001',
limit: 50,
})
// 分页列出记忆
const page = await ai.memory.listPage({
objectId: 'user-001',
offset: 0,
limit: 20,
})
if (page.success) {
// page.data.items: MemoryEntry[]
// page.data.total: number
}
// 删除指定记忆
await ai.memory.remove('mem_1709712345_abc12345')
// 按类型清空
await ai.memory.clear({ types: ['event'] })
// 按对象清空
await ai.memory.clear({ objectId: 'user-001' })
// 全部清空
await ai.memory.clear()Token 估算 — ai.token
估算文本或消息列表的 Token 数量。
// 估算消息列表的 Token 数
const tokenCount = ai.token.estimateMessages(messages)
// tokenCount: number
// 估算纯文本的 Token 数
const count = ai.token.estimateText('Hello world')
// count: number(中文约 1.5 token/字,英文约 4 字符/token)消息摘要 — ai.summary
对消息列表生成 LLM 摘要,支持增量追加。
// 对消息生成摘要
const result = await ai.summary.summarize(messages, {
model: 'gpt-4o-mini',
temperature: 0.3,
systemPrompt: 'Focus on key decisions, constraints, and unresolved action items.',
})
if (result.success) {
const { summary, tokenCount, coveredMessages } = result.data
// summary: 摘要文本
// tokenCount: 摘要的估算 Token 数
// coveredMessages: 覆盖的原始消息数
}
// 生成纯文本摘要
const textResult = await ai.summary.generate(messages, {
systemPrompt: 'Summarize in one concise paragraph for handoff notes.',
})
if (textResult.success) {
// textResult.data: string(摘要文本)
}
// 增量摘要(在已有摘要基础上追加)
const updated = await ai.summary.summarize(newMessages, {
systemPrompt: 'Keep the summary focused on product decisions and next steps.',
previousSummary: existingSummary,
})上下文压缩 — ai.compress
压缩超长对话至指定 Token 预算。
压缩策略
| 策略 | 说明 |
| ---------------- | ---------------------------------------- |
| sliding-window | 保留 system + 最近 N 条消息,丢弃旧消息 |
| summary | 对旧消息生成 LLM 摘要替换 |
| hybrid(默认) | 先滑动窗口,仍超限则对被移除部分生成摘要 |
压缩消息列表
const result = await ai.compress.tryCompress(messages, {
strategy: 'hybrid', // summary | sliding-window | hybrid
maxTokens: 4000, // 目标 Token 预算
preserveSystem: true, // 保留 system 消息(默认 true)
preserveLastN: 4, // 保留最近 N 条不压缩(默认 4)
summaryModel: 'gpt-4o-mini', // 摘要用模型
})
if (result.success) {
const { messages: compressed, originalTokens, compressedTokens, removedCount, summary } = result.data
// compressed: 压缩后的消息列表
// originalTokens: 原始 Token 数
// compressedTokens: 压缩后 Token 数
// removedCount: 被移除/合并的消息数
// summary: 生成的摘要文本(仅 summary/hybrid 策略有值)
const response = await ai.llm.chat({ messages: compressed })
}上下文管理 — ai.context
有状态上下文管理器,适用于多轮对话场景。自动跟踪 Token 使用量、超限时自动压缩,并可编排 LLM / Memory / RAG / Reasoning / Tools 进行完整的对话流程。支持会话级持久化。
创建管理器并对话
const managerResult = ai.context.createManager({
scope: { objectId: 'user-001', sessionId: 'sess-001' },
systemPrompt: '你是一个友好的助手。',
model: 'gpt-4o-mini',
compress: {
maxTokens: 8000,
strategy: 'hybrid',
preserveLastN: 4,
auto: true,
},
memory: { enable: true, enableExtract: true, topK: 5 },
})
if (!managerResult.success)
return
const manager = managerResult.data
// 直接对话(自动编排 LLM + 记忆注入/提取 + 压缩)
const chatResult = await manager.chat('你好')
if (chatResult.success) {
// chatResult.data.reply — LLM 回复
// chatResult.data.model — 使用的模型
// chatResult.data.usage — Token 使用统计
}
// 流式对话
for await (const event of manager.chatStream('讲个故事')) {
if (event.type === 'delta')
process.stdout.write(event.text)
if (event.type === 'done')
console.warn('\n完成', event.model)
}
// 持久化
await manager.save()底层消息管理
const managerResult = ai.context.createManager({
compress: { maxTokens: 8000, strategy: 'hybrid' },
})
const manager = managerResult.data
// 手动追加消息(超限时自动触发压缩)
await manager.addMessage({ role: 'user', content: userInput })
// 获取当前消息列表(已压缩)
const msgs = manager.getMessages()
const response = await ai.llm.chat({ messages: msgs.data })
// 追加助手回复
await manager.addMessage(response.data.choices[0].message)
// 查看 Token 使用量
const usage = manager.getTokenUsage()
// usage.data.current / usage.data.budget
// 查看历史摘要
const summaries = manager.getSummaries()
// 重置
manager.reset()恢复已有会话
const managerResult = await ai.context.restoreManager(
{ objectId: 'user-001', sessionId: 'sess-001' },
{ compress: { maxTokens: 8000 }, memory: { enable: true } },
)
if (managerResult.success) {
const manager = managerResult.data
// manager 已包含之前保存的消息和摘要
const result = await manager.chat('继续上次的话题')
}启用 RAG / Reasoning / Tools
const registry = ai.tools.createRegistry()
registry.register(ai.tools.define({
name: 'search',
description: '搜索',
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => `结果: ${query}`,
}))
const managerResult = ai.context.createManager({
scope: { objectId: 'user-001', sessionId: 'sess-001' },
rag: { enable: true, sources: ['docs'], topK: 5 },
reasoning: { enable: true, strategy: 'react', maxRounds: 5 },
tools: registry,
})列出对象的所有会话
const result = await ai.context.listSessions('user-001')
if (result.success) {
for (const session of result.data) {
// session.sessionId / session.objectId / session.createdAt / session.updatedAt
}
}重排序 — ai.rerank
对检索结果按查询相关性重新排序,提升 RAG / 检索质量。
// 快捷方式:文本列表重排序
const result = await ai.rerank.rerankTexts('数据库配置方法', [
'本文介绍如何配置 PostgreSQL 连接池',
'前端组件的生命周期管理',
'数据库连接字符串的最佳实践',
], 2) // topN: 返回前 2 个最相关的
if (result.success) {
for (const item of result.data) {
// item.index — 原始位置
// item.relevanceScore — 相关性分数
// item.document — 原始文本
}
}
// 完整请求
const result = await ai.rerank.rerank({
query: '配置方法',
documents: ['文档1', '文档2', '文档3'],
topN: 3,
returnDocuments: true,
})文件解析 — ai.file
解析多种格式文件为文本内容,支持 OCR。
const result = await ai.file.parse({
content: fileBuffer, // Buffer | string
mimeType: 'application/pdf',
maxPages: 50,
outputFormat: 'markdown', // 'text' | 'markdown'
useOcr: false, // 使用 Vision LLM 做 OCR
})
if (result.success) {
// result.data.text — 解析出的文本内容
// result.data.pages — 页数(PDF 有效)
// result.data.metadata — 文件元信息
}支持格式:text(UTF-8)、html(HTML→text)、pdf(pdfjs-dist)、docx(mammoth)、ocr(Vision LLM)。
A2A — ai.a2a
Agent-to-Agent 协议支持,实现代理间通信与任务委派。A2A 为延迟初始化子系统,需在配置中提供 a2a 配置段。
配置
await ai.init({
llm: { model: 'gpt-4o-mini', apiKey: process.env.API_KEY },
a2a: {
agentCard: {
name: 'my-agent',
description: '智能助手',
url: 'https://agent.example.com',
skills: [{ id: 'chat', name: '对话', description: '通用对话能力' }],
},
},
})注册执行器并处理请求
import type { AgentExecutor } from '@h-ai/ai'
// 实现执行器
const executor: AgentExecutor = {
async* execute(context) {
// context.userMessage — 用户消息
// 处理任务并返回结果
yield { state: 'completed', message: { role: 'agent', parts: [{ type: 'text', text: '处理完成' }] } }
},
}
ai.a2a.registerExecutor(executor)
// 在 API 端点中处理请求
const result = await ai.a2a.handleRequest(requestBody)
if (result.streaming) {
// result.stream — AsyncGenerator(流式响应)
}
else {
// result.body — JSON 响应
}调用远端代理
const result = await ai.a2a.callRemoteAgent(
'https://remote-agent.example.com',
'帮我分析这份季度报告',
{ timeout: 30000 },
)
if (result.success) {
// result.data.taskId — 任务 ID
// result.data.taskState — 任务状态
// result.data.responseText — 回复文本
}查询消息记录
const messages = await ai.a2a.listMessages({
contextId: 'ctx-001',
limit: 20,
})
if (messages.success) {
// messages.data.items: A2AMessageRecord[]
// messages.data.total: number
}MCP 服务器 — createMcpServer
独立工厂函数,不需要初始化即可使用。
创建服务器
import { createMcpServer, StreamableHTTPServerTransport } from '@h-ai/ai'
import { z } from 'zod'
const mcp = createMcpServer({ name: 'my-server', version: '1.0.0' })注册工具
mcp.registerTool('search', {
description: '搜索文档',
inputSchema: { query: z.string(), limit: z.number().optional() },
}, async ({ query, limit }) => ({
content: [{ type: 'text', text: `Found ${limit ?? 10} results for: ${query}` }],
}))注册资源
mcp.registerResource('config', 'config://app', {
description: '应用配置',
}, async uri => ({
contents: [{ uri: uri.href, text: JSON.stringify(appConfig) }],
}))注册提示词
mcp.registerPrompt('summarize', {
description: '总结文本',
argsSchema: { text: z.string() },
}, async ({ text }) => ({
messages: [{ role: 'user', content: { type: 'text', text: `请总结:${text}` } }],
}))连接传输层
import { randomUUID } from 'node:crypto'
// Streamable HTTP(推荐)
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
})
await mcp.connect(transport)
// 其他传输层
// import { SSEServerTransport, StdioServerTransport } from '@h-ai/ai'MCP 操作 — ai.mcp
作为 MCP 客户端注册与调用工具、资源和提示词。
// 注册工具
ai.mcp.registerTool(
{ name: 'calc', description: '计算', inputSchema: { expr: { type: 'string' } } },
async ({ expr }) => ({ result: '2' }),
)
// 调用工具
const result = await ai.mcp.callTool('calc', { expr: '1+1' })
// 注册并读取资源
ai.mcp.registerResource(
{ uri: 'data://users', name: 'users', description: '用户列表' },
async () => ({ uri: 'data://users', text: JSON.stringify(users) }),
)
const resource = await ai.mcp.readResource('data://users')
// 注册并获取提示词
ai.mcp.registerPrompt(
{ name: 'greet', arguments: [{ name: 'name', required: true }] },
async ({ name }) => [{ role: 'user', content: { type: 'text', text: `你好 ${name}` } }],
)
const prompt = await ai.mcp.getPrompt('greet', { name: '张三' })前端客户端 — createAIClient
浏览器端轻量客户端,通过 API 代理层与后端 AI 通信。
import { createAIClient } from '@h-ai/ai/client'
import { api } from '@h-ai/api-client'
await api.init({ baseUrl: '/api' })
const client = createAIClient({ api })非流式对话
const response = await client.chat({
messages: [{ role: 'user', content: '你好' }],
})
// response: ChatCompletionResponse(同服务端返回结构)流式对话
for await (const chunk of client.chatStream({ messages }, {
onProgress: (progress) => {
// progress.content — 已累积的内容
// progress.done — 是否结束
// progress.finishReason — 结束原因
},
})) {
const delta = chunk.choices[0]?.delta?.content
if (delta) {
updateUI(delta)
}
}便捷方式
// 发送单条消息,返回回复文本
const reply = await client.sendMessage('你好', '你是一个翻译助手')
// 流式发送,返回完整回复文本
const reply = await client.sendMessageStream('写一首诗', {
onProgress: p => updateUI(p.content),
})记忆与会话查询
// 检索相关记忆
const memories = await client.recallMemories('用户偏好', {
topK: 5,
objectId: 'user-001',
})
// 分页列出记忆
const page = await client.listMemories({
objectId: 'user-001',
offset: 0,
limit: 20,
})
// page.items: MemoryEntry[], page.total: number
// 列出某对象的所有会话
const sessions = await client.listSessions('user-001')
// sessions: SessionInfo[]错误处理
所有需要初始化的子系统方法均返回 HaiResult<T>,通过 result.success 判断成功/失败。
import { HaiAIError } from '@h-ai/ai'
const result = await ai.llm.chat({ messages })
if (!result.success) {
switch (result.error.code) {
case HaiAIError.NOT_INITIALIZED.code:
// 未初始化,请先调用 ai.init()
break
case HaiAIError.RATE_LIMITED.code:
// API 限流,稍后重试
break
case HaiAIError.TIMEOUT.code:
// 请求超时
break
case HaiAIError.CONTEXT_LENGTH_EXCEEDED.code:
// 上下文超长,需压缩消息
break
case HaiAIError.MEMORY_RECALL_FAILED.code:
// 记忆检索失败
break
case HaiAIError.CONTEXT_COMPRESS_FAILED.code:
// 上下文压缩失败
break
default:
// 通用错误
break
}
}设计说明
架构概览
AI 是生命周期单例模块,通过 ai.init() / ai.close() 管理运行时状态。内部由 17 个子模块构成,通过 getter 暴露给调用方。
ai.init(config?, options?)
├─ 配置校验(Zod schema)
├─ 存储 Provider 解析
│ ├─ options.storeProvider → 使用自定义 Provider
│ └─ 未提供 → 检查 reldb + vecdb 已初始化 → 创建默认 DB Provider
├─ createAISubsystems() — 按依赖创建所有子模块
│ ├─ LLM Provider(OpenAI 兼容,多模型场景映射)
│ ├─ MCP(工具/资源/提示词注册表)
│ ├─ Embedding(文本向量化)
│ ├─ Token(Token 估算)
│ ├─ Summary(消息摘要)
│ ├─ Compress(上下文压缩)
│ ├─ Memory(记忆提取/存储/检索)
│ ├─ Rerank(结果重排序)
│ ├─ Retrieval(多源向量检索)
│ ├─ RAG(检索增强问答)
│ ├─ Knowledge(知识库管理)
│ ├─ Reasoning(推理引擎)
│ ├─ Context(有状态上下文管理器)
│ ├─ File(文件解析)
│ └─ A2A(Agent-to-Agent,延迟初始化)
└─ StoreProvider.setupAll()(建表/建连接)初始化前访问任何需要 init 的子模块方法,均返回 NOT_INITIALIZED 错误(基于 NotInitializedKit Proxy)。ai.tools 和 ai.stream 为纯函数,无需初始化。
数据存储分布
下表为默认 DB Provider(reldb + vecdb)的存储映射。自定义
AIStoreProvider可对接其他存储后端。
| 数据类型 | 存储位置 | 表/集合名 |
| ---------- | ---------- | ----------------------------------------- |
| 对话记录 | DB | hai_ai_chat_records |
| 会话元数据 | DB | hai_ai_sessions |
| 检索源配置 | DB | hai_ai_retrieval_sources |
| 记忆条目 | DB + VecDB | hai_ai_memory + hai_ai_memory_vectors |
| 上下文状态 | DB | hai_ai_context |
| 知识库内容 | VecDB | hai_ai_knowledge(可配置) |
多模型场景映射
通过 llm.models 注册多个模型,通过 llm.scenarios 将不同使用场景映射到对应模型:
await ai.init({
llm: {
model: 'gpt-4o-mini', // 默认模型
models: [
{ id: 'fast', model: 'gpt-4o-mini', temperature: 0.3 },
{ id: 'strong', model: 'gpt-4o', maxTokens: 8192 },
{ id: 'embed', model: 'text-embedding-3-small' },
],
scenarios: {
default: 'fast',
chat: 'fast',
reasoning: 'strong',
embedding: 'embed',
summary: 'fast',
extraction: 'fast',
rerank: 'fast',
ocr: 'strong',
},
},
})各子模块自动使用对应场景的模型:
ai.llm.chat():scenarios.chat→scenarios.default→llm.modelai.embedding.embed():scenarios.embedding→llm.modelai.reasoning.run():scenarios.reasoning→llm.modelai.summary.summarize():scenarios.summary→llm.modelai.rerank.rerank():scenarios.rerank→llm.model
Provider 模式
LLM Provider
当前内置 OpenAI 兼容 Provider(ai-llm-provider-openai.ts),支持所有 OpenAI API 兼容服务(OpenAI、Azure OpenAI、Ollama、vLLM 等):
- API Key 解析顺序:模型条目 > 全局配置 > 环境变量(
HAI_AI_LLM_API_KEY/OPENAI_API_KEY) - Base URL 解析顺序:模型条目 > 全局配置 > 环境变量(
HAI_AI_LLM_BASE_URL/OPENAI_BASE_URL)> OpenAI 默认
Store Provider
存储层通过 AIStoreProvider 接口抽象,所有需要持久化的子系统(Memory / Knowledge / Context / Retrieval / A2A 等)统一通过此接口存取数据:
interface AIStoreProvider {
readonly name: string
createRelStore: <T>(name: string, options?: AIRelStoreOptions) => AIRelStore<T>
createVectorStore: (name: string) => AIVectorStore
createKnowledgeStore?: () => KnowledgeStore
setupAll: () => Promise<void>
}- 默认 DB Provider:基于
@h-ai/reldb+@h-ai/vecdb,未传入自定义 Provider 时自动使用 - 自定义 Provider:通过
ai.init(config, { storeProvider })注入,可对接 SaaS API、其他数据库等
上下文管理编排
ai.context.createManager() 创建的 ContextManager 是最上层的编排器,一次 chat() 调用的内部流程:
manager.chat(query)
├─ 记忆注入(memory.injectMemories → 从 VecDB 召回相关记忆追加到 system)
├─ RAG 检索(retrieval.retrieve → 从知识库检索相关片段注入上下文)
├─ 上下文压缩(compress.tryCompress → 超限时自动压缩旧消息)
├─ 推理引擎(reasoning.run → CoT/ReAct/Plan-Execute)或 LLM 直接调用
├─ 工具调用循环(tools 注册表 → 自动执行并续接)
├─ 记忆提取(memory.extract → 从新对话中提取事实/偏好/实体等)
└─ 持久化(save → 消息和摘要写入 DB)典型场景
带工具调用的多轮对话循环
import type { ChatMessage } from '@h-ai/ai'
import { ai } from '@h-ai/ai'
import { z } from 'zod'
// 定义并注册工具
const weatherTool = ai.tools.define({
name: 'get_weather',
description: '获取天气',
parameters: z.object({ city: z.string() }),
handler: async ({ city }) => ({ temp: 22, city, condition: '晴' }),
})
const registry = ai.tools.createRegistry()
registry.register(weatherTool)
const messages: ChatMessage[] = [
{ role: 'user', content: '北京和上海今天天气怎么样?' },
]
// 对话循环
let result = await ai.llm.chat({
messages,
tools: registry.getDefinitions(),
})
while (result.success && result.data.choices[0].finish_reason === 'tool_calls') {
const assistantMsg = result.data.choices[0].message
messages.push(assistantMsg)
const toolResults = await registry.executeAll(assistantMsg.tool_calls!, { parallel: true })
if (toolResults.success) {
messages.push(...toolResults.data)
}
result = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
}
if (result.success) {
const finalAnswer = result.data.choices[0].message.content
}记忆增强的长期对话
import type { ChatMessage } from '@h-ai/ai'
import { ai } from '@h-ai/ai'
await ai.init({
llm: { model: 'gpt-4o-mini', apiKey: process.env.API_KEY },
memory: { maxEntries: 500, embeddingEnabled: true },
compress: { defaultStrategy: 'hybrid', preserveLastN: 6 },
})
const messages: ChatMessage[] = [
{ role: 'system', content: '你是用户的私人助手,请记住用户的偏好。' },
]
async function chat(userInput: string): Promise<string | undefined> {
messages.push({ role: 'user', content: userInput })
// 1. 注入历史记忆
const enriched = await ai.memory.injectMemories(messages, {
topK: 5,
position: 'system',
maxTokens: 500,
})
const enrichedMessages = enriched.success ? enriched.data : messages
// 2. 压缩上下文以适应模型限制
const compressed = await ai.compress.tryCompress(enrichedMessages, {
maxTokens: 4000,
strategy: 'hybrid',
})
const finalMessages = compressed.success ? compressed.data.messages : enrichedMessages
// 3. 调用 LLM
const result = await ai.llm.chat({ messages: finalMessages })
if (!result.success)
return undefined
const reply = result.data.choices[0].message
messages.push(reply)
// 4. 异步提取新记忆(不阻塞响应)
ai.memory.extract(messages.slice(-2), { objectId: 'chat' })
return reply.content ?? undefined
}知识库 + RAG 完整流程
import { ai } from '@h-ai/ai'
// 初始化(默认 DB Provider 需要 vecdb + reldb)
await ai.init({
llm: { model: 'gpt-4o-mini', apiKey: process.env.API_KEY },
embedding: { model: 'text-embedding-3-small' },
knowledge: {
enableEntityExtraction: true,
cleanOptions: { removeHtml: true },
chunkOptions: { mode: 'markdown', maxSize: 1500, overlap: 200 },
},
})
// 1. 初始化知识库
await ai.knowledge.setup()
// 2. 批量导入文档
const documents = [
{ id: 'doc-1', content: '# 产品架构\n...', title: '架构文档' },
{ id: 'doc-2', content: '# 部署指南\n...', title: '部署文档' },
]
for (const doc of documents) {
const result = await ai.knowledge.ingest({
documentId: doc.id,
content: doc.content,
title: doc.title,
enableEntityExtraction: true,
})
}
// 3. 知识问答
const answer = await ai.knowledge.ask('项目的部署流程是什么?', {
topK: 5,
enableEntityBoost: true,
systemPrompt: '基于文档准确回答,标注引用来源。',
})
if (answer.success) {
// answer.data.answer — LLM 回答
// answer.data.citations — 引用列表
}有状态上下文管理器 + 流式输出
import { ai } from '@h-ai/ai'
await ai.init({
llm: { model: 'gpt-4o-mini', apiKey: process.env.API_KEY },
compress: { defaultStrategy: 'hybrid', preserveLastN: 6 },
})
// 创建管理器(使用嵌套 compress 配置)
const managerResult = ai.context.createManager({
compress: { maxTokens: 8000, auto: true },
memory: { enable: true, enableExtract: true },
})
if (!managerResult.success)
return
const manager = managerResult.data
// 方式一:直接使用 chatStream(推荐,自动编排)
for await (const event of manager.chatStream('你好')) {
if (event.type === 'delta')
process.stdout.write(event.text)
if (event.type === 'done')
console.warn('\n完成,模型:', event.model)
}
// 方式二:手动编排
async function streamChat(userInput: string): Promise<string> {
await manager.addMessage({ role: 'user', content: userInput })
const msgs = manager.getMessages()
if (!msgs.success)
return ''
const processor = ai.stream.createProcessor()
for await (const chunk of ai.llm.chatStream({ messages: msgs.data })) {
const delta = processor.process(chunk)
if (delta?.content) {
process.stdout.write(delta.content)
}
}
const assistantMsg = processor.toAssistantMessage()
await manager.addMessage(assistantMsg)
return assistantMsg.content ?? ''
}A2A 代理协作
import type { AgentExecutor } from '@h-ai/ai'
import { ai } from '@h-ai/ai'
await ai.init({
llm: { model: 'gpt-4o-mini', apiKey: process.env.API_KEY },
a2a: {
agentCard: {
name: 'coordinator',
description: '协调代理,分发任务给专业代理',
url: 'https://coordinator.example.com',
skills: [{ id: 'coordinate', name: '任务协调', description: '分析任务并委派给合适的代理' }],
},
},
})
// 注册本地执行器
const executor: AgentExecutor = {
async* execute(context) {
// 委派子任务给远端专业代理
const analysis = await ai.a2a.callRemoteAgent(
'https://analyst.example.com',
context.userMessage,
{ timeout: 60000 },
)
if (analysis.success) {
yield {
state: 'completed',
message: {
role: 'agent',
parts: [{ type: 'text', text: `分析结果:${analysis.data.responseText}` }],
},
}
}
},
}
ai.a2a.registerExecutor(executor)测试
pnpm --filter @h-ai/ai testLicense
Apache-2.0
