@h-ai/ai 0.1.0-alpha.67 → 0.1.0-alpha.68
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/README.en.md +104 -35
- package/README.ja.md +104 -35
- package/README.md +128 -36
- package/dist/ai-audio-types-4R5yUAVl.d.ts +614 -0
- package/dist/ai-audio-ws-protocol-nfhO0tOo.d.ts +7605 -0
- package/dist/browser.d.ts +6 -6
- package/dist/browser.js +3 -3
- package/dist/chunk-GMQFIWND.js +254 -0
- package/dist/chunk-GMQFIWND.js.map +1 -0
- package/dist/{chunk-Q7NORWP4.js → chunk-HEVXR252.js} +236 -15
- package/dist/chunk-HEVXR252.js.map +1 -0
- package/dist/{chunk-JC7QBT3C.js → chunk-HYMP57AC.js} +6 -2
- package/dist/chunk-HYMP57AC.js.map +1 -0
- package/dist/client/index.d.ts +38 -274
- package/dist/client/index.js +2 -2
- package/dist/index.d.ts +18 -40
- package/dist/index.js +2179 -1321
- package/dist/index.js.map +1 -1
- package/package.json +19 -7
- package/dist/ai-audio-ws-protocol-S4N8-LBa.d.ts +0 -1739
- package/dist/ai-reasoning-types-DZd_6Umd.d.ts +0 -3936
- package/dist/chunk-7SMQSE4S.js +0 -578
- package/dist/chunk-7SMQSE4S.js.map +0 -1
- package/dist/chunk-JC7QBT3C.js.map +0 -1
- package/dist/chunk-Q7NORWP4.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -24,8 +24,11 @@ AI capabilities through a unified `ai` service: LLM chat, image generation, tool
|
|
|
24
24
|
- `ai.audio`: ASR and TTS with full and streaming calls for OpenAI / MiMo / Qwen / Doubao.
|
|
25
25
|
- `ai.image`: OpenAI GPT Image, Google Gemini Image / Nano Banana, Qwen-Image 2.0/3.0, Seedream 4.x/5.x, and Pollinations free-quota models.
|
|
26
26
|
- `ai.a2a`: Agent-to-Agent request handling and remote calls.
|
|
27
|
-
- `@h-ai/ai/client`:
|
|
28
|
-
|
|
27
|
+
- `@h-ai/ai/client`: standalone browser Audio WebSocket client.
|
|
28
|
+
|
|
29
|
+
`@h-ai/ai/client` exports only `createAudioClient({ url, getTicket })` and Audio client types. Use typed domain methods from `@h-ai/api-client` for HTTP. Default chat completions are non-streaming; bind application contracts for streaming and A2A. Redundant string-path clients and SSE helpers have been removed. The browser does not export the Node `ai` singleton.
|
|
30
|
+
|
|
31
|
+
- `AIStoreProvider`: storage abstraction. Production requires `stateScope: 'shared'`; the built-in provider needs initialized PostgreSQL/MySQL and a network vecdb. Development/explicit tests may use a temporary store.
|
|
29
32
|
|
|
30
33
|
For the full method list, error codes, and longer examples, see [REFERENCE.md (Chinese)](./REFERENCE.md).
|
|
31
34
|
|
|
@@ -33,7 +36,7 @@ For the full method list, error codes, and longer examples, see [REFERENCE.md (C
|
|
|
33
36
|
|
|
34
37
|
### Quick start
|
|
35
38
|
|
|
36
|
-
#### LLM-only (no database dependencies)
|
|
39
|
+
#### Development LLM-only (no database dependencies)
|
|
37
40
|
|
|
38
41
|
```ts
|
|
39
42
|
import { ai } from '@h-ai/ai'
|
|
@@ -54,7 +57,7 @@ const result = await ai.llm.chat({
|
|
|
54
57
|
await ai.close()
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
Development/explicit tests without initialized reldb/vecdb use a temporary process store, lost on close, exit, or instance switching. When either `NODE_ENV` or `HAI_CORE_ENV` is production, temporary stores, SQLite and local vector backends fail initialization. Initialize shared RelDB and network vecdb first, or supply a shared provider with equivalent atomic guarantees.
|
|
58
61
|
|
|
59
62
|
#### Persistent DB provider (reldb + vecdb)
|
|
60
63
|
|
|
@@ -63,31 +66,42 @@ import { ai } from '@h-ai/ai'
|
|
|
63
66
|
import { reldb } from '@h-ai/reldb'
|
|
64
67
|
import { vecdb } from '@h-ai/vecdb'
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
messages: [{ role: 'user', content: '
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
// 开发/测试使用本地后端;生产改为共享 PostgreSQL/MySQL 与网络 vecdb。
|
|
70
|
+
const failures: unknown[] = []
|
|
71
|
+
try {
|
|
72
|
+
const database = await reldb.init({ type: 'sqlite', database: './ai.db' })
|
|
73
|
+
if (!database.success)
|
|
74
|
+
throw new Error(database.error.message)
|
|
75
|
+
const vectors = await vecdb.init({ type: 'lancedb', path: './ai-vec.db' })
|
|
76
|
+
if (!vectors.success)
|
|
77
|
+
throw new Error(vectors.error.message)
|
|
78
|
+
const initialized = await ai.init({
|
|
79
|
+
llm: { model: 'gpt-4o-mini', apiKey: process.env.HAI_AI_LLM_APIKEY },
|
|
80
|
+
})
|
|
81
|
+
if (!initialized.success)
|
|
82
|
+
throw new Error(initialized.error.message)
|
|
83
|
+
const result = await ai.llm.chat({ messages: [{ role: 'user', content: 'Hello!' }] })
|
|
84
|
+
if (!result.success)
|
|
85
|
+
throw new Error(result.error.message)
|
|
83
86
|
const reply = result.data.choices[0]?.message.content ?? ''
|
|
84
87
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
catch (error) {
|
|
89
|
+
failures.push(error)
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await ai.close()
|
|
93
|
+
for (const service of [vecdb, reldb]) {
|
|
94
|
+
const closed = await service.close()
|
|
95
|
+
if (!closed.success)
|
|
96
|
+
failures.push(new Error(closed.error.message))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (failures.length)
|
|
100
|
+
throw new AggregateError(failures, 'AI operation or cleanup failed')
|
|
89
101
|
```
|
|
90
102
|
|
|
103
|
+
Concurrent MySQL first startup accepts only the explicit mysql2 ER_DUP_KEYNAME/1061 error, then verifies the actual columns and order, non-unique full columns, ascending visible BTREE, and no expressions. An existing index with the wrong definition fails. The account needs table creation and INDEX privileges; metadata, permission, or other DDL failures return CONFIGURATION_ERROR from ai.init. All started DDL settles before return. MySQL DDL is not rolled back: repair privileges/schema before retrying partial initialization. Check each initialization result, reject business traffic on failure, and close AI, vecdb, and reldb in reverse order.
|
|
104
|
+
|
|
91
105
|
#### Custom StoreProvider
|
|
92
106
|
|
|
93
107
|
```ts
|
|
@@ -110,7 +124,9 @@ await ai.close()
|
|
|
110
124
|
- Lifecycle: `await ai.init(config, options?)` / `await ai.close()`; closure waits for custom `AIStoreProvider.close()`.
|
|
111
125
|
- Domain methods return `HaiResult<T>` or `Promise<HaiResult<T>>`, with failures expressed by `result.success === false` and `result.error.code`. Streaming `AsyncIterable`, client transports, and third-party callbacks can throw during establishment or iteration.
|
|
112
126
|
- `ai.tools` and `ai.stream` are pure-function subsystems usable without initialization.
|
|
113
|
-
-
|
|
127
|
+
- Development/tests without reldb/vecdb use temporary storage; both initialized selects DB storage. Production validates the real database/vector backend capability. Custom providers must accurately declare `stateScope`.
|
|
128
|
+
|
|
129
|
+
Inject stores through the public AIStoreProvider extension. Built-in LLM providers, factory dependencies and Context dependencies are internal. Configure model endpoints through configuration; history queries, AskOptions and ChatRecord remain public.
|
|
114
130
|
|
|
115
131
|
### API overview
|
|
116
132
|
|
|
@@ -144,6 +160,8 @@ const temp = await ai.llm.chat({
|
|
|
144
160
|
})
|
|
145
161
|
```
|
|
146
162
|
|
|
163
|
+
SSE text decoding follows [WHATWG event-stream rules](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation): split LF/CRLF/CR, multiline data, one optional space, inherited valid id/retry, default message events, and no dispatch of unfinished EOF data. encodeSSE normalizes data line endings and explicitly throws for invalid metadata. Anthropic/MiMo share an internal UTF-8 reader that cancels/releases the body on exit, cancellation, termination or failure, without reconnecting or replaying business operations. Anthropic deadlines cover the body; MiMo retains separate deadlines per HTTP request.
|
|
164
|
+
|
|
147
165
|
#### Tool calls
|
|
148
166
|
|
|
149
167
|
```ts
|
|
@@ -180,7 +198,13 @@ const chat = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
|
|
|
180
198
|
|
|
181
199
|
`ai.a2a.getAgentCard()` returns a complete A2A 0.3 Agent Card with `protocolVersion`, MIME, and streaming capabilities. Kit discovery defaults to `/.well-known/agent-card.json`. `handleRequest()` always returns JSON-RPC/SSE results; uninitialized calls return JSON-RPC errors. Trusted HTTP `agentId` maps to executor `context.user.userName`. JSON-RPC/SSE are supported; push notifications, gRPC, and REST are not claimed. Applications remain responsible for business resource authorization.
|
|
182
200
|
|
|
183
|
-
A2A
|
|
201
|
+
A2A journal, context ownership and message tables initialize during `ai.init()`. Tasks use trusted `agentId` scopes for get/cancel/continuation. Anonymous requests share one principal; authenticate at the trusted transport boundary in production. `listMessages` is a server administration API with `callerId`, `contextId`, and `since`; never expose it to unauthorized clients.
|
|
202
|
+
|
|
203
|
+
Tasks and events use one shared journal, without a separate legacy TaskStore. Push notification configuration and authenticated extended card RPCs return standard SDK unsupported/unconfigured errors without executing tasks. Executor types remain public; internal handler/Store assembly types are outside the public contract.
|
|
204
|
+
|
|
205
|
+
Inbound messages must persist before the executor starts, with shared generation/deadline checks repeated after that wait. Agent messages await logging inside the existing bounded event queue before journal publication. Logging failure produces a failed/interrupted Task rather than a false success; retrying the same message does not rerun business effects. Logs and the journal are separate records: a log is not proof of delivery. Reconcile possible effects using the Task's `requiresEffectReconciliation` marker. Query failures return `A2A_LIST_MESSAGES_FAILED` (984) without raw SQL or connection details.
|
|
206
|
+
|
|
207
|
+
Task and events publish in one CAS, so any node can subscribe. A retry with identical message ID/content reuses the current result; changed content or an older round fails. Cross-node cancellation invokes the real execution owner's executor.cancelTask. Pending cancellation blocks new messages on the same Task so a late callback cannot affect a later execution. Input-required tasks can continue after node restart. Expired leases, absolute deadlines, or an executor returning only working become failed/interrupted with `metadata.haiExecution.requiresEffectReconciliation=true`; late owners cannot write and unknown external effects are never replayed automatically. `a2a.execution` defaults to `leaseMs: 30000`, `pollIntervalMs: 250`, `maxExecutionMs: 300000`. Limits are 1 MiB/message, 8 MiB/journal, 2048 events/round, 256 receipts/Task and 32 reference tasks. Use stable business idempotency keys and reconcile unknown effects. See the [state/deployment matrix (Chinese)](../../.agents/instructions/distributed-state.md) for guarantees and validation boundaries.
|
|
184
208
|
|
|
185
209
|
#### MCP Server
|
|
186
210
|
|
|
@@ -202,6 +226,16 @@ await server.connect(new StreamableHTTPServerTransport({ sessionIdGenerator: ()
|
|
|
202
226
|
|
|
203
227
|
#### Memory / RAG / Knowledge
|
|
204
228
|
|
|
229
|
+
Each Knowledge ingestion creates a unique `revision` and chunk IDs. It prepares all chunks, embeddings and entities, persists a staging manifest, writes vectors, then publishes the complete document and entity snapshot with shared Store CAS. Only one writer can rebuild a document at a time. Failure preserves the complete old version; shorter replacements cannot expose old trailing chunks. Retrieval filters unpublished revisions and retries around publication; sustained contention returns an error. Candidate expansion and cleanup backlogs can increase query cost.
|
|
230
|
+
|
|
231
|
+
Successful ingestion returns `revision` and `cleanupPending`. A true flag means the new version is published but old vectors still require cleanup. Paginate `repair({ collection, offset, limit })` regularly across every manifest; setup checks the first 100. Staging expires after five minutes according to the storage clock and an expired writer cannot publish. Abandoned writes retain permanent cleanup tombstones so later scans can remove delayed external writes; do not prune these with logs. SQL and vector storage do not share a transaction. Removal hides data first and reports cleanup failures. Collection deletion retains the empty vector collection and generation tombstones for compensation; setup never revives the previous generation.
|
|
232
|
+
|
|
233
|
+
The DB Store propagates collection and vector failures. LanceDB uses persistent primary-key atomic merge. Qdrant writes/deletes wait until applied and counts are exact. Multiple nodes must share relational storage and a server vector backend. Custom KnowledgeStore implementations must provide the same publication and compensation contract.
|
|
234
|
+
|
|
235
|
+
With LanceDB, collection dimensions and metrics persist in the Arrow schema; connection metrics only affect new collections. Reuse the same persistent directory after restart and rebuild collections when embedding dimensions change. Local paths on separate nodes do not share data; distributed Knowledge should use a server vector backend.
|
|
236
|
+
|
|
237
|
+
Knowledge `retrieve/ask` and the default DB Store forward `filter` to the vector provider and propagate validation/query failures. Knowledge returns `KNOWLEDGE_RETRIEVE_FAILED` (`RAG_FAILED` from ask), without disguising failure as empty results. LanceDB supports AND equality on literal top-level keys with string, finite number, boolean, or null values, such as `{ tenant: 'acme', active: true }`. Null never matches missing keys; objects, arrays, and undefined are unsupported. Filtering precedes vector topK. Check null/complex-value semantics separately for other backends. LanceDB metadata now uses native JSON fields and retains nested JSON; rebuild legacy string-metadata collections before writing or filtering.
|
|
238
|
+
|
|
205
239
|
```ts
|
|
206
240
|
const enriched = await ai.memory.injectMemories(messages, { objectId: 'user-001', topK: 5 })
|
|
207
241
|
if (!enriched.success) {
|
|
@@ -221,10 +255,19 @@ const rag = await ai.rag.query('核心架构是什么?', { sources: ['docs'],
|
|
|
221
255
|
|
|
222
256
|
const setup = await ai.knowledge.setup()
|
|
223
257
|
if (setup.success) {
|
|
224
|
-
await ai.knowledge.ingest({ documentId: 'doc-001', content: markdownText, title: '产品手册' })
|
|
258
|
+
const ingested = await ai.knowledge.ingest({ documentId: 'doc-001', content: markdownText, title: '产品手册' })
|
|
259
|
+
if (!ingested.success)
|
|
260
|
+
return ingested
|
|
261
|
+
if (ingested.data.cleanupPending) {
|
|
262
|
+
const repaired = await ai.knowledge.repair({ offset: 0, limit: 100 })
|
|
263
|
+
if (!repaired.success)
|
|
264
|
+
return repaired
|
|
265
|
+
}
|
|
225
266
|
}
|
|
226
267
|
```
|
|
227
268
|
|
|
269
|
+
Knowledge chunks use datapipe units and hard bounds: words for word mode, Unicode code points otherwise, retaining separators from cleaned text. AI defaults remain markdown/maxSize=1500/overlap=200; a smaller maxSize override must also keep inherited overlap below it. Invalid chunk configuration or expressions return KNOWLEDGE_INGEST_FAILED before embedding and revision publication, without an unbounded whole-document fallback.
|
|
270
|
+
|
|
228
271
|
#### Audio
|
|
229
272
|
|
|
230
273
|
Register audio models in `ai.init()` and map the default transcription/synthesis models first. Credentials fall back to platform environment variables. Enable `inheritLlmApiKey` explicitly only when LLM and audio models are confirmed to share credentials:
|
|
@@ -332,6 +375,8 @@ Cancellation, timeout, and connection problems become domain errors: `AbortSigna
|
|
|
332
375
|
|
|
333
376
|
Browsers/mobile clients use the unified audio WebSocket exposed by `@h-ai/serv`. `@h-ai/ai/client` offers the same `audio.*` API as Node, hiding transport details. Browser clients distinguish completion, cancellation (`AUDIO_CANCELLED`), and abnormal disconnect (`AUDIO_CONNECTION_FAILED`); cancellation or disconnect before `end` throws the domain code. `synthesize` never treats incomplete audio as success.
|
|
334
377
|
|
|
378
|
+
IndexTTS pairs the same Undici version's fetch, Agent, and multipart types on Node. Model `timeout` covers response-header and body waits; user cancellation returns `AUDIO_CANCELLED`, and deadlines return `AUDIO_TIMEOUT`. Dedicated connections close after success, failure, or cancellation. Segmented streaming fallback retains errors during iteration.
|
|
379
|
+
|
|
335
380
|
> For self-hosted CPU/GPU model services (faster-whisper / IndexTTS / Qwen3-4B), weight downloads, and offline bundles, see [models/ (Chinese)](./models/README.md). Images and weights prefer ModelScope, with automatic HuggingFace mirror fallback.
|
|
336
381
|
|
|
337
382
|
#### Image generation
|
|
@@ -364,8 +409,14 @@ if (result.success) {
|
|
|
364
409
|
|
|
365
410
|
`referenceImages` is optional. Public APIs accept only image bytes and MIME; providers convert multipart, `inlineData`, and Data URLs. Set credentials on model entries or with `HAI_AI_IMAGE_<PROVIDER>_APIKEY` / vendor environment variables. Qwen and Seedream temporary URLs are downloaded immediately inside providers and never exposed publicly. See [REFERENCE.md (Chinese)](./REFERENCE.md#image) for model details and API differences.
|
|
366
411
|
|
|
412
|
+
Downloads require public HTTPS on default port 443 without URL credentials. Socket DNS lookup validates every returned address and connects using those same results. Private, loopback, link-local, mapped/translated, and reserved IPv4/IPv6 addresses are blocked. Each of at most three redirects is checked again. Downloads use isolated connections without vendor authentication or application cookies. `baseUrl` is trusted operator configuration; vendor API calls reject redirects.
|
|
413
|
+
|
|
414
|
+
`image.maxImageBytes` defaults to 20971520 (20 MiB; maximum 100 MiB) per output/reference image. `image.maxImages` defaults to 4 (maximum 16) for output/reference counts, with sequential URL downloads. Actual streamed bytes, including decompressed bytes, are bounded and excess responses are cancelled. JSON responses are limited to `max(64 KiB, 2 × maxImageBytes)` and error bodies to 64 KiB. Model `timeout` defaults to 120000ms for the entire vendor request, redirects, and downloads; `signal` cancels the whole process. Outputs require matching PNG/JPEG/GIF/WebP MIME and magic bytes; SVG is rejected. Magic checks do not decode complete images or verify dimensions. Address rejection, limits, and deadlines return `IMAGE_DOWNLOAD_BLOCKED`, `IMAGE_TOO_LARGE`, and `IMAGE_TIMEOUT`; check success before use.
|
|
415
|
+
|
|
367
416
|
#### Context manager
|
|
368
417
|
|
|
418
|
+
Automatic memory extraction runs locally in the background; `pendingMemoryTasks=0` does not imply success. `flush()` waits for the batch and returns `MEMORY_EXTRACT_FAILED` with a count for thrown errors or failed HaiResults. Concurrent observers share that result. Returning acknowledges the report; later flushes observe new tasks without replaying partially completed effects. `save()`, `reset({ waitForMemoryTasks: true })`, and `consolidate()` return that failure before subsequent writes. Reconcile external effects after a process crash.
|
|
419
|
+
|
|
369
420
|
```ts
|
|
370
421
|
const manager = ai.context.createManager({
|
|
371
422
|
scope: { objectId: 'user-001', sessionId: 'sess-001' },
|
|
@@ -375,7 +426,9 @@ const manager = ai.context.createManager({
|
|
|
375
426
|
})
|
|
376
427
|
if (manager.success) {
|
|
377
428
|
const reply = await manager.data.chat('你好')
|
|
378
|
-
await manager.data.save()
|
|
429
|
+
const saved = await manager.data.save()
|
|
430
|
+
if (!saved.success)
|
|
431
|
+
return saved
|
|
379
432
|
// reset 生命周期完整:终止活动生成、清空消息/摘要/轮次,默认保留系统提示词
|
|
380
433
|
await manager.data.reset() // 可传 { preserveSystemPrompt, cancelActiveTurn, waitForMemoryTasks }
|
|
381
434
|
}
|
|
@@ -392,12 +445,15 @@ With `turnCommit: 'manual'`, results are not stored automatically. A `turnId` is
|
|
|
392
445
|
`chatStream` registers a turn and emits `turn_started` **before calling the model** (`turn_started → delta* → done`, or `turn_started → delta* → cancelled` on cancellation). Even if `AbortSignal` cancels generation midway, the caller gets `turnId` and generated text for committing actual content:
|
|
393
446
|
|
|
394
447
|
```ts
|
|
395
|
-
const
|
|
448
|
+
const created = ai.context.createManager({ scope: { objectId: 'user-001', sessionId: 'sess-001' }, turnCommit: 'manual' })
|
|
449
|
+
if (!created.success)
|
|
450
|
+
return created
|
|
451
|
+
const m = created.data
|
|
396
452
|
const controller = new AbortController()
|
|
397
453
|
|
|
398
454
|
for await (const ev of m.chatStream('请展开讲讲', { signal: controller.signal })) {
|
|
399
455
|
if (ev.type === 'turn_started') {
|
|
400
|
-
m.markTurnSpeaking(ev.turnId) // 可选:标记进入播放
|
|
456
|
+
await m.markTurnSpeaking(ev.turnId) // 可选:标记进入播放
|
|
401
457
|
}
|
|
402
458
|
else if (ev.type === 'delta') {
|
|
403
459
|
feedTts(ev.text) // 边生成边合成播放
|
|
@@ -450,6 +506,7 @@ await ai.persona.save({
|
|
|
450
506
|
// 组合出可直接喂给 ContextManager 的系统提示词(systemPrompt + traits)
|
|
451
507
|
const composed = await ai.persona.compose('xiaoq')
|
|
452
508
|
const manager = ai.context.createManager({
|
|
509
|
+
scope: { objectId: 'user-001', sessionId: 'persona-session' },
|
|
453
510
|
systemPrompt: composed.data,
|
|
454
511
|
memory: { enable: true, enableExtract: true, scope: { personaId: 'xiaoq' } },
|
|
455
512
|
})
|
|
@@ -457,6 +514,8 @@ const manager = ai.context.createManager({
|
|
|
457
514
|
|
|
458
515
|
`ai.persona` exposes `save` / `get` / `update` / `remove` / `list` / `compose`. Profiles are shared globally, not isolated by `objectId`.
|
|
459
516
|
|
|
517
|
+
### Configuration
|
|
518
|
+
|
|
460
519
|
```yaml
|
|
461
520
|
llm:
|
|
462
521
|
apiKey: ''
|
|
@@ -512,9 +571,17 @@ memory:
|
|
|
512
571
|
```
|
|
513
572
|
|
|
514
573
|
- **`native` (default, recommended)**: HAI's native engine reuses vecdb, reldb, LLM, and Embedding. `extract` uses **Mem0-style batch merging**: one LLM call decides ADD / UPDATE / DELETE / NONE for extracted facts and related existing memories, providing incremental updates, deduplication, contradiction removal, and `category` labels. `maxEntriesPerObject`, `maxEntriesGlobal`, `recencyDecay`, `embeddingEnabled`, and `writebackRelatedTopK` apply. Eviction is partitioned by `objectId`, so heavy writes by one subject do not evict others' memories. `scope` candidates are narrowed by `objectId` (≤ `maxEntriesPerObject`); PostgreSQL also pushes scope into a JSONB containment query using a **GIN index**. SQLite/MySQL fall back to equivalent in-memory matching.
|
|
515
|
-
- **`mem0` (mem0ai/oss)**: directly embeds the `mem0ai/oss` `Memory` engine without cloud services. LLM/Embedder come from `llm` configuration; `qdrant`
|
|
574
|
+
- **`mem0` (mem0ai/oss)**: directly embeds the `mem0ai/oss` `Memory` engine without cloud services. LLM/Embedder come from `llm` configuration; `qdrant` can reuse vecdb. The adapter maps only Qdrant with complete connection settings. Production (`NODE_ENV` or `HAI_CORE_ENV=production`) rejects actual ephemeral fallback even when `memory.allowEphemeralFallback: true`; missing backends also fail. Explicit fallback is limited to development or `HAI_E2E=1` / `HAI_E2E_MOCK=1`. History is disabled by default.
|
|
516
575
|
|
|
517
|
-
Both providers share `ai.memory.*` (`extract` / `recall` / `injectMemories` / `add` / `update` / `get` / `remove` / `list` / `listPage` / `clear`) with `objectId` isolation and key-value `scope` filters such as `{ topicId, personaId }`. `recall` / `list` / `listPage` / `clear` strictly filter scope; `clear` with `types` / `scope` deletes only jointly matching entries. mem0 `extract` uses the shared extractor for classification/scoring (`types` / `model` / `minImportance` / `systemPrompt`), then writes with `infer:false`, preserving `hai_type` / `hai_importance`. Its `recall` supports `types` and `recencyWeight` decay, matching native behavior. One
|
|
576
|
+
Both providers share `ai.memory.*` (`extract` / `recall` / `injectMemories` / `add` / `update` / `get` / `remove` / `list` / `listPage` / `clear`) with `objectId` isolation and key-value `scope` filters such as `{ topicId, personaId }`. `recall` / `list` / `listPage` / `clear` strictly filter scope; `clear` with `types` / `scope` deletes only jointly matching entries. mem0 `extract` uses the shared extractor for classification/scoring (`types` / `model` / `minImportance` / `systemPrompt`), then writes with `infer:false`, preserving `hai_type` / `hai_importance`. Its `recall` supports `types` and `recencyWeight` decay, matching native behavior. Mem0 updates text and metadata in place, preserving `id` and creation time. SDK queries use `filters.user_id`; writes and whole-subject deletion use `userId`. With no SDK list cursor, `list`, `listPage`, and filtered `clear` support at most 1000 raw subject candidates, including expired records. One extra candidate detects truncation; exceeding the bound returns `MEMORY_LIST_INCOMPLETE` (906), without a fabricated total or partial filtered deletion. Complete candidates are filtered by UTC expiration date, type and scope before pagination. Whole-subject `deleteAll` is independent of this listing limit. Public empty-filter clear is rejected; only trusted administration may explicitly call `admin.clearAll({ confirm: true })`.
|
|
577
|
+
|
|
578
|
+
Mem0 3.3.1 still calls `QdrantClient.search`. The catalog and AI optional peer pin the tested `@qdrant/js-client-rest` version `1.18.0`; startup checks this public method and rejects incompatible installations. Do not override it with 1.19, which removed that method.
|
|
579
|
+
|
|
580
|
+
The adapter injects a dedicated client through the SDK's public `client` configuration. Mem0 Qdrant upserts and deletes use `wait: true`, awaiting server application before readback or the next deletion batch.
|
|
581
|
+
|
|
582
|
+
Mem0 with pgvector is rejected in every environment: the SDK privately owns a PostgreSQL connection without a public close API, so the framework cannot manage its lifecycle. Use `memory.provider: native` for pgvector; native support is retained. `disableHistory: true` selects a resource-free history implementation and creates no history SQLite file. The factory awaits backend readiness through one bounded public read before reporting success.
|
|
583
|
+
|
|
584
|
+
SDK add receipts omit full business fields, so `add` / `extract` read the stored record before returning. Extraction waits for all pending writes. A write or readback failure sets `error.ext.requiresEffectReconciliation=true` and retains known IDs in `memoryIds` (possibly empty if receipts were lost). Reconcile subject records before recovery; blindly repeating add/extract can duplicate committed writes. The framework does not replay automatically.
|
|
518
585
|
|
|
519
586
|
**Candidate pools and scope recall misses**: filtering scope after truncating to `topK` can let higher-ranked memories from other topics/personas displace the target scope. `recall` / `injectMemories` therefore fetch `topK × candidateMultiplier` candidates (default multiplier 5), filter, then take `topK`. Increase `candidateMultiplier` for finer isolation such as `topicId` + `personaId`:
|
|
520
587
|
|
|
@@ -560,8 +627,8 @@ Common error ranges:
|
|
|
560
627
|
- `hai:ai:600-701`:Retrieval/RAG。
|
|
561
628
|
- `hai:ai:800-805`:Knowledge。
|
|
562
629
|
- `hai:ai:050-059`:Audio。
|
|
563
|
-
- `hai:ai:060-
|
|
564
|
-
- `hai:ai:900-
|
|
630
|
+
- `hai:ai:060-068`: Image, including blocked downloads, resource limits, and total deadlines.
|
|
631
|
+
- `hai:ai:900-906`:Memory。
|
|
565
632
|
- `hai:ai:980-984`:A2A。
|
|
566
633
|
|
|
567
634
|
## Development and testing
|
|
@@ -573,6 +640,8 @@ pnpm --filter @h-ai/ai test
|
|
|
573
640
|
pnpm --filter @h-ai/ai build
|
|
574
641
|
```
|
|
575
642
|
|
|
643
|
+
Public JSDoc examples import actual package entrypoints; internal factory examples are marked with their module context and compiled against real local declarations. Run `pnpm check:jsdoc --package ai` from the repository root to verify coverage and example types. Ordinary business calls check HaiResult; streams and the browser AudioClient retain their explicit throwing contracts.
|
|
644
|
+
|
|
576
645
|
## License
|
|
577
646
|
|
|
578
647
|
Apache-2.0
|
package/README.ja.md
CHANGED
|
@@ -24,8 +24,11 @@
|
|
|
24
24
|
- `ai.audio`:通常・ストリーミングの音声認識(ASR)と音声合成(TTS)。OpenAI / MiMo / Qwen / Doubao に対応。
|
|
25
25
|
- `ai.image`:OpenAI GPT Image、Google Gemini Image / Nano Banana、Qwen-Image 2.0/3.0、Seedream 4.x/5.x、Pollinations の無料枠モデルによる画像生成。
|
|
26
26
|
- `ai.a2a`:Agent-to-Agent のリクエスト処理とリモート呼び出し。
|
|
27
|
-
- `@h-ai/ai/client
|
|
28
|
-
|
|
27
|
+
- `@h-ai/ai/client`:独立したブラウザー Audio WebSocket クライアント。
|
|
28
|
+
|
|
29
|
+
`@h-ai/ai/client` は `createAudioClient({ url, getTicket })` と Audio クライアント型のみを公開します。HTTP は `@h-ai/api-client` の型付きドメインメソッドを使います。既定のチャット補完は非ストリーミングで、ストリームと A2A はアプリ独自の contract に結び付けます。文字列パスの旧クライアントと重複 SSE helper は削除しました。ブラウザーには Node の `ai` シングルトンを公開しません。
|
|
30
|
+
|
|
31
|
+
- `AIStoreProvider`:ストレージ抽象。本番は `stateScope: 'shared'` が必須で、組み込み実装は PostgreSQL/MySQL とネットワーク vecdb の初期化を必要とします。開発・明示的テストでは一時 Store を利用できます。
|
|
29
32
|
|
|
30
33
|
詳しいメソッド一覧・エラーコード・長い例は [REFERENCE.md(中国語)](./REFERENCE.md) を参照してください。
|
|
31
34
|
|
|
@@ -33,7 +36,7 @@
|
|
|
33
36
|
|
|
34
37
|
### クイックスタート
|
|
35
38
|
|
|
36
|
-
#### LLM のみ(DB 依存なし)
|
|
39
|
+
#### 開発用 LLM のみ(DB 依存なし)
|
|
37
40
|
|
|
38
41
|
```ts
|
|
39
42
|
import { ai } from '@h-ai/ai'
|
|
@@ -54,7 +57,7 @@ const result = await ai.llm.chat({
|
|
|
54
57
|
await ai.close()
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
reldb/vecdb
|
|
60
|
+
開発・明示的テストで reldb/vecdb が未初期化の場合、一時 Store を使い、終了やインスタンス切り替えで失われます。`NODE_ENV` または `HAI_CORE_ENV` が production の場合、一時 Store・SQLite・ローカル vecdb は初期化に失敗します。共有 RelDB とネットワーク vecdb を先に初期化するか、同等の原子的契約を満たす共有 Provider を指定してください。
|
|
58
61
|
|
|
59
62
|
#### 永続 DB Provider(reldb + vecdb)
|
|
60
63
|
|
|
@@ -63,31 +66,42 @@ import { ai } from '@h-ai/ai'
|
|
|
63
66
|
import { reldb } from '@h-ai/reldb'
|
|
64
67
|
import { vecdb } from '@h-ai/vecdb'
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
messages: [{ role: 'user', content: '
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
// 开发/测试使用本地后端;生产改为共享 PostgreSQL/MySQL 与网络 vecdb。
|
|
70
|
+
const failures: unknown[] = []
|
|
71
|
+
try {
|
|
72
|
+
const database = await reldb.init({ type: 'sqlite', database: './ai.db' })
|
|
73
|
+
if (!database.success)
|
|
74
|
+
throw new Error(database.error.message)
|
|
75
|
+
const vectors = await vecdb.init({ type: 'lancedb', path: './ai-vec.db' })
|
|
76
|
+
if (!vectors.success)
|
|
77
|
+
throw new Error(vectors.error.message)
|
|
78
|
+
const initialized = await ai.init({
|
|
79
|
+
llm: { model: 'gpt-4o-mini', apiKey: process.env.HAI_AI_LLM_APIKEY },
|
|
80
|
+
})
|
|
81
|
+
if (!initialized.success)
|
|
82
|
+
throw new Error(initialized.error.message)
|
|
83
|
+
const result = await ai.llm.chat({ messages: [{ role: 'user', content: 'Hello!' }] })
|
|
84
|
+
if (!result.success)
|
|
85
|
+
throw new Error(result.error.message)
|
|
83
86
|
const reply = result.data.choices[0]?.message.content ?? ''
|
|
84
87
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
catch (error) {
|
|
89
|
+
failures.push(error)
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await ai.close()
|
|
93
|
+
for (const service of [vecdb, reldb]) {
|
|
94
|
+
const closed = await service.close()
|
|
95
|
+
if (!closed.success)
|
|
96
|
+
failures.push(new Error(closed.error.message))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (failures.length)
|
|
100
|
+
throw new AggregateError(failures, 'AI operation or cleanup failed')
|
|
89
101
|
```
|
|
90
102
|
|
|
103
|
+
MySQL の初回初期化は複数ノードで実行できます。mysql2 の明示的な ER_DUP_KEYNAME/1061 だけを受け入れ、実際の列・順序、非一意・列全体・昇順・可視 BTREE・式なしを再確認します。同名でも構造が異なれば失敗します。テーブル作成と INDEX 権限が必要で、メタデータ取得・権限・その他 DDL の失敗は ai.init の CONFIGURATION_ERROR になります。開始した全 DDL の終了を待ってから返します。MySQL DDL はロールバックされないため、権限や構造を修正してから再試行してください。各初期化結果を確認し、失敗時は業務リクエストを受けず、AI・vecdb・reldb の逆順で終了します。
|
|
104
|
+
|
|
91
105
|
#### 独自 StoreProvider
|
|
92
106
|
|
|
93
107
|
```ts
|
|
@@ -110,7 +124,9 @@ await ai.close()
|
|
|
110
124
|
- ライフサイクルは `await ai.init(config, options?)` / `await ai.close()`。終了時は独自 `AIStoreProvider.close()` を待ちます。
|
|
111
125
|
- ドメイン操作は `HaiResult<T>` または `Promise<HaiResult<T>>` を返し、`result.success === false` と `result.error.code` で失敗を表します。`AsyncIterable`、クライアント通信、外部コールバックは開始・反復時に例外を投げる場合があります。
|
|
112
126
|
- `ai.tools` と `ai.stream` は純粋関数で、初期化せず使用できます。
|
|
113
|
-
- reldb/vecdb
|
|
127
|
+
- 開発・テストで reldb/vecdb がなければ一時 Store、両方が初期化済みなら DB Provider を使います。本番では実際の DB/vecdb 共有能力を検証し、独自 Provider も正確な `stateScope` を宣言する必要があります。
|
|
128
|
+
|
|
129
|
+
Store の拡張には公開 AIStoreProvider を使用します。組み込み LLM Provider、ファクトリ依存、Context 内部依存は非公開です。モデル接続は設定で行い、履歴照会、AskOptions、ChatRecord は公開を維持します。
|
|
114
130
|
|
|
115
131
|
### API 一覧
|
|
116
132
|
|
|
@@ -144,6 +160,8 @@ const temp = await ai.llm.chat({
|
|
|
144
160
|
})
|
|
145
161
|
```
|
|
146
162
|
|
|
163
|
+
SSE は [WHATWG のイベントストリーム規則](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation)に従い、分割された LF/CRLF/CR、複数 data 行、コロン直後の空白一つ、id/retry の継承、既定の message を扱います。EOF の未完了イベントは配信しません。encodeSSE は data の改行を正規化し、不正なメタデータは明示的に例外を返します。Anthropic/MiMo は UTF-8 reader を共有し、終了・取消・失敗時に本文を取消して解放します。自動再接続や業務再実行は行わず、Anthropic の timeout は本文まで、MiMo は各 HTTP リクエスト単位で適用します。
|
|
164
|
+
|
|
147
165
|
#### ツール呼び出し
|
|
148
166
|
|
|
149
167
|
```ts
|
|
@@ -180,7 +198,13 @@ const chat = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
|
|
|
180
198
|
|
|
181
199
|
`ai.a2a.getAgentCard()` は `protocolVersion`、MIME、streaming 機能を含む完全な A2A 0.3 Agent Card を返します。Kit の既定検出パスは `/.well-known/agent-card.json`。`handleRequest()` は常に JSON-RPC/SSE の結果を返し、未初期化時は JSON-RPC エラーです。信頼された HTTP の `agentId` を executor の `context.user.userName` に対応付けます。JSON-RPC/SSE に対応し、push notifications・gRPC・REST 対応は宣言しません。業務リソースの認可はアプリの責任です。
|
|
182
200
|
|
|
183
|
-
A2A
|
|
201
|
+
A2A の journal・context 所有者・メッセージテーブルは `ai.init()` で初期化します。取得・キャンセル・継続は信頼された `agentId` で分離します。匿名は共通主体のため、本番は信頼された伝送層で認証してください。`listMessages` は `callerId`・`contextId`・`since` を持つ管理 API であり、未認可クライアントへ公開しません。
|
|
202
|
+
|
|
203
|
+
Task とイベントは同じ共有 journal を使用し、従来の独立 TaskStore は組み込みません。push notification 設定と authenticated extended card の RPC は SDK 標準の未対応・未設定エラーを返し、タスクを実行しません。executor 関連型は公開を維持し、内部 handler/Store の組み立て型は公開契約に含めません。
|
|
204
|
+
|
|
205
|
+
入力メッセージの永続化後、共有実行世代と期限を再確認してから executor を開始します。agent メッセージは既存の有界イベントキューでログ保存を待ち、journal に公開します。保存失敗は failed/interrupted Task となり、成功を偽装せず、同じメッセージの再送で業務を再実行しません。ログと journal は別レコードなので、ログの存在だけで配信成功と判断せず、Task の `requiresEffectReconciliation` に従って副作用を確認します。照会失敗は SQL・接続情報を含まない `A2A_LIST_MESSAGES_FAILED`(984)です。
|
|
206
|
+
|
|
207
|
+
Task とイベントは同じ CAS で保存し、任意ノードで購読できます。同一 message ID/内容の再試行は同じ結果を返し、内容変更や古いラウンドは拒否します。別ノードからのキャンセルは実行 owner の executor.cancelTask を呼び、確認待ちの間は同じ Task の新規メッセージを拒否して古いコールバックの影響を防ぎます。input-required は再起動後も継続できます。租約切れ・実行期限・working のまま終了した実行器は failed/interrupted となり、`metadata.haiExecution.requiresEffectReconciliation=true` を付けます。古い owner の書き込みと未知の外部効果の自動再実行を禁止します。`a2a.execution` の既定値は `leaseMs: 30000`、`pollIntervalMs: 250`、`maxExecutionMs: 300000`。上限はメッセージ 1 MiB、journal 8 MiB、1 ラウンド 2048 イベント、Task 256 回执、reference tasks 32 件です。安定した業務冪等キーと外部効果の照合が必要です。[共有状態表(中国語)](../../.agents/instructions/distributed-state.md)に保証と検証範囲を記載しています。
|
|
184
208
|
|
|
185
209
|
#### MCP Server
|
|
186
210
|
|
|
@@ -202,6 +226,16 @@ await server.connect(new StreamableHTTPServerTransport({ sessionIdGenerator: ()
|
|
|
202
226
|
|
|
203
227
|
#### Memory / RAG / Knowledge
|
|
204
228
|
|
|
229
|
+
Knowledge は取り込みごとに一意の `revision` と chunk ID を生成します。全文・ベクトル・エンティティを準備し、永続ステージングマニフェストとベクトルを書いた後、共有 Store CAS で完全なスナップショットを一度に公開します。同じ文書の再構築は一つの writer に制限され、失敗時は完全な旧版を保持します。短い新版に旧末尾 chunk が混在しません。検索は未公開版を除外し、公開と競合すれば再試行します。競合が続く場合はエラーを返します。候補拡張や清掃待ちは検索負荷を増やすことがあります。
|
|
230
|
+
|
|
231
|
+
成功結果の `revision` と `cleanupPending` を確認してください。後者が true の場合、新版は公開済みですが旧ベクトルの清掃が残っています。`repair({ collection, offset, limit })` で全ページを定期処理してください。setup は最初の 100 マニフェストを確認します。ステージングは保存先時計で 5 分後に失効し、旧 writer は公開できません。遅延した外部書き込みを繰り返し清掃するため、放棄版の墓碑は永久保持し、ログと共に削除しません。SQL とベクトルは共通トランザクションではありません。文書・集合の削除は先に可視性を取り消し、清掃失敗を返します。集合削除は空ベクトル集合と世代墓碑を補償用に残し、再 setup で旧世代を復元しません。
|
|
232
|
+
|
|
233
|
+
DB Store は集合・ベクトルの失敗を伝播します。LanceDB は永続主キーの原子 merge を使い、Qdrant は書き込み・削除の適用を待ち、正確な件数を取得します。分散ノードは同一の関係保存先とサーバー型ベクトル基盤を共有します。独自 KnowledgeStore も同じ公開・補償契約が必要です。
|
|
234
|
+
|
|
235
|
+
LanceDB の集合次元と metric は Arrow schema に永続化され、接続設定は新規集合にのみ影響します。再起動後は同じ永続ディレクトリを使い、embedding 次元を変更する場合は集合を再作成してください。別ノードのローカルパスは自動共有されないため、分散 Knowledge にはサーバー型ベクトルバックエンドを使用します。
|
|
236
|
+
|
|
237
|
+
Knowledge の `retrieve/ask` と既定 DB Store は `filter` をベクトル Provider に渡し、検証・検索エラーを上位に伝えます。Knowledge は空結果に偽装せず `KNOWLEDGE_RETRIEVE_FAILED`(ask は `RAG_FAILED`)を返します。LanceDB は最上位のリテラルキーについて string・有限 number・boolean・null の AND 完全一致のみを支援します。例:`{ tenant: 'acme', active: true }`。null は欠落キーに一致せず、オブジェクト・配列・undefined は非対応です。フィルターはベクトル topK の前に適用します。他バックエンドの null・複合値の意味は個別に確認してください。LanceDB metadata は入れ子 JSON も保存できるネイティブ JSON フィールドになり、旧文字列形式は書き込み・フィルター前に再作成が必要です。
|
|
238
|
+
|
|
205
239
|
```ts
|
|
206
240
|
const enriched = await ai.memory.injectMemories(messages, { objectId: 'user-001', topK: 5 })
|
|
207
241
|
if (!enriched.success) {
|
|
@@ -221,10 +255,19 @@ const rag = await ai.rag.query('核心架构是什么?', { sources: ['docs'],
|
|
|
221
255
|
|
|
222
256
|
const setup = await ai.knowledge.setup()
|
|
223
257
|
if (setup.success) {
|
|
224
|
-
await ai.knowledge.ingest({ documentId: 'doc-001', content: markdownText, title: '产品手册' })
|
|
258
|
+
const ingested = await ai.knowledge.ingest({ documentId: 'doc-001', content: markdownText, title: '产品手册' })
|
|
259
|
+
if (!ingested.success)
|
|
260
|
+
return ingested
|
|
261
|
+
if (ingested.data.cleanupPending) {
|
|
262
|
+
const repaired = await ai.knowledge.repair({ offset: 0, limit: 100 })
|
|
263
|
+
if (!repaired.success)
|
|
264
|
+
return repaired
|
|
265
|
+
}
|
|
225
266
|
}
|
|
226
267
|
```
|
|
227
268
|
|
|
269
|
+
Knowledge は datapipe の単位と上限に従い、word は語数、その他は Unicode code point として清洗後テキストの区切りを保持します。AI の既定は markdown/maxSize=1500/overlap=200 のままです。maxSize を小さく上書きする際は継承する overlap もそれ未満にしてください。不正な分割設定・正規表現は embedding と版の公開前に KNOWLEDGE_INGEST_FAILED を返し、文書全体への無制限フォールバックは行いません。
|
|
270
|
+
|
|
228
271
|
#### 音声(Audio)
|
|
229
272
|
|
|
230
273
|
先に `ai.init()` で音声モデルを登録し、既定の認識・合成モデルを対応付けます。認証情報はプラットフォームの環境変数へフォールバックします。LLM と音声が同じ認証情報を使うと確認できた場合にのみ、`inheritLlmApiKey` を明示的に有効化してください:
|
|
@@ -332,6 +375,8 @@ return playable.data
|
|
|
332
375
|
|
|
333
376
|
ブラウザー・モバイルは `@h-ai/serv` の共通音声 WebSocket を使用します。`@h-ai/ai/client` は通信詳細を隠して Node と同じ `audio.*` API を提供します。クライアントは正常終了・キャンセル(`AUDIO_CANCELLED`)・異常切断(`AUDIO_CONNECTION_FAILED`)を区別し、キャンセルや `end` 前の切断で対応コードを throw します。`synthesize` は未完了の部分音声を成功として返しません。
|
|
334
377
|
|
|
378
|
+
Node の IndexTTS は同じ Undici バージョンの fetch・Agent・multipart 型を組み合わせます。モデル `timeout` は応答ヘッダーと本文の待機に適用し、利用者の取消は `AUDIO_CANCELLED`、期限超過は `AUDIO_TIMEOUT` です。成功・失敗・取消のいずれも専用接続を閉じ、区間単位ストリーミングのエラーは反復中に送出します。
|
|
379
|
+
|
|
335
380
|
> 自前の CPU/GPU モデルサービス(faster-whisper / IndexTTS / Qwen3-4B)、重み取得、オフライン化は [models/(中国語)](./models/README.md) を参照してください。イメージ・重みは ModelScope を優先し、HuggingFace ミラーへ自動フォールバックします。
|
|
336
381
|
|
|
337
382
|
#### 画像生成(Image)
|
|
@@ -364,8 +409,14 @@ if (result.success) {
|
|
|
364
409
|
|
|
365
410
|
`referenceImages` は省略可能です。公開層は画像バイト列と MIME のみを受け付け、multipart・`inlineData`・Data URL は Provider が変換します。認証情報はモデル項目、`HAI_AI_IMAGE_<PROVIDER>_APIKEY`、サービス環境変数で指定できます。Qwen・Seedream の一時 URL は Provider 内ですぐダウンロードし、公開戻り値に出しません。詳細は [REFERENCE.md(中国語)](./REFERENCE.md#image) を参照してください。
|
|
366
411
|
|
|
412
|
+
ダウンロードは認証情報を含まない既定ポート 443 の公開 HTTPS に限定します。socket の DNS 解決で全アドレスを検証し、同じ結果で接続します。IPv4/IPv6 のプライベート・ループバック・リンクローカル・変換/マップ・予約アドレスを拒否します。リダイレクトは最大 3 回で各段階を再検証し、独立接続に厂商認証ヘッダーやアプリ Cookie を渡しません。`baseUrl` は運用者が管理する信頼された設定で、厂商 API 自体のリダイレクトは拒否します。
|
|
413
|
+
|
|
414
|
+
`image.maxImageBytes` は出力/参照画像ごとに既定 20971520(20 MiB、最大 100 MiB)、`image.maxImages` は出力/参照画像数ごとに既定 4(最大 16)です。URL は順次取得し、解凍後を含む実測バイト数が上限を超えると応答をキャンセルします。JSON は `max(64 KiB, 2 × maxImageBytes)`、エラー本文は 64 KiB までです。モデル `timeout` は既定 120000ms で API・リダイレクト・全ダウンロードを含み、`signal` で全体をキャンセルできます。出力は PNG/JPEG/GIF/WebP の MIME とマジック値の一致を要求し、SVG は拒否します。完全な画像デコード・寸法検証は行いません。アドレス拒否・上限超過・タイムアウトはそれぞれ `IMAGE_DOWNLOAD_BLOCKED`・`IMAGE_TOO_LARGE`・`IMAGE_TIMEOUT` を返すため、success を確認してください。
|
|
415
|
+
|
|
367
416
|
#### Context 管理
|
|
368
417
|
|
|
418
|
+
自動記憶抽出は現在のノードのバックグラウンドで動作し、`pendingMemoryTasks=0` だけでは成功を示しません。`flush()` は当該バッチを待ち、例外と失敗 HaiResult を件数付き `MEMORY_EXTRACT_FAILED` として返します。同時の観測者は同じ結果を受け取り、戻り時点で報告を確認済みとし、次回は新規タスクだけを観測します。部分実行の可能性がある処理を自動再実行しません。`save()`・`reset({ waitForMemoryTasks: true })`・`consolidate()` は失敗時に後続の書き込みへ進まず、プロセス停止後の外部効果は別途確認します。
|
|
419
|
+
|
|
369
420
|
```ts
|
|
370
421
|
const manager = ai.context.createManager({
|
|
371
422
|
scope: { objectId: 'user-001', sessionId: 'sess-001' },
|
|
@@ -375,7 +426,9 @@ const manager = ai.context.createManager({
|
|
|
375
426
|
})
|
|
376
427
|
if (manager.success) {
|
|
377
428
|
const reply = await manager.data.chat('你好')
|
|
378
|
-
await manager.data.save()
|
|
429
|
+
const saved = await manager.data.save()
|
|
430
|
+
if (!saved.success)
|
|
431
|
+
return saved
|
|
379
432
|
// reset 生命周期完整:终止活动生成、清空消息/摘要/轮次,默认保留系统提示词
|
|
380
433
|
await manager.data.reset() // 可传 { preserveSystemPrompt, cancelActiveTurn, waitForMemoryTasks }
|
|
381
434
|
}
|
|
@@ -392,12 +445,15 @@ if (manager.success) {
|
|
|
392
445
|
`chatStream` は**モデル呼び出し前**にターン登録と `turn_started` を行います(`turn_started → delta* → done`、キャンセル時は `turn_started → delta* → cancelled`)。途中で `AbortSignal` により中止しても `turnId` と生成済みテキストを取得し、実際の内容を保存できます:
|
|
393
446
|
|
|
394
447
|
```ts
|
|
395
|
-
const
|
|
448
|
+
const created = ai.context.createManager({ scope: { objectId: 'user-001', sessionId: 'sess-001' }, turnCommit: 'manual' })
|
|
449
|
+
if (!created.success)
|
|
450
|
+
return created
|
|
451
|
+
const m = created.data
|
|
396
452
|
const controller = new AbortController()
|
|
397
453
|
|
|
398
454
|
for await (const ev of m.chatStream('请展开讲讲', { signal: controller.signal })) {
|
|
399
455
|
if (ev.type === 'turn_started') {
|
|
400
|
-
m.markTurnSpeaking(ev.turnId) // 可选:标记进入播放
|
|
456
|
+
await m.markTurnSpeaking(ev.turnId) // 可选:标记进入播放
|
|
401
457
|
}
|
|
402
458
|
else if (ev.type === 'delta') {
|
|
403
459
|
feedTts(ev.text) // 边生成边合成播放
|
|
@@ -450,6 +506,7 @@ await ai.persona.save({
|
|
|
450
506
|
// 组合出可直接喂给 ContextManager 的系统提示词(systemPrompt + traits)
|
|
451
507
|
const composed = await ai.persona.compose('xiaoq')
|
|
452
508
|
const manager = ai.context.createManager({
|
|
509
|
+
scope: { objectId: 'user-001', sessionId: 'persona-session' },
|
|
453
510
|
systemPrompt: composed.data,
|
|
454
511
|
memory: { enable: true, enableExtract: true, scope: { personaId: 'xiaoq' } },
|
|
455
512
|
})
|
|
@@ -457,6 +514,8 @@ const manager = ai.context.createManager({
|
|
|
457
514
|
|
|
458
515
|
`ai.persona` は `save` / `get` / `update` / `remove` / `list` / `compose` を提供します。プロフィールは全体で共有し、`objectId` では分離しません。
|
|
459
516
|
|
|
517
|
+
### 設定
|
|
518
|
+
|
|
460
519
|
```yaml
|
|
461
520
|
llm:
|
|
462
521
|
apiKey: ''
|
|
@@ -512,9 +571,17 @@ memory:
|
|
|
512
571
|
```
|
|
513
572
|
|
|
514
573
|
- **`native`(既定・推奨)**:HAI のネイティブエンジンは vecdb、reldb、LLM、Embedding を再利用します。`extract` は **Mem0 型の一括マージ**で、抽出した事実と既存記憶に 1 回の LLM 呼び出しで ADD / UPDATE / DELETE / NONE を決定し、増分更新・重複除去・矛盾削除・`category` 分類を実現します。`maxEntriesPerObject`、`maxEntriesGlobal`、`recencyDecay`、`embeddingEnabled`、`writebackRelatedTopK` が適用されます。削除は `objectId` ごとなので、ある主体の大量書き込みでほかの主体の記憶は失いません。scope の候補は `objectId` で絞り(≤ `maxEntriesPerObject`)、PostgreSQL はさらに JSONB 包含検索へ scope を渡して **GIN インデックス**を使います。SQLite/MySQL はメモリー照合で同じ結果を返します。
|
|
515
|
-
- **`mem0`(mem0ai/oss)**:`mem0ai/oss` の `Memory` エンジンを直接組み込み、クラウドを使いません。LLM/Embedder は `llm` 設定から取得し、`qdrant`
|
|
574
|
+
- **`mem0`(mem0ai/oss)**:`mem0ai/oss` の `Memory` エンジンを直接組み込み、クラウドを使いません。LLM/Embedder は `llm` 設定から取得し、`qdrant` は vecdb を再利用できます。現在のアダプターは完全な接続設定を持つ Qdrant のみをマッピングします。本番(`NODE_ENV` または `HAI_CORE_ENV=production`)では `memory.allowEphemeralFallback: true` でも実際のメモリーへの移行を拒否し、バックエンド未指定も失敗します。一時利用は開発または明示的な `HAI_E2E=1` / `HAI_E2E_MOCK=1` に限ります。履歴は既定で無効です。
|
|
516
575
|
|
|
517
|
-
両 Provider は同じ `ai.memory.*`(`extract` / `recall` / `injectMemories` / `add` / `update` / `get` / `remove` / `list` / `listPage` / `clear`)を提供し、`objectId` の主体分離と `{ topicId, personaId }` などの `scope` をサポートします。`recall` / `list` / `listPage` / `clear` は scope を厳密に絞り、`clear` に `types` / `scope` を渡すと同時一致のみを削除します。mem0 の `extract` は共通抽出器で分類・採点(`types` / `model` / `minImportance` / `systemPrompt`)後、`infer:false` で `hai_type` / `hai_importance` を保存します。`recall` も `types` と `recencyWeight` 減衰に対応し、native
|
|
576
|
+
両 Provider は同じ `ai.memory.*`(`extract` / `recall` / `injectMemories` / `add` / `update` / `get` / `remove` / `list` / `listPage` / `clear`)を提供し、`objectId` の主体分離と `{ topicId, personaId }` などの `scope` をサポートします。`recall` / `list` / `listPage` / `clear` は scope を厳密に絞り、`clear` に `types` / `scope` を渡すと同時一致のみを削除します。mem0 の `extract` は共通抽出器で分類・採点(`types` / `model` / `minImportance` / `systemPrompt`)後、`infer:false` で `hai_type` / `hai_importance` を保存します。`recall` も `types` と `recencyWeight` 減衰に対応し、native と同じ動作です。Mem0 はテキストとメタデータをその場で更新し、`id` と作成時刻を保持します。SDK の検索は `filters.user_id`、書き込みと主体全体の削除は `userId` を使います。SDK に一覧カーソルがないため、`list` / `listPage` / 条件付き `clear` は期限切れを含む主体候補 1000 件まで対応します。1 件余分に読み、超過は `MEMORY_LIST_INCOMPLETE`(906)で失敗します。不正確な total や一部のみの条件付き削除は行いません。候補が完全な場合、UTC の有効期限、型、scope で絞ってからページ分割します。主体全体の `deleteAll` は一覧上限の対象外です。公開 API の空条件 clear は拒否し、全主体の削除は信頼された管理者による明示的な `admin.clearAll({ confirm: true })` に限定します。
|
|
577
|
+
|
|
578
|
+
Mem0 3.3.1 は `QdrantClient.search` を呼び出すため、カタログと AI の optional peer は検証済みの `@qdrant/js-client-rest` `1.18.0` に固定します。起動時に公開メソッドを確認し、不整合は設定エラーにします。このメソッドを削除した 1.19 への上書きはできません。
|
|
579
|
+
|
|
580
|
+
アダプターは SDK の公開 `client` 設定から専用クライアントを渡します。Mem0 の Qdrant 書き込みと削除は `wait: true` を指定し、サーバーでの適用完了後に書き込み後の読み取りや次の削除バッチへ進みます。
|
|
581
|
+
|
|
582
|
+
Mem0 + pgvector は全環境で起動を拒否します。SDK が PostgreSQL 接続を私有オブジェクトに保持し、公開された close API がないためです。pgvector は `memory.provider: native` で利用でき、ネイティブの対応は維持します。`disableHistory: true` はリソースを持たない履歴実装を選び、履歴 SQLite ファイルを作成しません。ファクトリーは上限付きの公開読み取りでバックエンドの準備完了を待ってから成功を公開します。
|
|
583
|
+
|
|
584
|
+
SDK の追加応答には完全な業務フィールドがないため、`add` / `extract` は書き込み後に保存済みレコードを読み取ります。extract は全ての進行中の書き込み完了を待ちます。書き込みまたは再読み取り失敗時、`error.ext.requiresEffectReconciliation=true` と既知の ID の `memoryIds` を返します(応答消失時は空の場合もあります)。主体の実データを照合してから復旧し、add/extract を無条件に繰り返さないでください。自動再実行は行いません。
|
|
518
585
|
|
|
519
586
|
**候補集合と scope の検索漏れ**:`topK` で先に切り捨てると、別の話題・人格の関連性が高い記憶が対象 scope を押し出します。そのため `recall` / `injectMemories` は `topK × candidateMultiplier`(既定 5)件を取得してから絞り、`topK` 件にします。`topicId` + `personaId` など細かく分離する場合は `candidateMultiplier` を増やせます:
|
|
520
587
|
|
|
@@ -560,8 +627,8 @@ if (!result.success) {
|
|
|
560
627
|
- `hai:ai:600-701`:Retrieval/RAG。
|
|
561
628
|
- `hai:ai:800-805`:Knowledge。
|
|
562
629
|
- `hai:ai:050-059`:Audio。
|
|
563
|
-
- `hai:ai:060-
|
|
564
|
-
- `hai:ai:900-
|
|
630
|
+
- `hai:ai:060-068`:Image(ダウンロード拒否、リソース上限、全体タイムアウトを含む)。
|
|
631
|
+
- `hai:ai:900-906`:Memory。
|
|
565
632
|
- `hai:ai:980-984`:A2A。
|
|
566
633
|
|
|
567
634
|
## 開発とテスト
|
|
@@ -573,6 +640,8 @@ pnpm --filter @h-ai/ai test
|
|
|
573
640
|
pnpm --filter @h-ai/ai build
|
|
574
641
|
```
|
|
575
642
|
|
|
643
|
+
公開 JSDoc の例は実際のパッケージ入口から import し、内部ファクトリの例はモジュール内部コンテキストを明示して実宣言で型検証します。ルートで `pnpm check:jsdoc --package ai` を実行すると網羅性と例の型を検証できます。通常の業務 API は HaiResult を確認し、ストリームとブラウザー AudioClient は明示された例外契約に従います。
|
|
644
|
+
|
|
576
645
|
## ライセンス
|
|
577
646
|
|
|
578
647
|
Apache-2.0
|