@fllf/agent-sdk 0.1.2 → 0.1.3
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/CHANGELOG.md +169 -0
- package/README.md +824 -198
- package/dist/agent/Agent.d.ts +16 -5
- package/dist/agent/Agent.d.ts.map +1 -1
- package/dist/agent/Agent.js +283 -2
- package/dist/agent/Agent.js.map +1 -1
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/types.d.ts +27 -3
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/config/config.d.ts +22 -3
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +194 -47
- package/dist/config/config.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/errors.d.ts +74 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +186 -0
- package/dist/errors.js.map +1 -0
- package/dist/executors/base-executor.d.ts +14 -0
- package/dist/executors/base-executor.d.ts.map +1 -0
- package/dist/executors/base-executor.js +31 -0
- package/dist/executors/base-executor.js.map +1 -0
- package/dist/executors/base.d.ts +36 -5
- package/dist/executors/base.d.ts.map +1 -1
- package/dist/executors/chat-request-builder.d.ts +10 -0
- package/dist/executors/chat-request-builder.d.ts.map +1 -0
- package/dist/executors/chat-request-builder.js +96 -0
- package/dist/executors/chat-request-builder.js.map +1 -0
- package/dist/executors/index.d.ts +4 -1
- package/dist/executors/index.d.ts.map +1 -1
- package/dist/executors/index.js +6 -1
- package/dist/executors/index.js.map +1 -1
- package/dist/executors/rag-executor.js +1 -1
- package/dist/executors/rag-executor.js.map +1 -1
- package/dist/executors/simple-chat-executor.d.ts +4 -2
- package/dist/executors/simple-chat-executor.d.ts.map +1 -1
- package/dist/executors/simple-chat-executor.js +59 -57
- package/dist/executors/simple-chat-executor.js.map +1 -1
- package/dist/executors/tool-calling-executor.d.ts +20 -2
- package/dist/executors/tool-calling-executor.d.ts.map +1 -1
- package/dist/executors/tool-calling-executor.js +189 -91
- package/dist/executors/tool-calling-executor.js.map +1 -1
- package/dist/history/base.d.ts +55 -2
- package/dist/history/base.d.ts.map +1 -1
- package/dist/history/base.js +49 -0
- package/dist/history/base.js.map +1 -1
- package/dist/history/compression.d.ts +49 -0
- package/dist/history/compression.d.ts.map +1 -0
- package/dist/history/compression.js +53 -0
- package/dist/history/compression.js.map +1 -0
- package/dist/history/context-window.d.ts +33 -0
- package/dist/history/context-window.d.ts.map +1 -0
- package/dist/history/context-window.js +68 -0
- package/dist/history/context-window.js.map +1 -0
- package/dist/history/in-memory.d.ts +6 -4
- package/dist/history/in-memory.d.ts.map +1 -1
- package/dist/history/in-memory.js +25 -39
- package/dist/history/in-memory.js.map +1 -1
- package/dist/history/index.d.ts +9 -2
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +18 -1
- package/dist/history/index.js.map +1 -1
- package/dist/history/postgres/index.d.ts +4 -0
- package/dist/history/postgres/index.d.ts.map +1 -0
- package/dist/history/postgres/index.js +20 -0
- package/dist/history/postgres/index.js.map +1 -0
- package/dist/history/postgres/postgres-message-history-store.d.ts +35 -0
- package/dist/history/postgres/postgres-message-history-store.d.ts.map +1 -0
- package/dist/history/postgres/postgres-message-history-store.js +195 -0
- package/dist/history/postgres/postgres-message-history-store.js.map +1 -0
- package/dist/history/postgres/schema.d.ts +18 -0
- package/dist/history/postgres/schema.d.ts.map +1 -0
- package/dist/history/postgres/schema.js +46 -0
- package/dist/history/postgres/schema.js.map +1 -0
- package/dist/history/postgres/sql.d.ts +29 -0
- package/dist/history/postgres/sql.d.ts.map +1 -0
- package/dist/history/postgres/sql.js +102 -0
- package/dist/history/postgres/sql.js.map +1 -0
- package/dist/history/postgres/types.d.ts +20 -0
- package/dist/history/postgres/types.d.ts.map +1 -0
- package/dist/history/postgres/types.js +3 -0
- package/dist/history/postgres/types.js.map +1 -0
- package/dist/history/tool-pairing.d.ts +11 -0
- package/dist/history/tool-pairing.d.ts.map +1 -0
- package/dist/history/tool-pairing.js +52 -0
- package/dist/history/tool-pairing.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/LLM.d.ts +1 -0
- package/dist/llm/LLM.d.ts.map +1 -1
- package/dist/llm/LLM.js +136 -26
- package/dist/llm/LLM.js.map +1 -1
- package/dist/llm/errors.d.ts +11 -1
- package/dist/llm/errors.d.ts.map +1 -1
- package/dist/llm/errors.js +50 -1
- package/dist/llm/errors.js.map +1 -1
- package/dist/llm/factory.d.ts +14 -2
- package/dist/llm/factory.d.ts.map +1 -1
- package/dist/llm/factory.js +48 -9
- package/dist/llm/factory.js.map +1 -1
- package/dist/llm/index.d.ts +8 -3
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +10 -1
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/providers/anthropic.d.ts +14 -0
- package/dist/llm/providers/anthropic.d.ts.map +1 -0
- package/dist/llm/providers/anthropic.js +336 -0
- package/dist/llm/providers/anthropic.js.map +1 -0
- package/dist/llm/providers/openai-compatible.d.ts +7 -0
- package/dist/llm/providers/openai-compatible.d.ts.map +1 -1
- package/dist/llm/providers/openai-compatible.js +151 -47
- package/dist/llm/providers/openai-compatible.js.map +1 -1
- package/dist/llm/stream-accumulator.d.ts +17 -0
- package/dist/llm/stream-accumulator.d.ts.map +1 -0
- package/dist/llm/stream-accumulator.js +83 -0
- package/dist/llm/stream-accumulator.js.map +1 -0
- package/dist/llm/tool-arguments.d.ts +6 -0
- package/dist/llm/tool-arguments.d.ts.map +1 -0
- package/dist/llm/tool-arguments.js +20 -0
- package/dist/llm/tool-arguments.js.map +1 -0
- package/dist/llm/types.d.ts +76 -4
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/messages/content.d.ts +36 -0
- package/dist/messages/content.d.ts.map +1 -0
- package/dist/messages/content.js +70 -0
- package/dist/messages/content.js.map +1 -0
- package/dist/messages/index.d.ts +4 -2
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/index.js +10 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/messages/message.d.ts +11 -9
- package/dist/messages/message.d.ts.map +1 -1
- package/dist/messages/message.js +69 -4
- package/dist/messages/message.js.map +1 -1
- package/dist/messages/types.d.ts +12 -0
- package/dist/messages/types.d.ts.map +1 -1
- package/dist/observability/collecting-observer.d.ts +8 -0
- package/dist/observability/collecting-observer.d.ts.map +1 -0
- package/dist/observability/collecting-observer.js +15 -0
- package/dist/observability/collecting-observer.js.map +1 -0
- package/dist/observability/index.d.ts +1 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +3 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/observer.d.ts.map +1 -1
- package/dist/observability/observer.js +14 -1
- package/dist/observability/observer.js.map +1 -1
- package/dist/observability/types.d.ts +16 -1
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/rag/chunking/auto-chunker.d.ts +7 -0
- package/dist/rag/chunking/auto-chunker.d.ts.map +1 -1
- package/dist/rag/chunking/auto-chunker.js +13 -0
- package/dist/rag/chunking/auto-chunker.js.map +1 -1
- package/dist/rag/chunking/chunker.d.ts.map +1 -1
- package/dist/rag/chunking/chunker.js +29 -5
- package/dist/rag/chunking/chunker.js.map +1 -1
- package/dist/rag/chunking/index.d.ts +2 -0
- package/dist/rag/chunking/index.d.ts.map +1 -1
- package/dist/rag/chunking/index.js +2 -0
- package/dist/rag/chunking/index.js.map +1 -1
- package/dist/rag/chunking/markdown-chunker.d.ts.map +1 -1
- package/dist/rag/chunking/markdown-chunker.js.map +1 -1
- package/dist/rag/chunking/qa-pair-chunker.d.ts +23 -0
- package/dist/rag/chunking/qa-pair-chunker.d.ts.map +1 -0
- package/dist/rag/chunking/qa-pair-chunker.js +162 -0
- package/dist/rag/chunking/qa-pair-chunker.js.map +1 -0
- package/dist/rag/chunking/semantic-chunker.d.ts +19 -0
- package/dist/rag/chunking/semantic-chunker.d.ts.map +1 -0
- package/dist/rag/chunking/semantic-chunker.js +291 -0
- package/dist/rag/chunking/semantic-chunker.js.map +1 -0
- package/dist/rag/embeddings/embedder.d.ts.map +1 -1
- package/dist/rag/embeddings/embedder.js +6 -0
- package/dist/rag/embeddings/embedder.js.map +1 -1
- package/dist/rag/generation/context-builder.d.ts +7 -0
- package/dist/rag/generation/context-builder.d.ts.map +1 -1
- package/dist/rag/generation/context-builder.js +4 -1
- package/dist/rag/generation/context-builder.js.map +1 -1
- package/dist/rag/ingestion/metadata.d.ts +6 -1
- package/dist/rag/ingestion/metadata.d.ts.map +1 -1
- package/dist/rag/ingestion/metadata.js +6 -2
- package/dist/rag/ingestion/metadata.js.map +1 -1
- package/dist/rag/pipeline.d.ts.map +1 -1
- package/dist/rag/pipeline.js +34 -11
- package/dist/rag/pipeline.js.map +1 -1
- package/dist/rag/retrieval/index.d.ts +1 -0
- package/dist/rag/retrieval/index.d.ts.map +1 -1
- package/dist/rag/retrieval/index.js +1 -0
- package/dist/rag/retrieval/index.js.map +1 -1
- package/dist/rag/retrieval/parent-child-expanding-retriever.d.ts +31 -0
- package/dist/rag/retrieval/parent-child-expanding-retriever.d.ts.map +1 -0
- package/dist/rag/retrieval/parent-child-expanding-retriever.js +194 -0
- package/dist/rag/retrieval/parent-child-expanding-retriever.js.map +1 -0
- package/dist/rag/stores/in-memory-keyword-store.d.ts.map +1 -1
- package/dist/rag/stores/in-memory-keyword-store.js +3 -8
- package/dist/rag/stores/in-memory-keyword-store.js.map +1 -1
- package/dist/rag/stores/keyword-tokenizer.d.ts +4 -0
- package/dist/rag/stores/keyword-tokenizer.d.ts.map +1 -0
- package/dist/rag/stores/keyword-tokenizer.js +113 -0
- package/dist/rag/stores/keyword-tokenizer.js.map +1 -0
- package/dist/rag/stores/postgres/pg-vector-store.d.ts.map +1 -1
- package/dist/rag/stores/postgres/pg-vector-store.js +21 -8
- package/dist/rag/stores/postgres/pg-vector-store.js.map +1 -1
- package/dist/rag/stores/postgres/postgres-document-store.d.ts.map +1 -1
- package/dist/rag/stores/postgres/postgres-document-store.js +26 -13
- package/dist/rag/stores/postgres/postgres-document-store.js.map +1 -1
- package/dist/rag/stores/postgres/postgres-keyword-store.d.ts.map +1 -1
- package/dist/rag/stores/postgres/postgres-keyword-store.js +43 -43
- package/dist/rag/stores/postgres/postgres-keyword-store.js.map +1 -1
- package/dist/rag/stores/postgres/sql.d.ts +22 -0
- package/dist/rag/stores/postgres/sql.d.ts.map +1 -1
- package/dist/rag/stores/postgres/sql.js +42 -0
- package/dist/rag/stores/postgres/sql.js.map +1 -1
- package/dist/rag/stores/types.d.ts +5 -0
- package/dist/rag/stores/types.d.ts.map +1 -1
- package/dist/rag/types.d.ts +6 -0
- package/dist/rag/types.d.ts.map +1 -1
- package/dist/testing/agent.d.ts +11 -0
- package/dist/testing/agent.d.ts.map +1 -0
- package/dist/testing/agent.js +45 -0
- package/dist/testing/agent.js.map +1 -0
- package/dist/testing/history-contract.d.ts +12 -0
- package/dist/testing/history-contract.d.ts.map +1 -0
- package/dist/testing/history-contract.js +111 -0
- package/dist/testing/history-contract.js.map +1 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/models.d.ts +34 -0
- package/dist/testing/models.d.ts.map +1 -0
- package/dist/testing/models.js +74 -0
- package/dist/testing/models.js.map +1 -0
- package/dist/testing/tool-schema.d.ts +6 -0
- package/dist/testing/tool-schema.d.ts.map +1 -0
- package/dist/testing/tool-schema.js +29 -0
- package/dist/testing/tool-schema.js.map +1 -0
- package/dist/testing/tools.d.ts +21 -0
- package/dist/testing/tools.d.ts.map +1 -0
- package/dist/testing/tools.js +43 -0
- package/dist/testing/tools.js.map +1 -0
- package/dist/tools/base.d.ts +38 -7
- package/dist/tools/base.d.ts.map +1 -1
- package/dist/tools/base.js +238 -11
- package/dist/tools/base.js.map +1 -1
- package/dist/tools/builtin/advancedSearchTool.d.ts.map +1 -1
- package/dist/tools/builtin/advancedSearchTool.js +30 -4
- package/dist/tools/builtin/advancedSearchTool.js.map +1 -1
- package/dist/tools/builtin/ragSearchTool.d.ts +6 -3
- package/dist/tools/builtin/ragSearchTool.d.ts.map +1 -1
- package/dist/tools/builtin/ragSearchTool.js +8 -6
- package/dist/tools/builtin/ragSearchTool.js.map +1 -1
- package/dist/tools/executor.d.ts +30 -2
- package/dist/tools/executor.d.ts.map +1 -1
- package/dist/tools/executor.js +83 -15
- package/dist/tools/executor.js.map +1 -1
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/registry.d.ts +15 -3
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +21 -2
- package/dist/tools/registry.js.map +1 -1
- package/package.json +58 -4
package/README.md
CHANGED
|
@@ -1,73 +1,34 @@
|
|
|
1
1
|
# FAgent Agent SDK
|
|
2
2
|
|
|
3
|
-
FAgent
|
|
4
|
-
workflows, and RAG-powered chat systems.
|
|
3
|
+
FAgent Agent SDK 是一个可组合的 TypeScript Agent 运行时基础库,用于在第三方系统中接入:
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
- LLM 对话与流式输出。
|
|
6
|
+
- OpenAI、Anthropic、OpenAI-compatible 本地/网关模型。
|
|
7
|
+
- 工具调用 Agent,含并发工具执行、工具错误回传、工具调用流式事件。
|
|
8
|
+
- 多模态消息内容:文本、图片 URL、图片 base64。
|
|
9
|
+
- 会话级消息历史、上下文窗口截断、可选历史摘要压缩。
|
|
10
|
+
- RAG 文档 ingestion、切块、embedding、检索、rerank、上下文构建、答案生成与引用校验。
|
|
11
|
+
- 内存存储与 Postgres/pgvector 参考存储。
|
|
12
|
+
- 运行时事件、错误类型与下游测试辅助工具。
|
|
13
|
+
|
|
14
|
+
本 SDK 只提供运行时与扩展点;不包含 Web UI、用户体系、鉴权、业务数据库迁移、部署脚本或产品级限流。
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
7
17
|
|
|
8
18
|
```bash
|
|
9
19
|
npm install @fllf/agent-sdk
|
|
10
20
|
```
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- Composable `Agent` runtime.
|
|
19
|
-
- Unified `LLM.chat()` interface.
|
|
20
|
-
- OpenAI and OpenAI-compatible local providers.
|
|
21
|
-
- Streaming through `LLM.stream()`.
|
|
22
|
-
- Timeout, retry, abort, and normalized LLM errors.
|
|
23
|
-
- `generateObject()` structured output with Zod validation.
|
|
24
|
-
- Session-scoped message history.
|
|
25
|
-
- Simple chat, tool-calling, and RAG executors.
|
|
26
|
-
- Zod-driven tool definitions.
|
|
27
|
-
- RAG ingestion, chunking, embedding, retrieval, reranking, context building,
|
|
28
|
-
generation, and verification.
|
|
29
|
-
- In-memory stores for development and tests.
|
|
30
|
-
- Postgres RAG store adapters that work with any compatible `query()` client.
|
|
31
|
-
- Observer events for logging, tracing, and debugging.
|
|
32
|
-
|
|
33
|
-
## Runtime requirements
|
|
34
|
-
|
|
35
|
-
- Node.js 18 or newer.
|
|
36
|
-
- TypeScript is recommended for typed integration.
|
|
37
|
-
- Install optional document conversion dependencies only if you use those
|
|
38
|
-
loaders:
|
|
22
|
+
运行要求:
|
|
23
|
+
|
|
24
|
+
- Node.js 18 或更高版本。
|
|
25
|
+
- 推荐使用 TypeScript。
|
|
26
|
+
- RAG 文档转换按需安装可选依赖:
|
|
39
27
|
- HTML: `turndown`, `turndown-plugin-gfm`
|
|
40
28
|
- DOCX: `mammoth`
|
|
41
29
|
- PDF: `pdf-parse`
|
|
42
30
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
OpenAI:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
OPENAI_API_KEY=...
|
|
49
|
-
OPENAI_MODEL=gpt-4o-mini
|
|
50
|
-
OPENAI_BASE_URL=https://api.openai.com/v1
|
|
51
|
-
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
OpenAI-compatible local runtime, such as Ollama, LM Studio, vLLM, or a gateway:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
LOCAL_BASE_URL=http://127.0.0.1:11434/v1
|
|
58
|
-
LOCAL_MODEL=llama3
|
|
59
|
-
LOCAL_API_KEY=local-key
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
If no OpenAI key is configured, the default `LLM` auto-detection falls back to
|
|
63
|
-
the local OpenAI-compatible provider.
|
|
64
|
-
|
|
65
|
-
`OPENAI_BASE_URL` also applies to `OpenAIEmbedder`. For OpenAI-compatible
|
|
66
|
-
embedding providers, configure it as the API root, for example
|
|
67
|
-
`https://gateway.example.test/v1`; the SDK posts embedding requests to
|
|
68
|
-
`{OPENAI_BASE_URL}/embeddings`.
|
|
69
|
-
|
|
70
|
-
## Basic chat integration
|
|
31
|
+
## 快速开始
|
|
71
32
|
|
|
72
33
|
```ts
|
|
73
34
|
import { Agent } from '@fllf/agent-sdk';
|
|
@@ -87,13 +48,10 @@ const result = await agent.run('How do I reset my password?', {
|
|
|
87
48
|
});
|
|
88
49
|
|
|
89
50
|
console.log(result.output);
|
|
51
|
+
console.log(result.usage);
|
|
90
52
|
```
|
|
91
53
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Your API layer owns authentication, authorization, persistence, rate limits, and
|
|
95
|
-
request validation. A typical chat endpoint can adapt incoming requests to
|
|
96
|
-
`Agent.run()`:
|
|
54
|
+
第三方 HTTP 服务通常只需要把自己的认证、租户、会话和用户输入转换为 `Agent.run()`:
|
|
97
55
|
|
|
98
56
|
```ts
|
|
99
57
|
import { Agent } from '@fllf/agent-sdk';
|
|
@@ -103,10 +61,10 @@ const agent = new Agent({
|
|
|
103
61
|
requireSessionId: true,
|
|
104
62
|
});
|
|
105
63
|
|
|
106
|
-
export async function
|
|
107
|
-
conversationId: string;
|
|
108
|
-
userId: string;
|
|
64
|
+
export async function handleChat(body: {
|
|
109
65
|
tenantId: string;
|
|
66
|
+
userId: string;
|
|
67
|
+
conversationId: string;
|
|
110
68
|
message: string;
|
|
111
69
|
}) {
|
|
112
70
|
const sessionId = `${body.tenantId}:${body.userId}:${body.conversationId}`;
|
|
@@ -116,6 +74,7 @@ export async function handleChatRequest(body: {
|
|
|
116
74
|
metadata: {
|
|
117
75
|
tenantId: body.tenantId,
|
|
118
76
|
userId: body.userId,
|
|
77
|
+
conversationId: body.conversationId,
|
|
119
78
|
},
|
|
120
79
|
});
|
|
121
80
|
|
|
@@ -126,54 +85,269 @@ export async function handleChatRequest(body: {
|
|
|
126
85
|
}
|
|
127
86
|
```
|
|
128
87
|
|
|
129
|
-
|
|
88
|
+
建议第三方系统自己负责:
|
|
89
|
+
|
|
90
|
+
- 登录态、租户边界、权限校验。
|
|
91
|
+
- 外部 API 输入校验与限流。
|
|
92
|
+
- 持久化用户、会话、消息、审计日志。
|
|
93
|
+
- 日志脱敏与成本控制。
|
|
94
|
+
|
|
95
|
+
## 模型与环境变量
|
|
96
|
+
|
|
97
|
+
`LLM` 默认自动探测 provider:
|
|
98
|
+
|
|
99
|
+
1. 存在 `OPENAI_API_KEY` 时使用 `openai`。
|
|
100
|
+
2. 否则存在 `ANTHROPIC_API_KEY` 时使用 `anthropic`。
|
|
101
|
+
3. 否则使用 `local`,即 OpenAI-compatible 本地服务。
|
|
102
|
+
|
|
103
|
+
### OpenAI
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
OPENAI_API_KEY=...
|
|
107
|
+
OPENAI_MODEL=gpt-4o-mini
|
|
108
|
+
OPENAI_BASE_URL=https://api.openai.com/v1
|
|
109
|
+
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
官方 OpenAI provider 会把 `maxTokens` 映射为 `max_completion_tokens`,流式请求会启用 usage 回传。
|
|
113
|
+
`OpenAIEmbedder` 也会读取 `OPENAI_API_KEY`、`OPENAI_BASE_URL` 和 `OPENAI_EMBEDDING_MODEL`;
|
|
114
|
+
兼容服务应把 `OPENAI_BASE_URL` 配为 API root,SDK 会请求 `{baseURL}/embeddings`。
|
|
115
|
+
|
|
116
|
+
### Anthropic
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
ANTHROPIC_API_KEY=...
|
|
120
|
+
ANTHROPIC_MODEL=claude-opus-4-8
|
|
121
|
+
ANTHROPIC_BASE_URL=
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Anthropic provider 支持 Messages API、多模态、工具调用、流式输出、结构化输出、reasoning effort、prompt cache usage 字段和 `tool_result.is_error`。
|
|
125
|
+
|
|
126
|
+
### 本地或网关 OpenAI-compatible 服务
|
|
127
|
+
|
|
128
|
+
适用于 Ollama、LM Studio、vLLM 或统一网关:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
LOCAL_BASE_URL=http://127.0.0.1:11434/v1
|
|
132
|
+
LOCAL_MODEL=llama3
|
|
133
|
+
LOCAL_API_KEY=local-key
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### SDK 运行参数
|
|
137
|
+
|
|
138
|
+
`Config.fromEnv()` 支持:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
MODEL=
|
|
142
|
+
MODEL_PROVIDER=
|
|
143
|
+
TEMPERATURE=
|
|
144
|
+
MAX_TOKENS=
|
|
145
|
+
MAX_INPUT_TOKENS=
|
|
146
|
+
REQUEST_TIMEOUT_MS=
|
|
147
|
+
MAX_RETRIES=
|
|
148
|
+
RETRY_DELAY_MS=
|
|
149
|
+
RETRY_MAX_DELAY_MS=
|
|
150
|
+
MAX_HISTORY_LENGTH=
|
|
151
|
+
DEBUG=
|
|
152
|
+
LOG_LEVEL=
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
示例:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { Config } from '@fllf/agent-sdk';
|
|
159
|
+
|
|
160
|
+
const config = Config.fromEnv({
|
|
161
|
+
onWarning: (warning) => logger.warn({ warning }, 'invalid SDK env var'),
|
|
162
|
+
});
|
|
130
163
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"conversationId": "conversation_1001",
|
|
134
|
-
"userId": "user_42",
|
|
135
|
-
"tenantId": "tenant_1",
|
|
136
|
-
"message": "How do I reset my password?"
|
|
164
|
+
if (config.envWarnings.length > 0) {
|
|
165
|
+
// 按业务策略决定继续启动或失败退出。
|
|
137
166
|
}
|
|
138
167
|
```
|
|
139
168
|
|
|
140
|
-
|
|
169
|
+
`Config.fromEnv({ strict: true })` 在环境变量非法时抛出 `ConfigError`。SDK 不会自动打印非法值。
|
|
170
|
+
|
|
171
|
+
注意:`temperature` 没有默认值;未显式配置时不下发,让模型/provider 使用自身默认值。
|
|
172
|
+
|
|
173
|
+
## Agent 核心用法
|
|
174
|
+
|
|
175
|
+
### 构造参数
|
|
176
|
+
|
|
177
|
+
常用 `Agent` 组件:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import {
|
|
181
|
+
Agent,
|
|
182
|
+
Config,
|
|
183
|
+
InMemoryMessageHistoryStore,
|
|
184
|
+
SimpleChatExecutor,
|
|
185
|
+
} from '@fllf/agent-sdk';
|
|
186
|
+
|
|
187
|
+
const agent = new Agent({
|
|
188
|
+
name: 'agent-name',
|
|
189
|
+
llmConfig: { provider: 'openai', model: 'gpt-4o-mini' },
|
|
190
|
+
config: new Config({
|
|
191
|
+
maxHistoryLength: 100,
|
|
192
|
+
maxInputTokens: 8000,
|
|
193
|
+
requestTimeoutMs: 60_000,
|
|
194
|
+
maxRetries: 2,
|
|
195
|
+
}),
|
|
196
|
+
historyStore: new InMemoryMessageHistoryStore(),
|
|
197
|
+
executor: new SimpleChatExecutor(),
|
|
198
|
+
systemPrompt: 'You are helpful.',
|
|
199
|
+
requireSessionId: true,
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
可替换组件包括:
|
|
204
|
+
|
|
205
|
+
- `llm`: 任意实现 `ChatModel` 的模型适配器。
|
|
206
|
+
- `historyStore`: 任意实现 `MessageHistoryStore` 的历史存储。
|
|
207
|
+
- `executor`: `SimpleChatExecutor`、`ToolCallingExecutor`、`RagExecutor` 或自定义 executor。
|
|
208
|
+
- `tools`: `Tool[]` 或 `ToolRegistry`。
|
|
209
|
+
- `observer`: 运行时事件接收器。
|
|
210
|
+
- `contextCompressor`: 超出 `maxInputTokens` 时的历史压缩器。
|
|
211
|
+
- `hooks`: `beforeRun`、`afterRun`、`onRunError` 生命周期钩子。
|
|
212
|
+
- `runIdFactory`: 自定义 run id 生成器。
|
|
213
|
+
|
|
214
|
+
### run 选项与返回值
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const result = await agent.run('hello', {
|
|
218
|
+
sessionId: 'tenant:user:conversation',
|
|
219
|
+
runId: 'optional-fixed-run-id',
|
|
220
|
+
model: 'gpt-4o-mini',
|
|
221
|
+
temperature: 0.2,
|
|
222
|
+
maxTokens: 1024,
|
|
223
|
+
timeoutMs: 30_000,
|
|
224
|
+
maxRetries: 1,
|
|
225
|
+
systemPrompt: 'Override this run only.',
|
|
226
|
+
signal: abortController.signal,
|
|
227
|
+
metadata: { tenantId: 'tenant_1' },
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`AgentRunOptions` 会在执行前校验;非法数值、空 `runId` 或非普通对象 `metadata` 会抛 `AgentExecutionError`,code 为 `invalid_agent_run_options`。
|
|
232
|
+
|
|
233
|
+
返回值:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
type AgentResult = {
|
|
237
|
+
output: string;
|
|
238
|
+
messages: Message[];
|
|
239
|
+
usage?: TokenUsage;
|
|
240
|
+
raw?: unknown;
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`raw` 由 executor 决定:
|
|
245
|
+
|
|
246
|
+
- simple chat: provider 原始响应。
|
|
247
|
+
- tool calling: 最近一次模型原始响应。
|
|
248
|
+
- RAG: `RagAnswer`。
|
|
249
|
+
|
|
250
|
+
### 会话与历史
|
|
251
|
+
|
|
252
|
+
`Agent` 实例可复用。`sessionId` 决定会话上下文:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
await agent.run('Hi', { sessionId: 'tenant_1:user_1:conv_a' });
|
|
256
|
+
await agent.run('New conversation', { sessionId: 'tenant_1:user_1:conv_b' });
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
同一 `sessionId` 的 run/stream/add/clear/get 操作在进程内串行化,避免消息顺序错乱。不同 session 可并发。
|
|
141
260
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
261
|
+
生产接入建议:
|
|
262
|
+
|
|
263
|
+
- 设置 `requireSessionId: true`。
|
|
264
|
+
- session id 由租户、用户、会话 id 共同组成。
|
|
265
|
+
- 开发/测试可使用内存历史;生产应接入持久化 `MessageHistoryStore`。
|
|
266
|
+
|
|
267
|
+
## 流式输出
|
|
268
|
+
|
|
269
|
+
`Agent.stream()` 返回 `AsyncIterable<AgentStreamEvent>`。
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const agent = new Agent({ name: 'streaming-chat' });
|
|
273
|
+
|
|
274
|
+
for await (const event of agent.stream('Write a short summary.', {
|
|
275
|
+
sessionId: 'tenant_1:user_42:conversation_stream',
|
|
276
|
+
})) {
|
|
277
|
+
if (event.type === 'agent:text_delta') {
|
|
278
|
+
process.stdout.write(event.delta ?? '');
|
|
279
|
+
}
|
|
280
|
+
if (event.type === 'agent:end') {
|
|
281
|
+
console.log('\nfinal:', event.output);
|
|
149
282
|
}
|
|
150
283
|
}
|
|
151
284
|
```
|
|
152
285
|
|
|
153
|
-
|
|
286
|
+
事件类型:
|
|
287
|
+
|
|
288
|
+
- `agent:start`
|
|
289
|
+
- `agent:text_delta`
|
|
290
|
+
- `agent:tool_call`
|
|
291
|
+
- `agent:tool_result`
|
|
292
|
+
- `agent:end`
|
|
293
|
+
- `agent:error`
|
|
294
|
+
|
|
295
|
+
流式行为:
|
|
296
|
+
|
|
297
|
+
- `SimpleChatExecutor` 支持流式,但要求模型实现 `stream()` 并声明 streaming capability。
|
|
298
|
+
- `ToolCallingExecutor` 支持工具调用流式;模型支持流式时聚合 tool call delta,模型不支持流式时会退化为一次非流式 `chat()` 并整体输出。
|
|
299
|
+
- `RagExecutor` 当前只实现非流式 `run()`。
|
|
300
|
+
- 流式成功完成后才提交本轮 user/assistant 消息;中途失败或 abort 时不会提交未完成的 simple-chat 轮次。
|
|
154
301
|
|
|
155
|
-
|
|
302
|
+
工具调用流式会额外产生:
|
|
156
303
|
|
|
157
304
|
```ts
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
305
|
+
if (event.type === 'agent:tool_call') {
|
|
306
|
+
console.log(event.toolCall);
|
|
307
|
+
}
|
|
308
|
+
if (event.type === 'agent:tool_result') {
|
|
309
|
+
console.log(event.toolResult?.isError, event.toolResult?.content);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 多模态消息
|
|
314
|
+
|
|
315
|
+
消息内容类型为:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
type MessageContent = string | ContentBlock[];
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
示例:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { Agent, imageUrlBlock, textBlock } from '@fllf/agent-sdk';
|
|
325
|
+
|
|
326
|
+
await agent.run([
|
|
327
|
+
textBlock('Describe this image.'),
|
|
328
|
+
imageUrlBlock('https://example.test/image.png'),
|
|
329
|
+
], {
|
|
330
|
+
sessionId: 'tenant:user:conv',
|
|
161
331
|
});
|
|
162
332
|
```
|
|
163
333
|
|
|
164
|
-
|
|
165
|
-
are serialized so message order remains stable.
|
|
334
|
+
也可使用 base64:
|
|
166
335
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
336
|
+
```ts
|
|
337
|
+
import { imageBase64Block } from '@fllf/agent-sdk';
|
|
338
|
+
|
|
339
|
+
const image = imageBase64Block('image/png', base64Data);
|
|
340
|
+
```
|
|
171
341
|
|
|
172
|
-
|
|
342
|
+
Provider 行为:
|
|
173
343
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
344
|
+
- OpenAI/OpenAI-compatible:user 多模态映射为 `image_url`;assistant/tool 非文本内容会降级为文本占位。
|
|
345
|
+
- Anthropic:user 图片映射为 Anthropic image block;tool 结果可携带 `is_error`。
|
|
346
|
+
- 不支持 vision 的 provider 会在 capability 检查或 provider 侧失败;第三方系统应按模型能力选择输入。
|
|
347
|
+
|
|
348
|
+
## 工具调用
|
|
349
|
+
|
|
350
|
+
工具通过 Zod schema 定义。SDK 会把 schema 转成 provider-neutral tool definition,并在执行前校验模型传入参数。
|
|
177
351
|
|
|
178
352
|
```ts
|
|
179
353
|
import { z } from 'zod';
|
|
@@ -190,6 +364,8 @@ class AddTool extends Tool<typeof AddToolSchema, number> {
|
|
|
190
364
|
name: 'add',
|
|
191
365
|
description: 'Adds two numbers.',
|
|
192
366
|
schema: AddToolSchema,
|
|
367
|
+
timeoutMs: 5000,
|
|
368
|
+
errorMode: 'return_to_model',
|
|
193
369
|
});
|
|
194
370
|
}
|
|
195
371
|
|
|
@@ -200,7 +376,7 @@ class AddTool extends Tool<typeof AddToolSchema, number> {
|
|
|
200
376
|
|
|
201
377
|
const agent = new Agent({
|
|
202
378
|
name: 'tool-agent',
|
|
203
|
-
executor: new ToolCallingExecutor(),
|
|
379
|
+
executor: new ToolCallingExecutor({ maxIterations: 5 }),
|
|
204
380
|
tools: [new AddTool()],
|
|
205
381
|
requireSessionId: true,
|
|
206
382
|
});
|
|
@@ -208,25 +384,252 @@ const agent = new Agent({
|
|
|
208
384
|
const result = await agent.run('Calculate 19 + 23 with the tool.', {
|
|
209
385
|
sessionId: 'tenant_1:user_42:conversation_tools',
|
|
210
386
|
});
|
|
387
|
+
```
|
|
211
388
|
|
|
212
|
-
|
|
389
|
+
工具运行控制:
|
|
390
|
+
|
|
391
|
+
- `timeoutMs`: 工具超时后抛 `ToolTimeoutError`。
|
|
392
|
+
- `AgentRunOptions.signal`: 会传入 `ToolRunContext.signal`。
|
|
393
|
+
- `ToolRunContext`: 包含 `runId`、`agentName`、`sessionId`、`metadata`、`signal`。
|
|
394
|
+
- `canExecute(context)`: 工具级权限策略,返回 `false` 时抛 `ToolPermissionError`。
|
|
395
|
+
- `errorMode`: 默认 `return_to_model`,工具失败会作为 `isError: true` 的工具结果回传给模型;设为 `throw` 会中止本次 Agent run。
|
|
396
|
+
- `serializeResult`: 可自定义工具结果如何转成 `MessageContent`。默认规则:字符串和 content block 原样返回,`undefined` 返回空字符串,其余值 `JSON.stringify()`。
|
|
397
|
+
|
|
398
|
+
工具名必须符合 OpenAI-compatible function name 要求:1-64 个字符,仅字母、数字、下划线或连字符。
|
|
399
|
+
|
|
400
|
+
`ToolRegistry` 默认拒绝重复工具名:
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
import { ToolRegistry } from '@fllf/agent-sdk';
|
|
404
|
+
|
|
405
|
+
const registry = new ToolRegistry();
|
|
406
|
+
registry.register(tool);
|
|
407
|
+
registry.register(nextTool, { replace: true });
|
|
213
408
|
```
|
|
214
409
|
|
|
215
|
-
|
|
410
|
+
`ToolCallingExecutor` 关键行为:
|
|
411
|
+
|
|
412
|
+
- 单轮模型返回多个 tool call 时并发执行,结果顺序保持与模型请求一致。
|
|
413
|
+
- 默认 `onMaxIterations: 'finalize'`:达到迭代上限后追加一轮不带工具的模型请求,让模型给出文字收尾。
|
|
414
|
+
- 可设置 `onMaxIterations: 'throw'` 恢复超限即抛错;已执行轨迹仍会写入 history。
|
|
415
|
+
|
|
416
|
+
## 直接使用 LLM
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
import { LLM } from '@fllf/agent-sdk';
|
|
216
420
|
|
|
217
|
-
|
|
218
|
-
|
|
421
|
+
const llm = new LLM({
|
|
422
|
+
provider: 'anthropic',
|
|
423
|
+
model: 'claude-opus-4-8',
|
|
424
|
+
});
|
|
219
425
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
426
|
+
const response = await llm.chat({
|
|
427
|
+
messages: [{ role: 'user', content: 'Say hello.' }],
|
|
428
|
+
maxTokens: 256,
|
|
429
|
+
});
|
|
224
430
|
|
|
225
|
-
|
|
431
|
+
console.log(response.content);
|
|
432
|
+
```
|
|
226
433
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
434
|
+
流式:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
for await (const chunk of llm.stream({
|
|
438
|
+
messages: [{ role: 'user', content: 'Stream a short answer.' }],
|
|
439
|
+
})) {
|
|
440
|
+
process.stdout.write(chunk.contentDelta ?? '');
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
结构化输出:
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
import { z } from 'zod';
|
|
448
|
+
import { LLM } from '@fllf/agent-sdk';
|
|
449
|
+
|
|
450
|
+
const llm = new LLM();
|
|
451
|
+
|
|
452
|
+
const result = await llm.generateObject({
|
|
453
|
+
messages: [{ role: 'user', content: 'Return a short project summary.' }],
|
|
454
|
+
schema: z.object({
|
|
455
|
+
summary: z.string(),
|
|
456
|
+
confidence: z.number(),
|
|
457
|
+
}),
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
console.log(result.value);
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
高级请求字段:
|
|
464
|
+
|
|
465
|
+
- `tools`: provider-neutral `ToolDefinition[]`。
|
|
466
|
+
- `toolChoice`: `'auto' | 'none' | 'required' | { type: 'tool'; name: string }`。
|
|
467
|
+
- `reasoning: { effort }`: OpenAI 映射为 `reasoning_effort`,Anthropic 映射为 `output_config.effort`。
|
|
468
|
+
- `ChatMessage.cacheControl: 'ephemeral'`: Anthropic prompt cache 断点;其他 provider 忽略。
|
|
469
|
+
- `responseFormat`: JSON schema 结构化输出。
|
|
470
|
+
|
|
471
|
+
`ChatStreamAccumulator` 可把 provider 的 `ChatStreamChunk` 聚合成完整 `ChatResponse`:
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
import { ChatStreamAccumulator } from '@fllf/agent-sdk';
|
|
475
|
+
|
|
476
|
+
const acc = new ChatStreamAccumulator();
|
|
477
|
+
for await (const chunk of llm.stream(request)) {
|
|
478
|
+
acc.add(chunk);
|
|
479
|
+
}
|
|
480
|
+
const response = acc.toResponse();
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## 自定义模型 provider
|
|
484
|
+
|
|
485
|
+
全局注册:
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
import { LLM, ModelProviderFactory } from '@fllf/agent-sdk';
|
|
489
|
+
|
|
490
|
+
ModelProviderFactory.register('tenant-provider', (config) => (
|
|
491
|
+
new TenantProvider(config)
|
|
492
|
+
));
|
|
493
|
+
|
|
494
|
+
const llm = new LLM({ provider: 'tenant-provider' });
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
多租户、测试或插件系统建议使用隔离 registry,避免全局污染:
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
import { LLM, ModelProviderRegistry } from '@fllf/agent-sdk';
|
|
501
|
+
|
|
502
|
+
const registry = new ModelProviderRegistry({
|
|
503
|
+
includeDefaultProviders: false,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
registry.register('tenant-provider', (config) => new TenantProvider(config));
|
|
507
|
+
|
|
508
|
+
const llm = new LLM({
|
|
509
|
+
provider: 'tenant-provider',
|
|
510
|
+
providerRegistry: registry,
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
自定义 provider 需要实现 `ChatModel`,并正确声明 `capabilities()`。SDK 会在调用前校验 tool calling、structured output、streaming 等能力,不支持时抛 `LLMUnsupportedCapabilityError`。
|
|
515
|
+
|
|
516
|
+
## 消息历史与持久化
|
|
517
|
+
|
|
518
|
+
`Message.toJSON()` 是推荐持久化格式;读取时用 `Message.fromJSON(row)` 或 `reviveMessage(row)`。
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
type MessageJSON = {
|
|
522
|
+
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
523
|
+
content: string | ContentBlock[];
|
|
524
|
+
timestamp: string;
|
|
525
|
+
metadata: Record<string, unknown>;
|
|
526
|
+
};
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
读取窗口:
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
const recent = await history.get({ limit: 20 });
|
|
533
|
+
const before = await history.get({
|
|
534
|
+
before: '2026-01-01T00:00:00.000Z',
|
|
535
|
+
limit: 20,
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
`before` 是排他的时间游标;`limit` 返回过滤后的最近 N 条消息。窗口截断会修复开头孤儿 tool 上下文,避免 provider 400。
|
|
540
|
+
|
|
541
|
+
### Postgres 消息历史
|
|
542
|
+
|
|
543
|
+
适用于任何提供 `query(sql, params)` 的对象,例如 `pg.Pool`。SDK 不依赖 `pg` 包,也不依赖事务 API;原子写路径使用单条 SQL。
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
import {
|
|
547
|
+
createPostgresMessageHistorySchemaSql,
|
|
548
|
+
PostgresMessageHistoryStore,
|
|
549
|
+
} from '@fllf/agent-sdk';
|
|
550
|
+
|
|
551
|
+
await db.query(createPostgresMessageHistorySchemaSql({
|
|
552
|
+
tablePrefix: 'agent_history',
|
|
553
|
+
}));
|
|
554
|
+
|
|
555
|
+
const historyStore = new PostgresMessageHistoryStore({
|
|
556
|
+
db,
|
|
557
|
+
tablePrefix: 'agent_history',
|
|
558
|
+
});
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
多实例共享同一个历史库时,可使用乐观并发:
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
const history = historyStore.get(sessionId);
|
|
565
|
+
const version = await history.getVersion?.();
|
|
566
|
+
|
|
567
|
+
await history.addMany(messages, {
|
|
568
|
+
expectedVersion: version,
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
版本冲突抛 `HistoryVersionConflictError`,`retryable: true`。典型处理方式是重新读取历史和版本后重试。
|
|
573
|
+
|
|
574
|
+
## 上下文窗口与历史压缩
|
|
575
|
+
|
|
576
|
+
配置 `maxInputTokens` 后,executor 会从最新消息向前保留,超预算时截断较旧消息。估算器为轻量启发式,不用于计费。
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
import {
|
|
580
|
+
Agent,
|
|
581
|
+
Config,
|
|
582
|
+
SummarizingContextCompressor,
|
|
583
|
+
} from '@fllf/agent-sdk';
|
|
584
|
+
|
|
585
|
+
const agent = new Agent({
|
|
586
|
+
name: 'compressing-agent',
|
|
587
|
+
config: new Config({ maxInputTokens: 8000 }),
|
|
588
|
+
contextCompressor: new SummarizingContextCompressor({
|
|
589
|
+
maxSummaryTokens: 512,
|
|
590
|
+
}),
|
|
591
|
+
});
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
`SummarizingContextCompressor` 会复用当前 Agent 的 LLM,把被截断的旧消息压缩成一条摘要前缀。摘要失败时自动降级为纯截断,不中断主请求。
|
|
595
|
+
|
|
596
|
+
## RAG 接入
|
|
597
|
+
|
|
598
|
+
RAG 子系统通过 `RagPipeline` 暴露三个核心方法:
|
|
599
|
+
|
|
600
|
+
- `ingest(input)`: 文档解析、标准化、切块、embedding、写入文档库/向量索引/关键词索引。
|
|
601
|
+
- `retrieve(query, options)`: 只返回可引用证据。
|
|
602
|
+
- `answer(query, options)`: 检索、构建上下文、调用模型生成答案、校验引用。
|
|
603
|
+
|
|
604
|
+
常用检索/回答参数:
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
type RetrieveOptions = {
|
|
608
|
+
topK?: number;
|
|
609
|
+
minScore?: number;
|
|
610
|
+
tenantId?: string;
|
|
611
|
+
knowledgeBaseId?: string;
|
|
612
|
+
acl?: string[];
|
|
613
|
+
documentIds?: string[];
|
|
614
|
+
sources?: string[];
|
|
615
|
+
metadataFilter?: Record<string, unknown>;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
type AnswerOptions = RetrieveOptions & {
|
|
619
|
+
llm?: ChatModel;
|
|
620
|
+
maxContextTokens?: number;
|
|
621
|
+
minFusedScore?: number;
|
|
622
|
+
requireCitations?: boolean;
|
|
623
|
+
systemPrompt?: string;
|
|
624
|
+
metadata?: Record<string, unknown>;
|
|
625
|
+
};
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
`minScore` 作用在具体 retriever 的原始分数尺度上;`minFusedScore` 作用在 context builder 的融合后分数尺度上,两者不要混用。
|
|
629
|
+
|
|
630
|
+
### 构建内存 RAG pipeline
|
|
631
|
+
|
|
632
|
+
适合本地开发、测试或最小 MVP;生产应替换为 Postgres/pgvector 或自有存储。
|
|
230
633
|
|
|
231
634
|
```ts
|
|
232
635
|
import {
|
|
@@ -242,6 +645,7 @@ import {
|
|
|
242
645
|
InMemoryKeywordStore,
|
|
243
646
|
InMemoryVectorStore,
|
|
244
647
|
OpenAIEmbedder,
|
|
648
|
+
ParentChildExpandingRetriever,
|
|
245
649
|
RuleBasedVerifier,
|
|
246
650
|
SparseRetriever,
|
|
247
651
|
} from '@fllf/agent-sdk';
|
|
@@ -264,6 +668,17 @@ const sparseRetriever = new SparseRetriever({
|
|
|
264
668
|
documentStore,
|
|
265
669
|
});
|
|
266
670
|
|
|
671
|
+
const retriever = new ParentChildExpandingRetriever({
|
|
672
|
+
retriever: new HybridRetriever({
|
|
673
|
+
denseRetriever,
|
|
674
|
+
sparseRetriever,
|
|
675
|
+
}),
|
|
676
|
+
documentStore,
|
|
677
|
+
strategy: 'parent_or_neighbors',
|
|
678
|
+
neighborWindow: 1,
|
|
679
|
+
maxExpandedChunkCharacters: 3000,
|
|
680
|
+
});
|
|
681
|
+
|
|
267
682
|
export const ragPipeline = new DefaultRagPipeline({
|
|
268
683
|
loader: new AutoDocumentLoader(),
|
|
269
684
|
normalizer: new DefaultDocumentNormalizer(),
|
|
@@ -272,23 +687,26 @@ export const ragPipeline = new DefaultRagPipeline({
|
|
|
272
687
|
documentStore,
|
|
273
688
|
vectorStore,
|
|
274
689
|
keywordStore,
|
|
275
|
-
retriever
|
|
276
|
-
|
|
277
|
-
sparseRetriever,
|
|
278
|
-
}),
|
|
279
|
-
contextBuilder: new DefaultContextBuilder({
|
|
280
|
-
maxContextTokens: 3000,
|
|
281
|
-
}),
|
|
690
|
+
retriever,
|
|
691
|
+
contextBuilder: new DefaultContextBuilder({ maxContextTokens: 3000 }),
|
|
282
692
|
generator: new DefaultRagGenerator(),
|
|
283
693
|
verifier: new RuleBasedVerifier(),
|
|
284
694
|
});
|
|
285
695
|
```
|
|
286
696
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
697
|
+
### Embedding
|
|
698
|
+
|
|
699
|
+
OpenAI embedding:
|
|
700
|
+
|
|
701
|
+
```ts
|
|
702
|
+
import { OpenAIEmbedder } from '@fllf/agent-sdk';
|
|
703
|
+
|
|
704
|
+
const embedder = new OpenAIEmbedder({
|
|
705
|
+
model: 'text-embedding-3-small',
|
|
706
|
+
});
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
OpenAI-compatible embedding provider:
|
|
292
710
|
|
|
293
711
|
```ts
|
|
294
712
|
import {
|
|
@@ -308,14 +726,20 @@ const vectorStore = new InMemoryVectorStore({
|
|
|
308
726
|
});
|
|
309
727
|
```
|
|
310
728
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
729
|
+
未知 embedding 模型必须显式传 `dimensions`。更换 embedding 模型或维度时,应新建向量集合或重新 ingestion,不能混用不同维度/模型的向量。
|
|
730
|
+
|
|
731
|
+
可用 `EmbeddingCache` 缓存 embedding:
|
|
314
732
|
|
|
315
|
-
|
|
733
|
+
```ts
|
|
734
|
+
import { EmbeddingCache, OpenAIEmbedder } from '@fllf/agent-sdk';
|
|
735
|
+
|
|
736
|
+
const embedder = new EmbeddingCache({
|
|
737
|
+
embedder: new OpenAIEmbedder(),
|
|
738
|
+
namespace: 'tenant_1:kb_support',
|
|
739
|
+
});
|
|
740
|
+
```
|
|
316
741
|
|
|
317
|
-
|
|
318
|
-
document synchronization job.
|
|
742
|
+
### Ingest 文档
|
|
319
743
|
|
|
320
744
|
```ts
|
|
321
745
|
await ragPipeline.ingest({
|
|
@@ -328,16 +752,92 @@ await ragPipeline.ingest({
|
|
|
328
752
|
});
|
|
329
753
|
```
|
|
330
754
|
|
|
331
|
-
|
|
755
|
+
关键字段:
|
|
756
|
+
|
|
757
|
+
- `source`: 稳定来源路径、URL 或业务文档 id,会出现在引用中。
|
|
758
|
+
- `tenantId`: 租户边界。
|
|
759
|
+
- `knowledgeBaseId`: 知识库边界。
|
|
760
|
+
- `acl`: 检索阶段的访问控制标签。
|
|
761
|
+
- `metadata`: 文档元数据,可包含 `title`、`mimeType`、`version`、`extra` 等。
|
|
762
|
+
- `replaceExisting`: 使用稳定 document id 清理旧 chunks/vectors/keywords;checksum 相同则跳过重复 ingestion。
|
|
763
|
+
|
|
764
|
+
`AutoDocumentLoader` 根据 `mimeType` 或 `source` 后缀选择 Markdown、HTML、DOCX、PDF、纯文本 loader。HTML/DOCX/PDF 需要安装对应可选依赖。
|
|
765
|
+
|
|
766
|
+
### 切块与检索
|
|
767
|
+
|
|
768
|
+
`AutoChunker` 当前策略:
|
|
769
|
+
|
|
770
|
+
- Markdown 或由 HTML/DOCX/PDF 转换出的 Markdown:`MarkdownChunker`,保留标题、表格、代码等结构。
|
|
771
|
+
- FAQ/QA 风格文本:`QaPairChunker`。
|
|
772
|
+
- 长无结构文本:`ParentChildChunker`,小块召回、大块提供上下文。
|
|
773
|
+
- 其他短文本:`RecursiveChunker`。
|
|
774
|
+
|
|
775
|
+
检索器:
|
|
776
|
+
|
|
777
|
+
- `DenseRetriever`: embedding + vector store。
|
|
778
|
+
- `SparseRetriever`: keyword store。
|
|
779
|
+
- `HybridRetriever`: dense/sparse 并行检索,使用 RRF 融合,可接 reranker。
|
|
780
|
+
- `ParentChildExpandingRetriever`: 把 child hit 扩展为 parent 或邻近 chunk。
|
|
781
|
+
- `DefaultReranker`: 未配置模型时 no-op;配置 `RerankModelClient` 后按 rerank 分数重排。
|
|
782
|
+
- `HttpRerankModelClient`: 适配 Jina/Cohere/常见网关 `/rerank` 端点。
|
|
783
|
+
|
|
784
|
+
Rerank 示例:
|
|
785
|
+
|
|
786
|
+
```ts
|
|
787
|
+
import {
|
|
788
|
+
DefaultReranker,
|
|
789
|
+
HttpRerankModelClient,
|
|
790
|
+
HybridRetriever,
|
|
791
|
+
} from '@fllf/agent-sdk';
|
|
792
|
+
|
|
793
|
+
const reranker = new DefaultReranker({
|
|
794
|
+
model: new HttpRerankModelClient({
|
|
795
|
+
baseURL: process.env.RERANK_BASE_URL,
|
|
796
|
+
apiKey: process.env.RERANK_API_KEY,
|
|
797
|
+
model: process.env.RERANK_MODEL ?? 'jina-reranker-v2-base-multilingual',
|
|
798
|
+
}),
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const retriever = new HybridRetriever({
|
|
802
|
+
denseRetriever,
|
|
803
|
+
sparseRetriever,
|
|
804
|
+
reranker,
|
|
805
|
+
});
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### Postgres/pgvector RAG 存储
|
|
332
809
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
810
|
+
```ts
|
|
811
|
+
import {
|
|
812
|
+
createPostgresRagSchemaSql,
|
|
813
|
+
PgVectorStore,
|
|
814
|
+
PostgresDocumentStore,
|
|
815
|
+
PostgresKeywordStore,
|
|
816
|
+
} from '@fllf/agent-sdk';
|
|
339
817
|
|
|
340
|
-
|
|
818
|
+
await db.query(createPostgresRagSchemaSql({
|
|
819
|
+
tablePrefix: 'rag',
|
|
820
|
+
dimensions: embedder.dimensions,
|
|
821
|
+
}));
|
|
822
|
+
|
|
823
|
+
const documentStore = new PostgresDocumentStore({ db, tablePrefix: 'rag' });
|
|
824
|
+
const vectorStore = new PgVectorStore({
|
|
825
|
+
db,
|
|
826
|
+
tablePrefix: 'rag',
|
|
827
|
+
dimensions: embedder.dimensions,
|
|
828
|
+
});
|
|
829
|
+
const keywordStore = new PostgresKeywordStore({ db, tablePrefix: 'rag' });
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
建表 SQL 默认:
|
|
833
|
+
|
|
834
|
+
- 创建 `vector` 扩展。
|
|
835
|
+
- 创建 documents、chunks、embeddings、keyword_entries 表。
|
|
836
|
+
- 创建 HNSW 向量索引、GIN 关键词/ACL/extra 索引等。
|
|
837
|
+
|
|
838
|
+
所有 Postgres RAG store 只依赖注入的 `query(sql, params)`,可直接使用 `pg.Pool` 或兼容封装。
|
|
839
|
+
|
|
840
|
+
### Knowledge-base QA Agent
|
|
341
841
|
|
|
342
842
|
```ts
|
|
343
843
|
import { Agent, RagExecutor } from '@fllf/agent-sdk';
|
|
@@ -363,11 +863,17 @@ const result = await knowledgeAgent.run('What is the refund window?', {
|
|
|
363
863
|
},
|
|
364
864
|
});
|
|
365
865
|
|
|
366
|
-
|
|
367
|
-
|
|
866
|
+
return {
|
|
867
|
+
message: result.output,
|
|
868
|
+
citations: (result.raw as any).citations,
|
|
869
|
+
confidence: (result.raw as any).confidence,
|
|
870
|
+
usage: result.usage,
|
|
871
|
+
};
|
|
368
872
|
```
|
|
369
873
|
|
|
370
|
-
`
|
|
874
|
+
`RagExecutor` 会从 `metadata` 读取 `tenantId`、`knowledgeBaseId`、`acl`、`documentIds`、`sources`、`metadataFilter`、`topK`、`minScore`、`maxContextTokens`。
|
|
875
|
+
|
|
876
|
+
`RagAnswer` 形状:
|
|
371
877
|
|
|
372
878
|
```ts
|
|
373
879
|
type RagAnswer = {
|
|
@@ -383,27 +889,15 @@ type RagAnswer = {
|
|
|
383
889
|
quote?: string;
|
|
384
890
|
}>;
|
|
385
891
|
confidence: 'high' | 'medium' | 'low';
|
|
386
|
-
retrieved:
|
|
387
|
-
usage?:
|
|
892
|
+
retrieved: RetrievedChunk[];
|
|
893
|
+
usage?: TokenUsage;
|
|
388
894
|
metadata?: Record<string, unknown>;
|
|
389
895
|
};
|
|
390
896
|
```
|
|
391
897
|
|
|
392
|
-
|
|
898
|
+
### RAG 作为可选工具
|
|
393
899
|
|
|
394
|
-
|
|
395
|
-
return {
|
|
396
|
-
message: result.output,
|
|
397
|
-
citations: (result.raw as any).citations,
|
|
398
|
-
confidence: (result.raw as any).confidence,
|
|
399
|
-
usage: result.usage,
|
|
400
|
-
};
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### RAG as an optional search tool
|
|
404
|
-
|
|
405
|
-
Use `RagSearchTool` when the agent should decide whether to search the knowledge
|
|
406
|
-
base.
|
|
900
|
+
模型需要时才检索知识库:
|
|
407
901
|
|
|
408
902
|
```ts
|
|
409
903
|
import {
|
|
@@ -420,6 +914,9 @@ const agent = new Agent({
|
|
|
420
914
|
new RagSearchTool({
|
|
421
915
|
pipeline: ragPipeline,
|
|
422
916
|
defaultRetrieveOptions: {
|
|
917
|
+
tenantId: 'tenant_1',
|
|
918
|
+
knowledgeBaseId: 'kb_support',
|
|
919
|
+
acl: ['user:user_42', 'role:support'],
|
|
423
920
|
topK: 5,
|
|
424
921
|
},
|
|
425
922
|
}),
|
|
@@ -428,30 +925,53 @@ const agent = new Agent({
|
|
|
428
925
|
});
|
|
429
926
|
```
|
|
430
927
|
|
|
431
|
-
|
|
928
|
+
安全边界:`RagSearchTool` 不把 `tenantId`、`knowledgeBaseId`、`acl` 暴露给模型参数,这些权限字段只能由服务端通过 `defaultRetrieveOptions` 设置。模型可传的仅是 `query`、`topK`、`minScore`、`documentIds`、`sources`、`metadataFilter` 等检索调优字段。
|
|
929
|
+
|
|
930
|
+
## 内置搜索工具
|
|
931
|
+
|
|
932
|
+
`AdvancedSearchTool` 可接入 Tavily 和 SerpAPI:
|
|
432
933
|
|
|
433
934
|
```ts
|
|
434
|
-
import {
|
|
435
|
-
import { LLM } from '@fllf/agent-sdk';
|
|
935
|
+
import { AdvancedSearchTool } from '@fllf/agent-sdk';
|
|
436
936
|
|
|
437
|
-
const
|
|
937
|
+
const tools = [new AdvancedSearchTool({ logger })];
|
|
938
|
+
```
|
|
438
939
|
|
|
439
|
-
|
|
440
|
-
messages: [
|
|
441
|
-
{ role: 'user', content: 'Return a short project summary.' },
|
|
442
|
-
],
|
|
443
|
-
schema: z.object({
|
|
444
|
-
summary: z.string(),
|
|
445
|
-
confidence: z.number(),
|
|
446
|
-
}),
|
|
447
|
-
});
|
|
940
|
+
环境变量:
|
|
448
941
|
|
|
449
|
-
|
|
942
|
+
```bash
|
|
943
|
+
TAVILY_API_KEY=...
|
|
944
|
+
SERPAPI_API_KEY=...
|
|
450
945
|
```
|
|
451
946
|
|
|
452
|
-
|
|
947
|
+
若两个 key 都未配置,工具会返回提示文本,不会抛出配置错误。该工具会真实调用外部搜索 API,第三方系统应自行处理网络权限、配额、审计和结果可信度。
|
|
453
948
|
|
|
454
|
-
|
|
949
|
+
## 生命周期钩子
|
|
950
|
+
|
|
951
|
+
```ts
|
|
952
|
+
import { Agent } from '@fllf/agent-sdk';
|
|
953
|
+
|
|
954
|
+
const agent = new Agent({
|
|
955
|
+
name: 'audited-chat',
|
|
956
|
+
hooks: {
|
|
957
|
+
beforeRun(context) {
|
|
958
|
+
authorize(context.metadata);
|
|
959
|
+
},
|
|
960
|
+
afterRun(context, result) {
|
|
961
|
+
audit.info({ runId: context.runId, outputLength: result.output.length });
|
|
962
|
+
},
|
|
963
|
+
onRunError(context, error) {
|
|
964
|
+
audit.error({ runId: context.runId, error });
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
});
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
`beforeRun` 失败会中止 run。`afterRun` 和 `onRunError` 失败不会替换主结果/主错误,会作为 warn 级 `agent:error` observer event 发出。
|
|
971
|
+
|
|
972
|
+
## 可观测性
|
|
973
|
+
|
|
974
|
+
SDK 默认不打印日志。接入方可通过 observer 收集事件。
|
|
455
975
|
|
|
456
976
|
```ts
|
|
457
977
|
import { Agent, ConsoleObserver } from '@fllf/agent-sdk';
|
|
@@ -464,7 +984,7 @@ const agent = new Agent({
|
|
|
464
984
|
await agent.run('Hello');
|
|
465
985
|
```
|
|
466
986
|
|
|
467
|
-
|
|
987
|
+
事件类型:
|
|
468
988
|
|
|
469
989
|
- `agent:start`
|
|
470
990
|
- `agent:end`
|
|
@@ -472,37 +992,101 @@ Event types include:
|
|
|
472
992
|
- `llm:start`
|
|
473
993
|
- `llm:end`
|
|
474
994
|
- `llm:error`
|
|
995
|
+
- `llm:retry`
|
|
475
996
|
- `tool:start`
|
|
476
997
|
- `tool:end`
|
|
477
998
|
- `tool:error`
|
|
478
999
|
|
|
479
|
-
|
|
1000
|
+
自定义 observer:
|
|
480
1001
|
|
|
481
1002
|
```ts
|
|
482
1003
|
const observer = {
|
|
1004
|
+
redactEvent(event) {
|
|
1005
|
+
return {
|
|
1006
|
+
...event,
|
|
1007
|
+
metadata: undefined,
|
|
1008
|
+
};
|
|
1009
|
+
},
|
|
483
1010
|
onEvent(event) {
|
|
484
1011
|
logger.info({ event }, 'agent runtime event');
|
|
485
1012
|
},
|
|
486
1013
|
};
|
|
487
1014
|
```
|
|
488
1015
|
|
|
489
|
-
|
|
1016
|
+
事件包含 `runId`、`timestamp`、可选 `spanId` / `parentSpanId`、provider/model、toolName/toolCallId、duration、usage、error 等字段。不要把敏感信息放入 `metadata`,因为 metadata 可能出现在消息、工具上下文和 observer 事件中。
|
|
1017
|
+
|
|
1018
|
+
测试中可使用:
|
|
1019
|
+
|
|
1020
|
+
```ts
|
|
1021
|
+
import { CollectingObserver } from '@fllf/agent-sdk';
|
|
1022
|
+
|
|
1023
|
+
const observer = new CollectingObserver();
|
|
1024
|
+
await agent.run('Hello', { observer });
|
|
1025
|
+
|
|
1026
|
+
console.log(observer.events);
|
|
1027
|
+
observer.clear();
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
## 错误处理
|
|
1031
|
+
|
|
1032
|
+
核心错误类型从根包和 `@fllf/agent-sdk/errors` 导出:
|
|
1033
|
+
|
|
1034
|
+
- `AgentSDKError`: SDK 统一错误基类,含 `code`、`retryable`、`metadata`。
|
|
1035
|
+
- `AgentError`、`AgentSessionError`、`AgentExecutionError`。
|
|
1036
|
+
- `ToolError`、`ToolConfigurationError`、`ToolPermissionError`、`ToolTimeoutError`、`ToolAbortError`、`ToolExecutionError`、`ToolNotFoundError`。
|
|
1037
|
+
- `HistoryError`、`HistoryVersionConflictError`。
|
|
1038
|
+
- `ConfigError`。
|
|
1039
|
+
- LLM 层错误:`LLMAuthenticationError`、`LLMRateLimitError`、`LLMTimeoutError`、`LLMAbortError`、`LLMUnsupportedCapabilityError`、`LLMProviderError`、`LLMInvalidResponseError`、`LLMStructuredOutputError`。
|
|
1040
|
+
|
|
1041
|
+
示例:
|
|
1042
|
+
|
|
1043
|
+
```ts
|
|
1044
|
+
import { AgentSDKError } from '@fllf/agent-sdk';
|
|
1045
|
+
|
|
1046
|
+
try {
|
|
1047
|
+
await agent.run(input, options);
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
if (error instanceof AgentSDKError) {
|
|
1050
|
+
logger.warn({
|
|
1051
|
+
code: error.code,
|
|
1052
|
+
retryable: error.retryable,
|
|
1053
|
+
metadata: error.metadata,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
throw error;
|
|
1057
|
+
}
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
LLM 会把 401/403、429、超时、调用方 abort、常见瞬态网络错误归一化为稳定错误类型。可重试错误会触发内置重试,并产生 `llm:retry` 事件。
|
|
1061
|
+
|
|
1062
|
+
## 测试辅助工具
|
|
1063
|
+
|
|
1064
|
+
下游系统可使用 SDK 提供的测试工具编写稳定集成测试:
|
|
490
1065
|
|
|
491
|
-
|
|
1066
|
+
```ts
|
|
1067
|
+
import {
|
|
1068
|
+
assertMessageHistoryStoreContract,
|
|
1069
|
+
assertToolSchemaSnapshot,
|
|
1070
|
+
createTestAgent,
|
|
1071
|
+
createToolSchemaSnapshot,
|
|
1072
|
+
FakeChatModel,
|
|
1073
|
+
FakeTool,
|
|
1074
|
+
ScriptedChatModel,
|
|
1075
|
+
} from '@fllf/agent-sdk';
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
用途:
|
|
492
1079
|
|
|
493
|
-
-
|
|
494
|
-
-
|
|
495
|
-
-
|
|
496
|
-
-
|
|
497
|
-
-
|
|
498
|
-
-
|
|
499
|
-
- Rate limits, model cost controls, and request timeouts.
|
|
500
|
-
- Redaction or filtering for logs that may contain user messages or document
|
|
501
|
-
snippets.
|
|
1080
|
+
- `ScriptedChatModel`: 队列化返回固定模型响应,并记录请求。
|
|
1081
|
+
- `FakeChatModel`: 回显最后一条 user 消息。
|
|
1082
|
+
- `FakeTool`: 校验输入并记录调用。
|
|
1083
|
+
- `createTestAgent()`: 快速创建测试 Agent。
|
|
1084
|
+
- `createToolSchemaSnapshot()` / `assertToolSchemaSnapshot()`: 对工具暴露 schema 做快照测试。
|
|
1085
|
+
- `assertMessageHistoryStoreContract()`: 校验自定义历史存储满足 session 隔离、窗口读取、防御性拷贝、可选 CAS 语义等契约。
|
|
502
1086
|
|
|
503
|
-
##
|
|
1087
|
+
## 包导出
|
|
504
1088
|
|
|
505
|
-
|
|
1089
|
+
推荐从根包导入稳定 API:
|
|
506
1090
|
|
|
507
1091
|
```ts
|
|
508
1092
|
import {
|
|
@@ -516,33 +1100,75 @@ import {
|
|
|
516
1100
|
} from '@fllf/agent-sdk';
|
|
517
1101
|
```
|
|
518
1102
|
|
|
519
|
-
|
|
1103
|
+
也支持子路径导入:
|
|
1104
|
+
|
|
1105
|
+
```ts
|
|
1106
|
+
import { Agent } from '@fllf/agent-sdk/agent';
|
|
1107
|
+
import { Config } from '@fllf/agent-sdk/config';
|
|
1108
|
+
import { AgentSDKError } from '@fllf/agent-sdk/errors';
|
|
1109
|
+
import { LLM } from '@fllf/agent-sdk/llm';
|
|
1110
|
+
import { Message } from '@fllf/agent-sdk/messages';
|
|
1111
|
+
import { ConsoleObserver } from '@fllf/agent-sdk/observability';
|
|
1112
|
+
import { DefaultRagPipeline } from '@fllf/agent-sdk/rag';
|
|
1113
|
+
import { Tool } from '@fllf/agent-sdk/tools';
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
稳定接入面:
|
|
1117
|
+
|
|
1118
|
+
- Agent: `Agent`、`createAgent`、`AgentRunOptions`、`AgentResult`、`AgentLifecycleHooks`。
|
|
1119
|
+
- Executors: `SimpleChatExecutor`、`ToolCallingExecutor`、`RagExecutor`、`BaseExecutor`、`buildChatMessages`、`buildChatRequest`。
|
|
1120
|
+
- LLM: `LLM`、`ChatModel`、`ChatRequest`、`ChatResponse`、`ModelProviderFactory`、`ModelProviderRegistry`。
|
|
1121
|
+
- Messages: `Message`、`MessageContent`、`ContentBlock`、`textBlock()`、`imageUrlBlock()`、`imageBase64Block()`、`contentToText()`。
|
|
1122
|
+
- Tools: `Tool`、`ToolRegistry`、`ToolExecutor`、`RagSearchTool`、`AdvancedSearchTool`。
|
|
1123
|
+
- History: `MessageHistory`、`MessageHistoryStore`、`InMemoryMessageHistoryStore`、`PostgresMessageHistoryStore`、`MessageJSON`、`reviveMessage()`。
|
|
1124
|
+
- RAG: pipeline、loader、normalizer、chunker、embedder、retriever、reranker、store、generator、verifier 相关接口与默认实现。
|
|
1125
|
+
- Observability: `Observer`、`RuntimeEvent`、`ConsoleObserver`、`CollectingObserver`。
|
|
1126
|
+
- Testing: `ScriptedChatModel`、`FakeChatModel`、`FakeTool`、`createTestAgent()`、契约/快照 helper。
|
|
1127
|
+
|
|
1128
|
+
包仍处于 `0.x` 阶段。应用代码应优先依赖上述稳定集成面;低层 provider 映射细节、SQL helper、实验性 chunker 等可能在小版本中调整。
|
|
1129
|
+
|
|
1130
|
+
## 生产接入清单
|
|
520
1131
|
|
|
521
|
-
-
|
|
522
|
-
- `
|
|
523
|
-
- `
|
|
524
|
-
-
|
|
525
|
-
-
|
|
526
|
-
-
|
|
527
|
-
|
|
528
|
-
-
|
|
1132
|
+
- 为每个外部请求建立明确的 tenant/user/conversation/session 边界。
|
|
1133
|
+
- 在调用 `Agent.run()`、`Agent.stream()`、`RagPipeline.retrieve()` 前完成认证与授权。
|
|
1134
|
+
- 使用持久化 `MessageHistoryStore`,多实例部署时处理 `HistoryVersionConflictError` 重试。
|
|
1135
|
+
- RAG 检索必须带 `tenantId`、`knowledgeBaseId` 和 ACL/metadata filter。
|
|
1136
|
+
- 文档 ingestion 放入后台任务或管理端流程,支持重建索引。
|
|
1137
|
+
- 为模型请求、工具、外部搜索和 rerank 设置超时与配额。
|
|
1138
|
+
- 对日志、observer event、metadata、工具结果和 RAG 片段做脱敏。
|
|
1139
|
+
- 不把用户可控字段作为 RAG 权限字段;`RagSearchTool` 权限字段只由服务端设置。
|
|
1140
|
+
- 更换 embedding 模型或维度后重建向量索引。
|
|
529
1141
|
|
|
530
|
-
##
|
|
1142
|
+
## 本仓库开发命令
|
|
531
1143
|
|
|
532
1144
|
```bash
|
|
533
1145
|
npm install
|
|
534
1146
|
npm run typecheck
|
|
535
1147
|
npm test
|
|
536
1148
|
npm run build
|
|
1149
|
+
npm run test:exports
|
|
537
1150
|
```
|
|
538
1151
|
|
|
539
|
-
|
|
1152
|
+
示例:
|
|
540
1153
|
|
|
541
1154
|
```bash
|
|
542
1155
|
npm run example:simple-chat
|
|
1156
|
+
npm run example:streaming-chat
|
|
543
1157
|
npm run example:tool-calling
|
|
1158
|
+
npm run example:tool-error-policy
|
|
544
1159
|
npm run example:local-ollama
|
|
1160
|
+
npm run example:custom-provider
|
|
1161
|
+
npm run example:custom-history-store
|
|
1162
|
+
npm run example:custom-executor
|
|
1163
|
+
npm run example:custom-observer
|
|
545
1164
|
```
|
|
546
1165
|
|
|
547
|
-
|
|
548
|
-
|
|
1166
|
+
发布前建议:
|
|
1167
|
+
|
|
1168
|
+
```bash
|
|
1169
|
+
npm run typecheck
|
|
1170
|
+
npm test
|
|
1171
|
+
npm run build
|
|
1172
|
+
npm run test:exports
|
|
1173
|
+
npm run pack:dry-run
|
|
1174
|
+
```
|