@h-ai/cli 0.1.0-alpha.19 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BPO34NRL.js → chunk-3BBXDQ4Y.js} +5 -5
- package/dist/chunk-3BBXDQ4Y.js.map +1 -0
- package/dist/cli-config-templates-IGGO7P45.js +5 -0
- package/dist/{cli-config-templates-JCS7T3NU.js.map → cli-config-templates-IGGO7P45.js.map} +1 -1
- package/dist/index.js +67 -39
- package/dist/index.js.map +1 -1
- package/dist/{src-N4NPS7AG.js → src-QNEJJ3BV.js} +261 -103
- package/dist/src-QNEJJ3BV.js.map +1 -0
- package/package.json +2 -2
- package/templates/apps/admin/messages/en-US.json +5 -0
- package/templates/apps/admin/messages/zh-CN.json +5 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +5 -5
- package/templates/apps/admin/src/routes/+page.svelte.hbs +32 -12
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +37 -20
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +33 -24
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +16 -13
- package/templates/apps/android-app/src/lib/api.ts.hbs +4 -3
- package/templates/apps/android-app/src/lib/capacitor.ts +1 -1
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +11 -10
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +17 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +4 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +14 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +13 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server-app.ts.hbs +23 -3
- package/templates/apps/h5/messages/en-US.json +3 -0
- package/templates/apps/h5/messages/zh-CN.json +3 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +30 -29
- package/templates/apps/h5/src/routes/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +17 -6
- package/templates/apps/website/src/routes/+layout.svelte.hbs +35 -26
- package/templates/apps/website/src/routes/+page.svelte.hbs +28 -10
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +11 -6
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +37 -34
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +27 -40
- package/templates/base/package.json.hbs +26 -21
- package/templates/base/src/app.html.hbs +4 -0
- package/templates/base/src/hooks.server.ts.hbs +17 -7
- package/templates/base/src/lib/server/init.ts.hbs +20 -12
- package/templates/base/static/favicon.png +0 -0
- package/templates/base/tests/scaffold.test.ts.hbs +9 -1
- package/templates/base/vite.config.ts.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +5 -5
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +34 -32
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +46 -48
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +35 -31
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +41 -38
- package/templates/skills/AGENTS.md +11 -1
- package/templates/skills/CLAUDE.md +2 -1
- package/templates/skills/copilot-instructions.md +22 -6
- package/templates/skills/hai-ai/SKILL.md +115 -648
- package/templates/skills/hai-ai/reference.md +197 -0
- package/templates/skills/hai-api-client/SKILL.md +3 -1
- package/templates/skills/hai-app-create/SKILL.md +1 -1
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-deploy/SKILL.md +12 -9
- package/templates/skills/hai-kit/SKILL.md +5 -3
- package/templates/skills/hai-serv/SKILL.md +2 -0
- package/templates/skills/hai-ui/SKILL.md +25 -11
- package/dist/chunk-BPO34NRL.js.map +0 -1
- package/dist/cli-config-templates-JCS7T3NU.js +0 -5
- package/dist/src-N4NPS7AG.js.map +0 -1
|
@@ -3,735 +3,202 @@ name: hai-ai
|
|
|
3
3
|
description: 使用 @h-ai/ai 进行 LLM 调用(OpenAI 兼容)、MCP 服务器创建、工具定义与注册、流式处理、记忆管理(objectId 隔离)、上下文压缩与会话持久化;当需求涉及 AI 对话、工具调用、MCP 协议、流式响应、记忆提取注入、上下文摘要压缩、会话管理或 AI 客户端时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# hai-ai
|
|
6
|
+
# hai-ai — @h-ai/ai 快速指南
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`@h-ai/ai` 统一提供 LLM、工具、MCP、Embedding、Memory、Retrieval/RAG、Knowledge、Context、File、Rerank 与 A2A 能力。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## 运行环境
|
|
13
|
-
|
|
14
|
-
| 能力 | Node.js | 浏览器 |
|
|
15
|
-
|------|---------|--------|
|
|
16
|
-
| `ai.llm` LLM 调用 | ✅ | ❌(通过 API 端点代理) |
|
|
17
|
-
| `ai.mcp` MCP 服务器 | ✅ | ❌ |
|
|
18
|
-
| `ai.tools` 工具定义 | ✅ | ✅(纯函数,无需 init) |
|
|
19
|
-
| `ai.stream` 流处理 | ✅ | ✅(纯函数,无需 init) |
|
|
20
|
-
| `ai.memory` / `ai.conversation` | ✅ | ❌(通过 API 查询) |
|
|
21
|
-
| `ai.knowledge` 知识库 | ✅ | ❌(通过 API 查询) |
|
|
10
|
+
> 详细 API 表、错误码与长示例见同目录 `reference.md`。只有需要完整契约或边界用例时再读取,避免把长参考塞进上下文。
|
|
22
11
|
|
|
23
|
-
|
|
12
|
+
## 使用边界
|
|
24
13
|
|
|
25
|
-
|
|
14
|
+
- `ai.tools`、`ai.stream` 是纯函数能力,无需 `ai.init()`。
|
|
15
|
+
- `ai.llm`、`ai.embedding`、`ai.memory`、`ai.retrieval`、`ai.rag`、`ai.knowledge`、`ai.context`、`ai.a2a` 需要先 `await ai.init(...)`。
|
|
16
|
+
- 默认 DB Provider 需要在 `ai.init()` 前完成 `reldb.init()` 与 `vecdb.init()`;自定义 `AIStoreProvider` 可跳过这两个依赖。
|
|
17
|
+
- 浏览器端不要直接调用 Node-only 能力;通过 `@h-ai/api-client` 或应用自定义 API/SSE endpoint 代理。
|
|
18
|
+
- 公共 API 返回 `HaiResult<T>` 时按 `if (!result.success) return result` 处理;不要用 `try/catch` 包裹正常业务错误。
|
|
26
19
|
|
|
27
|
-
##
|
|
20
|
+
## 初始化与关闭
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
| `@h-ai/datapipe` | 数据管道(知识库文档切分) | 内部使用 | 无需手动初始化 |
|
|
22
|
+
```ts
|
|
23
|
+
import { ai } from '@h-ai/ai'
|
|
24
|
+
import { reldb } from '@h-ai/reldb'
|
|
25
|
+
import { vecdb } from '@h-ai/vecdb'
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
await reldb.init(core.config.get('db'))
|
|
28
|
+
await vecdb.init(core.config.get('vecdb'))
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
const init = await ai.init(core.config.get('ai'))
|
|
31
|
+
if (!init.success) return init
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
// ... 使用 ai.llm / ai.memory / ai.knowledge 等
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- 创建 MCP 服务器(Streamable HTTP/SSE/Stdio)
|
|
44
|
-
- 注册 MCP 资源与提示词
|
|
45
|
-
- 自动提取和管理对话记忆
|
|
46
|
-
- 上下文压缩与 Token 控制
|
|
47
|
-
- 会话持久化与恢复(配合 reldb/vecdb)
|
|
48
|
-
- 前端 AI 客户端集成(记忆/会话查询)
|
|
35
|
+
await ai.close()
|
|
36
|
+
```
|
|
49
37
|
|
|
50
|
-
|
|
38
|
+
自定义存储:
|
|
51
39
|
|
|
52
|
-
|
|
40
|
+
```ts
|
|
41
|
+
await ai.init(core.config.get('ai'), { storeProvider: myProvider })
|
|
42
|
+
await ai.close()
|
|
43
|
+
```
|
|
53
44
|
|
|
54
|
-
|
|
45
|
+
## 配置要点
|
|
55
46
|
|
|
56
47
|
```yaml
|
|
57
|
-
# config/_ai.yml
|
|
58
48
|
llm:
|
|
59
49
|
apiKey: ${HAI_AI_LLM_API_KEY:}
|
|
60
50
|
baseUrl: ${HAI_AI_LLM_BASE_URL:https://api.openai.com/v1}
|
|
61
51
|
model: ${HAI_AI_LLM_MODEL:gpt-4o-mini}
|
|
62
|
-
maxTokens: ${HAI_AI_LLM_MAX_TOKENS:4096}
|
|
63
|
-
temperature: 0.7
|
|
64
52
|
timeout: 60000
|
|
53
|
+
scenarios:
|
|
54
|
+
chat: fast
|
|
55
|
+
reasoning: strong
|
|
56
|
+
embedding: embed
|
|
57
|
+
|
|
58
|
+
embedding:
|
|
59
|
+
dimensions: 1536
|
|
60
|
+
batchSize: 100
|
|
65
61
|
|
|
66
|
-
# 存储配置(可选)
|
|
67
|
-
store:
|
|
68
|
-
mode: memory # memory | persistent(需 reldb + vecdb 已初始化)
|
|
69
|
-
|
|
70
|
-
# 记忆管理(可选)
|
|
71
|
-
memory:
|
|
72
|
-
maxEntries: 1000 # 最大记忆条数
|
|
73
|
-
extractModel: gpt-4o-mini # 提取用模型(默认用 LLM 配置模型)
|
|
74
|
-
apiKey: # 可选,覆盖 LLM apiKey
|
|
75
|
-
baseUrl: # 可选,覆盖 LLM baseUrl
|
|
76
|
-
recencyDecay: 0.95 # 时间衰减系数
|
|
77
|
-
embeddingEnabled: true # 启用向量检索
|
|
78
|
-
defaultTopK: 10 # 默认检索数量
|
|
79
|
-
|
|
80
|
-
# Token 估算(可选)
|
|
81
|
-
token:
|
|
82
|
-
tokenRatio: 0.25 # Token 估算系数(4 字符 ≈ 1 token)
|
|
83
|
-
|
|
84
|
-
# 摘要生成(可选)
|
|
85
|
-
summary:
|
|
86
|
-
systemPrompt: # 自定义摘要系统提示词
|
|
87
|
-
|
|
88
|
-
# 上下文压缩(可选)
|
|
89
|
-
compress:
|
|
90
|
-
defaultStrategy: hybrid # 压缩策略:summary | sliding-window | hybrid
|
|
91
|
-
defaultMaxTokens: 0 # 0 = 取模型 maxTokens 的 80%
|
|
92
|
-
preserveLastN: 4 # 保留最近 N 条消息
|
|
93
|
-
|
|
94
|
-
# 知识库(可选)
|
|
95
62
|
knowledge:
|
|
96
63
|
collection: hai_ai_knowledge
|
|
97
64
|
dimension: 1536
|
|
98
|
-
enableEntityExtraction: true
|
|
99
|
-
cleanOptions:
|
|
100
|
-
removeHtml: true
|
|
101
|
-
normalizeWhitespace: true
|
|
102
65
|
chunkOptions:
|
|
103
|
-
mode: markdown
|
|
66
|
+
mode: markdown
|
|
104
67
|
maxSize: 1500
|
|
105
68
|
overlap: 200
|
|
106
69
|
```
|
|
107
70
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
import { ai } from '@h-ai/ai'
|
|
112
|
-
import { reldb } from '@h-ai/reldb'
|
|
113
|
-
import { vecdb } from '@h-ai/vecdb'
|
|
114
|
-
|
|
115
|
-
// 先初始化依赖
|
|
116
|
-
await reldb.init(core.config.get('db'))
|
|
117
|
-
await vecdb.init(core.config.get('vecdb'))
|
|
118
|
-
|
|
119
|
-
// 再初始化 AI 模块(自动使用已初始化的 reldb 和 vecdb 单例)
|
|
120
|
-
ai.init(core.config.get('ai'))
|
|
121
|
-
// 使用后
|
|
122
|
-
ai.close()
|
|
123
|
-
```
|
|
71
|
+
`ai.config` 是脱敏快照;`apiKey`、`privateKey`、URL 内嵌凭证会被隐藏。
|
|
124
72
|
|
|
125
|
-
|
|
73
|
+
## 常用 API
|
|
126
74
|
|
|
127
|
-
|
|
75
|
+
| 场景 | 首选 API | 备注 |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| 非流式对话 | `ai.llm.chat({ messages })` | OpenAI 兼容 Chat Completion |
|
|
78
|
+
| 流式对话 | `ai.llm.chatStream({ messages })` | 返回 `AsyncIterable` |
|
|
79
|
+
| 工具定义 | `ai.tools.define(...)` | Zod schema 转 JSON Schema |
|
|
80
|
+
| 工具执行 | `registry.executeAll(toolCalls)` | 支持并行执行 |
|
|
81
|
+
| MCP 服务 | `createMcpServer(...)` | 按需连接 HTTP/SSE/Stdio transport |
|
|
82
|
+
| Embedding | `ai.embedding.embedText(text)` | 批量用 `embedBatch` |
|
|
83
|
+
| 记忆 | `ai.memory.extract/recall/injectMemories` | 用 `objectId` 做主体隔离 |
|
|
84
|
+
| Retrieval/RAG | `ai.retrieval.retrieve` / `ai.rag.query` | Retrieval source 先注册或由配置预置 |
|
|
85
|
+
| Knowledge | `ai.knowledge.setup/ingest/ask` | 入库前会调用 datapipe 清洗分块 |
|
|
86
|
+
| Context | `ai.context.createManager` | 编排 LLM + Memory + RAG + 压缩 |
|
|
87
|
+
| A2A | `ai.a2a.registerExecutor/handleRequest` | 延迟初始化 SDK handler |
|
|
128
88
|
|
|
129
|
-
|
|
89
|
+
## LLM + 工具调用
|
|
130
90
|
|
|
131
|
-
|
|
91
|
+
```ts
|
|
92
|
+
const registry = ai.tools.createRegistry()
|
|
93
|
+
registry.register(ai.tools.define({
|
|
94
|
+
name: 'get_weather',
|
|
95
|
+
description: '获取天气',
|
|
96
|
+
parameters: z.object({ city: z.string() }),
|
|
97
|
+
handler: async ({ city }) => ({ city, temperature: 20 }),
|
|
98
|
+
}))
|
|
132
99
|
|
|
133
|
-
|
|
100
|
+
const messages: ChatMessage[] = [{ role: 'user', content: '北京天气?' }]
|
|
101
|
+
let result = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
|
|
134
102
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
| `listModels` | `() => Promise<HaiResult<string[]>>` | 获取可用模型列表 |
|
|
103
|
+
while (result.success && result.data.choices[0]?.finish_reason === 'tool_calls') {
|
|
104
|
+
const assistant = result.data.choices[0].message
|
|
105
|
+
const toolCalls = assistant.tool_calls ?? []
|
|
106
|
+
messages.push(assistant)
|
|
140
107
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
messages: [
|
|
145
|
-
{ role: 'system', content: '你是一个有帮助的助手' },
|
|
146
|
-
{ role: 'user', content: '你好' },
|
|
147
|
-
],
|
|
148
|
-
tools: registry.getDefinitions(), // 可选
|
|
149
|
-
})
|
|
150
|
-
if (result.success) {
|
|
151
|
-
const message = result.data.choices[0].message
|
|
152
|
-
}
|
|
108
|
+
const toolMessages = await registry.executeAll(toolCalls, { parallel: true })
|
|
109
|
+
if (!toolMessages.success) return toolMessages
|
|
110
|
+
messages.push(...toolMessages.data)
|
|
153
111
|
|
|
154
|
-
|
|
155
|
-
for await (const chunk of ai.llm.chatStream({ messages })) {
|
|
156
|
-
const delta = chunk.choices[0]?.delta?.content
|
|
157
|
-
if (delta)
|
|
158
|
-
process.stdout.write(delta)
|
|
112
|
+
result = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
|
|
159
113
|
}
|
|
160
114
|
```
|
|
161
115
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
import { ai } from '@h-ai/ai'
|
|
166
|
-
import { z } from 'zod'
|
|
167
|
-
|
|
168
|
-
// 定义工具(Zod Schema 自动转 JSON Schema)
|
|
169
|
-
const weatherTool = ai.tools.define({
|
|
170
|
-
name: 'get_weather',
|
|
171
|
-
description: '获取天气信息',
|
|
172
|
-
parameters: z.object({
|
|
173
|
-
city: z.string().describe('城市名称'),
|
|
174
|
-
unit: z.enum(['celsius', 'fahrenheit']).optional(),
|
|
175
|
-
}),
|
|
176
|
-
handler: async ({ city, unit }) => {
|
|
177
|
-
return { temperature: 20, city, unit: unit ?? 'celsius' }
|
|
178
|
-
},
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// 注册表管理
|
|
182
|
-
const registry = ai.tools.createRegistry()
|
|
183
|
-
registry.register(weatherTool)
|
|
184
|
-
|
|
185
|
-
// 获取 OpenAI 工具定义(传递给 LLM)
|
|
186
|
-
const definitions = registry.getDefinitions()
|
|
187
|
-
|
|
188
|
-
// 执行 LLM 返回的工具调用
|
|
189
|
-
const toolMessage = await registry.execute(toolCall)
|
|
190
|
-
const toolMessages = await registry.executeAll(toolCalls, { parallel: true })
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
> `ai.tools.define` 和 `ai.tools.createRegistry` 也可通过 `defineTool` / `createToolRegistry` 直接导入,两者等价。
|
|
194
|
-
|
|
195
|
-
### 流处理 — `ai.stream`
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
import { ai } from '@h-ai/ai'
|
|
116
|
+
## 流式处理
|
|
199
117
|
|
|
118
|
+
```ts
|
|
200
119
|
const processor = ai.stream.createProcessor()
|
|
201
120
|
for await (const chunk of ai.llm.chatStream({ messages })) {
|
|
202
121
|
const delta = processor.process(chunk)
|
|
203
|
-
if (delta?.content)
|
|
122
|
+
if (delta?.content) process.stdout.write(delta.content)
|
|
204
123
|
}
|
|
205
|
-
|
|
206
|
-
const result = processor.getResult()
|
|
207
|
-
// { content: string, toolCalls: ToolCall[], finishReason: string | null }
|
|
208
124
|
const message = processor.toAssistantMessage()
|
|
209
|
-
// { role: 'assistant', content: '...', tool_calls?: [...] }
|
|
210
|
-
|
|
211
|
-
// 快捷收集全部内容
|
|
212
|
-
const collected = await ai.stream.collect(stream)
|
|
213
125
|
```
|
|
214
126
|
|
|
215
|
-
|
|
127
|
+
## MCP 服务器
|
|
216
128
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
129
|
+
```ts
|
|
220
130
|
import { createMcpServer, StreamableHTTPServerTransport } from '@h-ai/ai'
|
|
221
|
-
import { z } from 'zod'
|
|
222
|
-
|
|
223
|
-
const mcp = createMcpServer({ name: 'my-server', version: '1.0.0' })
|
|
224
131
|
|
|
225
|
-
|
|
226
|
-
|
|
132
|
+
const server = createMcpServer({ name: 'my-server', version: '1.0.0' })
|
|
133
|
+
server.registerTool('search', {
|
|
227
134
|
description: '搜索',
|
|
228
135
|
inputSchema: { query: z.string() },
|
|
229
136
|
}, async ({ query }) => ({
|
|
230
137
|
content: [{ type: 'text', text: `Results for ${query}` }],
|
|
231
138
|
}))
|
|
232
139
|
|
|
233
|
-
|
|
234
|
-
mcp.registerResource('config', 'config://app', {
|
|
235
|
-
description: '应用配置',
|
|
236
|
-
}, async uri => ({
|
|
237
|
-
contents: [{ uri: uri.href, text: '{}' }],
|
|
238
|
-
}))
|
|
239
|
-
|
|
240
|
-
// 注册提示词
|
|
241
|
-
mcp.registerPrompt('summarize', {
|
|
242
|
-
description: '总结文本',
|
|
243
|
-
argsSchema: { text: z.string() },
|
|
244
|
-
}, async ({ text }) => ({
|
|
245
|
-
messages: [{ role: 'user', content: { type: 'text', text } }],
|
|
246
|
-
}))
|
|
247
|
-
|
|
248
|
-
// 连接传输层
|
|
249
|
-
const transport = new StreamableHTTPServerTransport({
|
|
250
|
-
sessionIdGenerator: () => randomUUID(),
|
|
251
|
-
})
|
|
252
|
-
await mcp.connect(transport)
|
|
140
|
+
await server.connect(new StreamableHTTPServerTransport({ sessionIdGenerator: crypto.randomUUID }))
|
|
253
141
|
```
|
|
254
142
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
### MCP 操作 — `ai.mcp`
|
|
143
|
+
## 记忆、RAG 与知识库
|
|
258
144
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
| `callTool` | `(name, input) => Promise<HaiResult>` | 调用工具 |
|
|
263
|
-
| `registerResource` | `(def, reader) => void` | 注册资源 |
|
|
264
|
-
| `readResource` | `(uri) => Promise<HaiResult>` | 读取资源 |
|
|
265
|
-
| `registerPrompt` | `(def, generator) => void` | 注册提示词 |
|
|
266
|
-
| `getPrompt` | `(name, args) => Promise<HaiResult>` | 获取提示词 |
|
|
145
|
+
```ts
|
|
146
|
+
const enriched = await ai.memory.injectMemories(messages, { objectId: 'user-001', topK: 5 })
|
|
147
|
+
if (!enriched.success) return enriched
|
|
267
148
|
|
|
268
|
-
|
|
149
|
+
const compressed = await ai.compress.tryCompress(enriched.data, { strategy: 'hybrid', maxTokens: 4000 })
|
|
150
|
+
if (!compressed.success) return compressed
|
|
269
151
|
|
|
270
|
-
|
|
271
|
-
import { apiClient } from '@h-ai/api-client'
|
|
272
|
-
|
|
273
|
-
await apiClient.init({ baseUrl: '/api/v1', auth: {} })
|
|
274
|
-
|
|
275
|
-
// 非流式
|
|
276
|
-
const response = await apiClient.ai.chats.createCompletion({ messages })
|
|
277
|
-
|
|
278
|
-
// 便捷方法
|
|
279
|
-
const reply = await apiClient.ai.chats.sendMessage({ message: '你好', systemPrompt: '系统提示' })
|
|
280
|
-
|
|
281
|
-
// 记忆与会话查询
|
|
282
|
-
const memories = await apiClient.ai.memories.recall({ query: '用户偏好', topK: 5, objectId: 'user-001' })
|
|
283
|
-
const page = await apiClient.ai.memories.list({ objectId: 'user-001', limit: 20 })
|
|
284
|
-
const sessions = await apiClient.ai.sessions.list({ objectId: 'user-001' })
|
|
152
|
+
const answer = await ai.rag.query('核心架构是什么?', { sources: ['docs'], topK: 5 })
|
|
285
153
|
```
|
|
286
154
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
| 方法 | 签名 | 说明 |
|
|
290
|
-
| ----------------- | ------------------------------------------------------------- | ---------------------------------- |
|
|
291
|
-
| `extract` | `(messages, options?) => Promise<HaiResult<MemoryEntry[]>>` | 从对话中自动提取记忆(LLM 驱动) |
|
|
292
|
-
| `add` | `(entry) => Promise<HaiResult<MemoryEntry>>` | 手动添加一条记忆 |
|
|
293
|
-
| `get` | `(memoryId) => Promise<HaiResult<MemoryEntry>>` | 获取指定记忆 |
|
|
294
|
-
| `recall` | `(query, options?) => Promise<HaiResult<MemoryEntry[]>>` | 按查询检索最相关的记忆 |
|
|
295
|
-
| `injectMemories` | `(messages, options?) => Promise<HaiResult<ChatMessage[]>>` | 将相关记忆自动注入消息列表 |
|
|
296
|
-
| `remove` | `(memoryId) => Promise<HaiResult<void>>` | 删除指定记忆 |
|
|
297
|
-
| `list` | `(options?) => Promise<HaiResult<MemoryEntry[]>>` | 列出记忆(支持类型/objectId 过滤) |
|
|
298
|
-
| `listPage` | `(options?) => Promise<HaiResult<StorePage<MemoryEntry>>>` | 分页列出记忆 |
|
|
299
|
-
| `clear` | `(options?) => Promise<HaiResult<void>>` | 清空记忆(支持按类型/objectId 清空)|
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
// 从对话中自动提取记忆
|
|
303
|
-
const extracted = await ai.memory.extract(messages, { objectId: 'user-001' })
|
|
304
|
-
if (extracted.success) {
|
|
305
|
-
// extracted.data: MemoryEntry[](已自动存储)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// 手动添加记忆
|
|
309
|
-
await ai.memory.add({
|
|
310
|
-
content: '用户偏好使用中文',
|
|
311
|
-
type: 'preference', // fact | preference | event | entity | instruction
|
|
312
|
-
importance: 0.8,
|
|
313
|
-
objectId: 'user-001', // 可选,不传为全局
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
// 检索相关记忆
|
|
317
|
-
const memories = await ai.memory.recall('语言偏好', { topK: 5, objectId: 'user-001' })
|
|
318
|
-
|
|
319
|
-
// 将记忆注入消息列表(自动检索 + 格式化 + 插入 system prompt)
|
|
320
|
-
const enriched = await ai.memory.injectMemories(messages, { topK: 5, position: 'system' })
|
|
321
|
-
if (enriched.success) {
|
|
322
|
-
const response = await ai.llm.chat({ messages: enriched.data })
|
|
323
|
-
}
|
|
324
|
-
```
|
|
155
|
+
知识库流程:
|
|
325
156
|
|
|
326
|
-
|
|
157
|
+
```ts
|
|
158
|
+
const setup = await ai.knowledge.setup()
|
|
159
|
+
if (!setup.success) return setup
|
|
327
160
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
// 估算消息列表 Token 数
|
|
335
|
-
const tokens = ai.token.estimateMessages(messages)
|
|
336
|
-
if (tokens.success) {
|
|
337
|
-
// tokens.data — 估算的 Token 数
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// 估算纯文本 Token 数
|
|
341
|
-
const count = ai.token.estimateText('Hello world')
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### 消息摘要 — `ai.summary`
|
|
345
|
-
|
|
346
|
-
| 方法 | 签名 | 说明 |
|
|
347
|
-
| ----------- | ----------------------------------------------------------------- | ----------------------------- |
|
|
348
|
-
| `summarize` | `(messages, options?) => Promise<HaiResult<SummaryResult>>` | 对消息生成摘要(支持增量) |
|
|
349
|
-
| `generate` | `(text, options?) => Promise<HaiResult<SummaryResult>>` | 对纯文本生成摘要 |
|
|
350
|
-
|
|
351
|
-
### 上下文压缩 — `ai.compress`
|
|
352
|
-
|
|
353
|
-
| 方法 | 签名 | 说明 |
|
|
354
|
-
| ------------- | ----------------------------------------------------------------- | ------------------------------------- |
|
|
355
|
-
| `tryCompress` | `(messages, options?) => Promise<HaiResult<CompressResult>>` | 压缩消息列表至指定 Token 预算 |
|
|
356
|
-
|
|
357
|
-
```typescript
|
|
358
|
-
// 压缩超长对话
|
|
359
|
-
const result = await ai.compress.tryCompress(messages, {
|
|
360
|
-
strategy: 'hybrid', // summary | sliding-window | hybrid
|
|
361
|
-
maxTokens: 4000,
|
|
362
|
-
preserveLastN: 4,
|
|
161
|
+
const ingest = await ai.knowledge.ingest({
|
|
162
|
+
documentId: 'doc-001',
|
|
163
|
+
content: markdownText,
|
|
164
|
+
title: '产品手册',
|
|
165
|
+
enableEntityExtraction: true,
|
|
363
166
|
})
|
|
364
|
-
if (
|
|
365
|
-
// result.data.messages — 压缩后的消息列表
|
|
366
|
-
// result.data.originalTokens / compressedTokens — 压缩前后 Token 数
|
|
367
|
-
// result.data.summary — 生成的摘要文本
|
|
368
|
-
}
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### 上下文管理 — `ai.context`
|
|
372
|
-
|
|
373
|
-
| 方法 | 签名 | 说明 |
|
|
374
|
-
| ---------------- | ----------------------------------------------------------------- | ------------------------------------------------------- |
|
|
375
|
-
| `createManager` | `(options?) => HaiResult<ContextManager>` | 创建有状态上下文管理器(压缩 + LLM/Memory/RAG 编排) |
|
|
376
|
-
| `restoreManager` | `(scope, options?) => Promise<HaiResult<ContextManager>>` | 从持久化存储恢复上下文管理器 |
|
|
377
|
-
| `listSessions` | `(objectId) => Promise<HaiResult<SessionInfo[]>>` | 列出指定对象的所有会话 |
|
|
378
|
-
| `renameSession` | `(sessionId, title) => Promise<HaiResult<void>>` | 重命名会话 |
|
|
379
|
-
| `removeSession` | `(sessionId) => Promise<HaiResult<void>>` | 删除会话 |
|
|
380
|
-
|
|
381
|
-
**ContextManager 方法**:
|
|
382
|
-
- `addMessage(msg)` — 追加消息(自动压缩)
|
|
383
|
-
- `getMessages()` / `getTokenUsage()` / `getSummaries()` — 查询状态
|
|
384
|
-
- `chat(message, options?)` — 发送消息并获取回复(编排 LLM + Memory + RAG + Reasoning)
|
|
385
|
-
- `chatStream(message, options?)` — 流式对话(产出 delta*/done 事件)
|
|
386
|
-
- `save()` — 持久化 / `reset()` — 重置
|
|
387
|
-
|
|
388
|
-
```typescript
|
|
389
|
-
// 创建管理器并直接对话(推荐)
|
|
390
|
-
const managerResult = ai.context.createManager({
|
|
391
|
-
scope: { objectId: 'user-001', sessionId: 'sess-001' },
|
|
392
|
-
systemPrompt: '你是一个友好的助手。',
|
|
393
|
-
compress: { maxTokens: 8000, strategy: 'hybrid', auto: true },
|
|
394
|
-
memory: { enable: true, enableExtract: true },
|
|
395
|
-
})
|
|
396
|
-
if (managerResult.success) {
|
|
397
|
-
const manager = managerResult.data
|
|
398
|
-
const result = await manager.chat('你好')
|
|
399
|
-
// result.data.reply / result.data.model / result.data.usage
|
|
400
|
-
await manager.save()
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// 流式对话
|
|
404
|
-
for await (const event of manager.chatStream('讲个故事')) {
|
|
405
|
-
if (event.type === 'delta') process.stdout.write(event.text)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// 恢复已有会话
|
|
409
|
-
const restored = await ai.context.restoreManager(
|
|
410
|
-
{ objectId: 'user-001', sessionId: 'sess-001' },
|
|
411
|
-
{ compress: { maxTokens: 8000 }, memory: { enable: true } },
|
|
412
|
-
)
|
|
167
|
+
if (!ingest.success) return ingest
|
|
413
168
|
|
|
414
|
-
|
|
415
|
-
const sessions = await ai.context.listSessions('user-001')
|
|
169
|
+
const answer = await ai.knowledge.ask('核心架构是什么?')
|
|
416
170
|
```
|
|
417
171
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
## 错误码 — `HaiAIError`
|
|
421
|
-
|
|
422
|
-
| 错误码 | code | 说明 |
|
|
423
|
-
| --------------------------------------------------- | -------------- | -------------------- |
|
|
424
|
-
| `HaiAIError.INTERNAL_ERROR` | `hai:ai:000` | 内部错误 |
|
|
425
|
-
| `HaiAIError.NOT_INITIALIZED` | `hai:ai:010` | 服务未初始化 |
|
|
426
|
-
| `HaiAIError.CONFIGURATION_ERROR` | `hai:ai:011` | 配置错误 |
|
|
427
|
-
| `HaiAIError.INIT_IN_PROGRESS` | `hai:ai:012` | 初始化进行中 |
|
|
428
|
-
| `HaiAIError.RERANK_API_ERROR` | `hai:ai:020` | Rerank API 调用错误 |
|
|
429
|
-
| `HaiAIError.RERANK_INVALID_REQUEST` | `hai:ai:021` | Rerank 请求参数无效 |
|
|
430
|
-
| `HaiAIError.FILE_PARSE_FAILED` | `hai:ai:030` | 文件解析失败 |
|
|
431
|
-
| `HaiAIError.FILE_UNSUPPORTED_FORMAT` | `hai:ai:031` | 不支持的文件格式 |
|
|
432
|
-
| `HaiAIError.FILE_OCR_FAILED` | `hai:ai:032` | OCR 识别失败 |
|
|
433
|
-
| `HaiAIError.FILE_INVALID_CONTENT` | `hai:ai:033` | 文件内容无效 |
|
|
434
|
-
| `HaiAIError.API_ERROR` | `hai:ai:100` | API 调用错误 |
|
|
435
|
-
| `HaiAIError.INVALID_REQUEST` | `hai:ai:101` | 无效请求 |
|
|
436
|
-
| `HaiAIError.RATE_LIMITED` | `hai:ai:102` | 速率限制 |
|
|
437
|
-
| `HaiAIError.TIMEOUT` | `hai:ai:103` | 请求超时 |
|
|
438
|
-
| `HaiAIError.MODEL_NOT_FOUND` | `hai:ai:104` | 模型未找到 |
|
|
439
|
-
| `HaiAIError.CONTEXT_LENGTH_EXCEEDED` | `hai:ai:105` | 上下文长度超限 |
|
|
440
|
-
| `HaiAIError.LLM_RECORD_FAILED` | `hai:ai:106` | 对话记录保存失败 |
|
|
441
|
-
| `HaiAIError.LLM_HISTORY_FAILED` | `hai:ai:107` | 对话历史查询失败 |
|
|
442
|
-
| `HaiAIError.MCP_CONNECTION_ERROR` | `hai:ai:200` | MCP 连接错误 |
|
|
443
|
-
| `HaiAIError.MCP_PROTOCOL_ERROR` | `hai:ai:201` | MCP 协议错误 |
|
|
444
|
-
| `HaiAIError.MCP_TOOL_ERROR` | `hai:ai:202` | MCP 工具错误 |
|
|
445
|
-
| `HaiAIError.MCP_RESOURCE_ERROR` | `hai:ai:203` | MCP 资源错误 |
|
|
446
|
-
| `HaiAIError.MCP_SERVER_ERROR` | `hai:ai:204` | MCP 服务器错误 |
|
|
447
|
-
| `HaiAIError.EMBEDDING_API_ERROR` | `hai:ai:300` | Embedding 调用错误 |
|
|
448
|
-
| `HaiAIError.EMBEDDING_MODEL_NOT_FOUND` | `hai:ai:301` | Embedding 模型未找到 |
|
|
449
|
-
| `HaiAIError.EMBEDDING_INPUT_TOO_LONG` | `hai:ai:302` | Embedding 输入过长 |
|
|
450
|
-
| `HaiAIError.TOOL_NOT_FOUND` | `hai:ai:400` | 工具未找到 |
|
|
451
|
-
| `HaiAIError.TOOL_VALIDATION_FAILED` | `hai:ai:401` | 工具验证失败 |
|
|
452
|
-
| `HaiAIError.TOOL_EXECUTION_FAILED` | `hai:ai:402` | 工具执行失败 |
|
|
453
|
-
| `HaiAIError.TOOL_TIMEOUT` | `hai:ai:403` | 工具执行超时 |
|
|
454
|
-
| `HaiAIError.REASONING_FAILED` | `hai:ai:500` | 推理执行失败 |
|
|
455
|
-
| `HaiAIError.REASONING_MAX_ROUNDS` | `hai:ai:501` | 推理轮次超限 |
|
|
456
|
-
| `HaiAIError.REASONING_STRATEGY_NOT_FOUND` | `hai:ai:502` | 推理策略未找到 |
|
|
457
|
-
| `HaiAIError.RETRIEVAL_FAILED` | `hai:ai:600` | 检索执行失败 |
|
|
458
|
-
| `HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND` | `hai:ai:601` | 检索源未配置 |
|
|
459
|
-
| `HaiAIError.RAG_FAILED` | `hai:ai:700` | RAG 执行失败 |
|
|
460
|
-
| `HaiAIError.RAG_CONTEXT_BUILD_FAILED` | `hai:ai:701` | RAG 上下文构建失败 |
|
|
461
|
-
| `HaiAIError.KNOWLEDGE_SETUP_FAILED` | `hai:ai:800` | 知识库初始化失败 |
|
|
462
|
-
| `HaiAIError.KNOWLEDGE_INGEST_FAILED` | `hai:ai:801` | 知识入库失败 |
|
|
463
|
-
| `HaiAIError.KNOWLEDGE_RETRIEVE_FAILED` | `hai:ai:802` | 知识检索失败 |
|
|
464
|
-
| `HaiAIError.KNOWLEDGE_ENTITY_EXTRACT_FAILED` | `hai:ai:803` | 实体提取失败 |
|
|
465
|
-
| `HaiAIError.KNOWLEDGE_NOT_SETUP` | `hai:ai:804` | 知识库未初始化 |
|
|
466
|
-
| `HaiAIError.KNOWLEDGE_COLLECTION_NOT_FOUND` | `hai:ai:805` | 集合不存在 |
|
|
467
|
-
| `HaiAIError.MEMORY_EXTRACT_FAILED` | `hai:ai:900` | 记忆提取失败 |
|
|
468
|
-
| `HaiAIError.MEMORY_STORE_FAILED` | `hai:ai:901` | 记忆存储失败 |
|
|
469
|
-
| `HaiAIError.MEMORY_RECALL_FAILED` | `hai:ai:902` | 记忆召回失败 |
|
|
470
|
-
| `HaiAIError.MEMORY_NOT_FOUND` | `hai:ai:903` | 记忆条目不存在 |
|
|
471
|
-
| `HaiAIError.MEMORY_ENRICH_FAILED` | `hai:ai:904` | 记忆注入失败 |
|
|
472
|
-
| `HaiAIError.CONTEXT_COMPRESS_FAILED` | `hai:ai:950` | 上下文压缩失败 |
|
|
473
|
-
| `HaiAIError.CONTEXT_SUMMARIZE_FAILED` | `hai:ai:951` | 上下文摘要失败 |
|
|
474
|
-
| `HaiAIError.CONTEXT_TOKEN_ESTIMATE_FAILED` | `hai:ai:952` | Token 估算失败 |
|
|
475
|
-
| `HaiAIError.CONTEXT_BUDGET_EXCEEDED` | `hai:ai:953` | Token 预算超限 |
|
|
476
|
-
| `HaiAIError.STORE_FAILED` | `hai:ai:960` | 存储操作失败 |
|
|
477
|
-
| `HaiAIError.STORE_NOT_AVAILABLE` | `hai:ai:961` | 存储后端不可用 |
|
|
478
|
-
| `HaiAIError.SESSION_NOT_FOUND` | `hai:ai:970` | 会话不存在 |
|
|
479
|
-
| `HaiAIError.SESSION_FAILED` | `hai:ai:971` | 会话操作失败 |
|
|
480
|
-
| `HaiAIError.A2A_NOT_CONFIGURED` | `hai:ai:980` | A2A 服务未配置 |
|
|
481
|
-
| `HaiAIError.A2A_HANDLE_FAILED` | `hai:ai:981` | A2A 请求处理失败 |
|
|
482
|
-
| `HaiAIError.A2A_REMOTE_CALL_FAILED` | `hai:ai:982` | A2A 远端调用失败 |
|
|
483
|
-
| `HaiAIError.A2A_AUTH_FAILED` | `hai:ai:983` | A2A 认证失败 |
|
|
484
|
-
| `HaiAIError.A2A_LIST_MESSAGES_FAILED` | `hai:ai:984` | A2A 消息查询失败 |
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
## 常见模式
|
|
489
|
-
|
|
490
|
-
### 带工具调用的对话循环
|
|
491
|
-
|
|
492
|
-
```typescript
|
|
493
|
-
const registry = ai.tools.createRegistry()
|
|
494
|
-
registry.register(weatherTool)
|
|
495
|
-
|
|
496
|
-
const messages: ChatMessage[] = [
|
|
497
|
-
{ role: 'user', content: '北京天气怎么样?' },
|
|
498
|
-
]
|
|
499
|
-
|
|
500
|
-
let result = await ai.llm.chat({
|
|
501
|
-
messages,
|
|
502
|
-
tools: registry.getDefinitions(),
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
while (result.success && result.data.choices[0].finish_reason === 'tool_calls') {
|
|
506
|
-
const toolCalls = result.data.choices[0].message.tool_calls!
|
|
507
|
-
messages.push(result.data.choices[0].message)
|
|
508
|
-
|
|
509
|
-
const toolMessages = await registry.executeAll(toolCalls)
|
|
510
|
-
if (toolMessages.success)
|
|
511
|
-
messages.push(...toolMessages.data)
|
|
512
|
-
|
|
513
|
-
result = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
|
|
514
|
-
}
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### 知识库完整流程(文档入库 → 问答)
|
|
518
|
-
|
|
519
|
-
```typescript
|
|
520
|
-
import { ai } from '@h-ai/ai'
|
|
521
|
-
|
|
522
|
-
// 初始化
|
|
523
|
-
await ai.knowledge.setup()
|
|
524
|
-
|
|
525
|
-
// 导入多篇文档
|
|
526
|
-
for (const doc of documents) {
|
|
527
|
-
const result = await ai.knowledge.ingest({
|
|
528
|
-
documentId: doc.id,
|
|
529
|
-
content: doc.content,
|
|
530
|
-
title: doc.title,
|
|
531
|
-
enableEntityExtraction: true,
|
|
532
|
-
})
|
|
533
|
-
if (!result.success) {
|
|
534
|
-
// 按错误码处理
|
|
535
|
-
switch (result.error.code) {
|
|
536
|
-
case HaiAIError.KNOWLEDGE_INGEST_FAILED:
|
|
537
|
-
// 入库失败
|
|
538
|
-
break
|
|
539
|
-
case HaiAIError.EMBEDDING_API_ERROR:
|
|
540
|
-
// 嵌入调用失败
|
|
541
|
-
break
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// 问答
|
|
547
|
-
const answer = await ai.knowledge.ask('项目核心架构是什么?')
|
|
548
|
-
if (answer.success) {
|
|
549
|
-
// answer.data.answer — LLM 生成的回答
|
|
550
|
-
// answer.data.citations — 信源引用列表
|
|
551
|
-
}
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
### SvelteKit API 端点
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
// src/routes/api/ai/+server.ts
|
|
558
|
-
export async function POST(event) {
|
|
559
|
-
const { valid, data } = await kit.validate.form(event.request, ChatSchema)
|
|
560
|
-
if (!valid)
|
|
561
|
-
return kit.response.badRequest('Invalid request')
|
|
562
|
-
|
|
563
|
-
const result = await ai.llm.chat({ messages: data!.messages })
|
|
564
|
-
if (!result.success)
|
|
565
|
-
return kit.response.error(result.error.code, result.error.message)
|
|
566
|
-
|
|
567
|
-
return kit.response.ok(result.data)
|
|
568
|
-
}
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
### 记忆增强的多轮对话
|
|
572
|
-
|
|
573
|
-
```typescript
|
|
574
|
-
import { ai } from '@h-ai/ai'
|
|
575
|
-
|
|
576
|
-
const messages: ChatMessage[] = [
|
|
577
|
-
{ role: 'system', content: '你是一个有帮助的助手' },
|
|
578
|
-
]
|
|
579
|
-
|
|
580
|
-
async function chat(userInput: string) {
|
|
581
|
-
messages.push({ role: 'user', content: userInput })
|
|
582
|
-
|
|
583
|
-
// 1. 注入相关记忆到消息列表
|
|
584
|
-
const enriched = await ai.memory.injectMemories(messages, {
|
|
585
|
-
topK: 5,
|
|
586
|
-
position: 'system',
|
|
587
|
-
})
|
|
588
|
-
if (!enriched.success) return
|
|
589
|
-
|
|
590
|
-
// 2. 压缩上下文以适应 Token 限制
|
|
591
|
-
const compressed = await ai.compress.tryCompress(enriched.data, {
|
|
592
|
-
maxTokens: 4000,
|
|
593
|
-
strategy: 'hybrid',
|
|
594
|
-
})
|
|
595
|
-
if (!compressed.success) return
|
|
596
|
-
|
|
597
|
-
// 3. 调用 LLM
|
|
598
|
-
const result = await ai.llm.chat({ messages: compressed.data.messages })
|
|
599
|
-
if (!result.success) return
|
|
600
|
-
|
|
601
|
-
const reply = result.data.choices[0].message
|
|
602
|
-
messages.push(reply)
|
|
603
|
-
|
|
604
|
-
// 4. 从新对话中提取记忆(异步,不阻塞响应)
|
|
605
|
-
ai.memory.extract(messages.slice(-2))
|
|
606
|
-
|
|
607
|
-
return reply.content
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
### 有状态上下文管理器
|
|
172
|
+
## Context 管理器
|
|
612
173
|
|
|
613
|
-
```
|
|
614
|
-
import { ai } from '@h-ai/ai'
|
|
615
|
-
|
|
616
|
-
// 创建管理器(嵌套配置 + 对话编排)
|
|
174
|
+
```ts
|
|
617
175
|
const managerResult = ai.context.createManager({
|
|
618
176
|
scope: { objectId: 'user-001', sessionId: 'sess-001' },
|
|
619
177
|
systemPrompt: '你是一个友好的助手。',
|
|
620
|
-
compress: {
|
|
178
|
+
compress: { auto: true, strategy: 'hybrid', maxTokens: 8000 },
|
|
621
179
|
memory: { enable: true, enableExtract: true },
|
|
622
180
|
})
|
|
623
|
-
if (!managerResult.success)
|
|
624
|
-
const manager = managerResult.data
|
|
625
|
-
|
|
626
|
-
// 方式一:直接对话(推荐,自动编排 LLM + Memory + 压缩)
|
|
627
|
-
const result = await manager.chat('你好')
|
|
628
|
-
if (result.success) {
|
|
629
|
-
// result.data.reply / result.data.model / result.data.usage
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// 流式对话
|
|
633
|
-
for await (const event of manager.chatStream('讲个故事')) {
|
|
634
|
-
if (event.type === 'delta') process.stdout.write(event.text)
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// 方式二:手动管理消息
|
|
638
|
-
async function manualChat(userInput: string) {
|
|
639
|
-
await manager.addMessage({ role: 'user', content: userInput })
|
|
640
|
-
const msgs = manager.getMessages()
|
|
641
|
-
if (!msgs.success) return
|
|
642
|
-
const chatResult = await ai.llm.chat({ messages: msgs.data })
|
|
643
|
-
if (!chatResult.success) return
|
|
644
|
-
await manager.addMessage(chatResult.data.choices[0].message)
|
|
645
|
-
await manager.save()
|
|
646
|
-
return chatResult.data.choices[0].message.content
|
|
647
|
-
}
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
---
|
|
651
|
-
|
|
652
|
-
## 浏览器端使用
|
|
653
|
-
|
|
654
|
-
### typed 对话调用
|
|
655
|
-
|
|
656
|
-
浏览器 / App 通过 `@h-ai/api-client` 调用由 `@h-ai/api-contract` 定义、`@h-ai/serv` 挂载的 AI contract:
|
|
657
|
-
|
|
658
|
-
```typescript
|
|
659
|
-
import { apiClient } from '@h-ai/api-client'
|
|
660
|
-
|
|
661
|
-
const result = await apiClient.ai.chats.createCompletion({
|
|
662
|
-
messages: [{ role: 'user', content: '你好' }],
|
|
663
|
-
})
|
|
181
|
+
if (!managerResult.success) return managerResult
|
|
664
182
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
183
|
+
const reply = await managerResult.data.chat('你好')
|
|
184
|
+
await managerResult.data.save()
|
|
668
185
|
```
|
|
669
186
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
标准 `api-client` 不再提供旧的通用流式方法。若产品需要 SSE 流式输出,请在应用服务端显式定义流式 endpoint(例如 SvelteKit `+server.ts` 或 `@h-ai/serv` 自定义 route),浏览器端再用 `fetch` / `apiFetch` 消费。
|
|
187
|
+
## SvelteKit API 端点模式
|
|
673
188
|
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
method: 'POST',
|
|
681
|
-
headers: { 'Content-Type': 'application/json' },
|
|
682
|
-
body: JSON.stringify({ conversationId: 'conv-1', content: '你好' }),
|
|
683
|
-
signal: controller.signal,
|
|
189
|
+
```ts
|
|
190
|
+
export const POST = kit.handler(async ({ request }) => {
|
|
191
|
+
const data = await kit.validate.body(request, ChatSchema)
|
|
192
|
+
const result = await ai.llm.chat({ messages: data.messages })
|
|
193
|
+
if (!result.success) return kit.response.error(result.error.code, result.error.message)
|
|
194
|
+
return kit.response.ok(result.data)
|
|
684
195
|
})
|
|
685
|
-
|
|
686
|
-
const reader = res.body!.getReader()
|
|
687
|
-
const decoder = new TextDecoder()
|
|
688
|
-
while (true) {
|
|
689
|
-
const { done, value } = await reader.read()
|
|
690
|
-
if (done)
|
|
691
|
-
break
|
|
692
|
-
|
|
693
|
-
appendToUI(decoder.decode(value))
|
|
694
|
-
}
|
|
695
196
|
```
|
|
696
197
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
```typescript
|
|
700
|
-
const res = await fetch(`${apiBase}/ai/chat-stream`, {
|
|
701
|
-
method: 'POST',
|
|
702
|
-
headers: { 'Content-Type': 'application/json' },
|
|
703
|
-
body: JSON.stringify({ conversationId, content: userInput }),
|
|
704
|
-
signal: controller.signal,
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
// 手动消费 SSE 流
|
|
708
|
-
const reader = res.body!.getReader()
|
|
709
|
-
const decoder = new TextDecoder()
|
|
710
|
-
while (true) {
|
|
711
|
-
const { done, value } = await reader.read()
|
|
712
|
-
if (done) break
|
|
713
|
-
appendToUI(decoder.decode(value))
|
|
714
|
-
}
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
### 浏览器端使用 ai.stream(处理流式 chunk)
|
|
718
|
-
|
|
719
|
-
`ai.stream` 是纯函数,浏览器端可直接使用:
|
|
720
|
-
|
|
721
|
-
```typescript
|
|
722
|
-
import { ai } from '@h-ai/ai'
|
|
723
|
-
|
|
724
|
-
const processor = ai.stream.createProcessor()
|
|
725
|
-
// 将服务端返回的 SSE chunk 逐条 process,提取 delta
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
---
|
|
729
|
-
|
|
730
|
-
## 相关 Skills
|
|
198
|
+
## 质量与安全检查
|
|
731
199
|
|
|
732
|
-
-
|
|
733
|
-
-
|
|
734
|
-
-
|
|
735
|
-
-
|
|
736
|
-
- `
|
|
737
|
-
- `hai-datapipe`:数据清洗与分块(Knowledge 入库前的数据处理)
|
|
200
|
+
- API Key 只从配置 / 环境变量读取,禁止写死。
|
|
201
|
+
- 工具 handler 必须校验输入;用户可见错误走 i18n/错误码。
|
|
202
|
+
- 批量导入、批量 embedding 用批量 API,避免 await-in-loop。
|
|
203
|
+
- 文件解析/OCR 失败按 `HaiAIError.FILE_*` 返回,不抛业务异常。
|
|
204
|
+
- A2A 认证失败返回 `A2A_AUTH_FAILED`;不要记录 token、apiKey、私钥等敏感字段。
|