@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.
Files changed (266) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +824 -198
  3. package/dist/agent/Agent.d.ts +16 -5
  4. package/dist/agent/Agent.d.ts.map +1 -1
  5. package/dist/agent/Agent.js +283 -2
  6. package/dist/agent/Agent.js.map +1 -1
  7. package/dist/agent/index.d.ts +1 -1
  8. package/dist/agent/index.d.ts.map +1 -1
  9. package/dist/agent/types.d.ts +27 -3
  10. package/dist/agent/types.d.ts.map +1 -1
  11. package/dist/config/config.d.ts +22 -3
  12. package/dist/config/config.d.ts.map +1 -1
  13. package/dist/config/config.js +194 -47
  14. package/dist/config/config.js.map +1 -1
  15. package/dist/config/index.d.ts +1 -1
  16. package/dist/config/index.d.ts.map +1 -1
  17. package/dist/errors.d.ts +74 -0
  18. package/dist/errors.d.ts.map +1 -0
  19. package/dist/errors.js +186 -0
  20. package/dist/errors.js.map +1 -0
  21. package/dist/executors/base-executor.d.ts +14 -0
  22. package/dist/executors/base-executor.d.ts.map +1 -0
  23. package/dist/executors/base-executor.js +31 -0
  24. package/dist/executors/base-executor.js.map +1 -0
  25. package/dist/executors/base.d.ts +36 -5
  26. package/dist/executors/base.d.ts.map +1 -1
  27. package/dist/executors/chat-request-builder.d.ts +10 -0
  28. package/dist/executors/chat-request-builder.d.ts.map +1 -0
  29. package/dist/executors/chat-request-builder.js +96 -0
  30. package/dist/executors/chat-request-builder.js.map +1 -0
  31. package/dist/executors/index.d.ts +4 -1
  32. package/dist/executors/index.d.ts.map +1 -1
  33. package/dist/executors/index.js +6 -1
  34. package/dist/executors/index.js.map +1 -1
  35. package/dist/executors/rag-executor.js +1 -1
  36. package/dist/executors/rag-executor.js.map +1 -1
  37. package/dist/executors/simple-chat-executor.d.ts +4 -2
  38. package/dist/executors/simple-chat-executor.d.ts.map +1 -1
  39. package/dist/executors/simple-chat-executor.js +59 -57
  40. package/dist/executors/simple-chat-executor.js.map +1 -1
  41. package/dist/executors/tool-calling-executor.d.ts +20 -2
  42. package/dist/executors/tool-calling-executor.d.ts.map +1 -1
  43. package/dist/executors/tool-calling-executor.js +189 -91
  44. package/dist/executors/tool-calling-executor.js.map +1 -1
  45. package/dist/history/base.d.ts +55 -2
  46. package/dist/history/base.d.ts.map +1 -1
  47. package/dist/history/base.js +49 -0
  48. package/dist/history/base.js.map +1 -1
  49. package/dist/history/compression.d.ts +49 -0
  50. package/dist/history/compression.d.ts.map +1 -0
  51. package/dist/history/compression.js +53 -0
  52. package/dist/history/compression.js.map +1 -0
  53. package/dist/history/context-window.d.ts +33 -0
  54. package/dist/history/context-window.d.ts.map +1 -0
  55. package/dist/history/context-window.js +68 -0
  56. package/dist/history/context-window.js.map +1 -0
  57. package/dist/history/in-memory.d.ts +6 -4
  58. package/dist/history/in-memory.d.ts.map +1 -1
  59. package/dist/history/in-memory.js +25 -39
  60. package/dist/history/in-memory.js.map +1 -1
  61. package/dist/history/index.d.ts +9 -2
  62. package/dist/history/index.d.ts.map +1 -1
  63. package/dist/history/index.js +18 -1
  64. package/dist/history/index.js.map +1 -1
  65. package/dist/history/postgres/index.d.ts +4 -0
  66. package/dist/history/postgres/index.d.ts.map +1 -0
  67. package/dist/history/postgres/index.js +20 -0
  68. package/dist/history/postgres/index.js.map +1 -0
  69. package/dist/history/postgres/postgres-message-history-store.d.ts +35 -0
  70. package/dist/history/postgres/postgres-message-history-store.d.ts.map +1 -0
  71. package/dist/history/postgres/postgres-message-history-store.js +195 -0
  72. package/dist/history/postgres/postgres-message-history-store.js.map +1 -0
  73. package/dist/history/postgres/schema.d.ts +18 -0
  74. package/dist/history/postgres/schema.d.ts.map +1 -0
  75. package/dist/history/postgres/schema.js +46 -0
  76. package/dist/history/postgres/schema.js.map +1 -0
  77. package/dist/history/postgres/sql.d.ts +29 -0
  78. package/dist/history/postgres/sql.d.ts.map +1 -0
  79. package/dist/history/postgres/sql.js +102 -0
  80. package/dist/history/postgres/sql.js.map +1 -0
  81. package/dist/history/postgres/types.d.ts +20 -0
  82. package/dist/history/postgres/types.d.ts.map +1 -0
  83. package/dist/history/postgres/types.js +3 -0
  84. package/dist/history/postgres/types.js.map +1 -0
  85. package/dist/history/tool-pairing.d.ts +11 -0
  86. package/dist/history/tool-pairing.d.ts.map +1 -0
  87. package/dist/history/tool-pairing.js +52 -0
  88. package/dist/history/tool-pairing.js.map +1 -0
  89. package/dist/index.d.ts +2 -0
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +2 -0
  92. package/dist/index.js.map +1 -1
  93. package/dist/llm/LLM.d.ts +1 -0
  94. package/dist/llm/LLM.d.ts.map +1 -1
  95. package/dist/llm/LLM.js +136 -26
  96. package/dist/llm/LLM.js.map +1 -1
  97. package/dist/llm/errors.d.ts +11 -1
  98. package/dist/llm/errors.d.ts.map +1 -1
  99. package/dist/llm/errors.js +50 -1
  100. package/dist/llm/errors.js.map +1 -1
  101. package/dist/llm/factory.d.ts +14 -2
  102. package/dist/llm/factory.d.ts.map +1 -1
  103. package/dist/llm/factory.js +48 -9
  104. package/dist/llm/factory.js.map +1 -1
  105. package/dist/llm/index.d.ts +8 -3
  106. package/dist/llm/index.d.ts.map +1 -1
  107. package/dist/llm/index.js +10 -1
  108. package/dist/llm/index.js.map +1 -1
  109. package/dist/llm/providers/anthropic.d.ts +14 -0
  110. package/dist/llm/providers/anthropic.d.ts.map +1 -0
  111. package/dist/llm/providers/anthropic.js +336 -0
  112. package/dist/llm/providers/anthropic.js.map +1 -0
  113. package/dist/llm/providers/openai-compatible.d.ts +7 -0
  114. package/dist/llm/providers/openai-compatible.d.ts.map +1 -1
  115. package/dist/llm/providers/openai-compatible.js +151 -47
  116. package/dist/llm/providers/openai-compatible.js.map +1 -1
  117. package/dist/llm/stream-accumulator.d.ts +17 -0
  118. package/dist/llm/stream-accumulator.d.ts.map +1 -0
  119. package/dist/llm/stream-accumulator.js +83 -0
  120. package/dist/llm/stream-accumulator.js.map +1 -0
  121. package/dist/llm/tool-arguments.d.ts +6 -0
  122. package/dist/llm/tool-arguments.d.ts.map +1 -0
  123. package/dist/llm/tool-arguments.js +20 -0
  124. package/dist/llm/tool-arguments.js.map +1 -0
  125. package/dist/llm/types.d.ts +76 -4
  126. package/dist/llm/types.d.ts.map +1 -1
  127. package/dist/messages/content.d.ts +36 -0
  128. package/dist/messages/content.d.ts.map +1 -0
  129. package/dist/messages/content.js +70 -0
  130. package/dist/messages/content.js.map +1 -0
  131. package/dist/messages/index.d.ts +4 -2
  132. package/dist/messages/index.d.ts.map +1 -1
  133. package/dist/messages/index.js +10 -1
  134. package/dist/messages/index.js.map +1 -1
  135. package/dist/messages/message.d.ts +11 -9
  136. package/dist/messages/message.d.ts.map +1 -1
  137. package/dist/messages/message.js +69 -4
  138. package/dist/messages/message.js.map +1 -1
  139. package/dist/messages/types.d.ts +12 -0
  140. package/dist/messages/types.d.ts.map +1 -1
  141. package/dist/observability/collecting-observer.d.ts +8 -0
  142. package/dist/observability/collecting-observer.d.ts.map +1 -0
  143. package/dist/observability/collecting-observer.js +15 -0
  144. package/dist/observability/collecting-observer.js.map +1 -0
  145. package/dist/observability/index.d.ts +1 -0
  146. package/dist/observability/index.d.ts.map +1 -1
  147. package/dist/observability/index.js +3 -1
  148. package/dist/observability/index.js.map +1 -1
  149. package/dist/observability/observer.d.ts.map +1 -1
  150. package/dist/observability/observer.js +14 -1
  151. package/dist/observability/observer.js.map +1 -1
  152. package/dist/observability/types.d.ts +16 -1
  153. package/dist/observability/types.d.ts.map +1 -1
  154. package/dist/rag/chunking/auto-chunker.d.ts +7 -0
  155. package/dist/rag/chunking/auto-chunker.d.ts.map +1 -1
  156. package/dist/rag/chunking/auto-chunker.js +13 -0
  157. package/dist/rag/chunking/auto-chunker.js.map +1 -1
  158. package/dist/rag/chunking/chunker.d.ts.map +1 -1
  159. package/dist/rag/chunking/chunker.js +29 -5
  160. package/dist/rag/chunking/chunker.js.map +1 -1
  161. package/dist/rag/chunking/index.d.ts +2 -0
  162. package/dist/rag/chunking/index.d.ts.map +1 -1
  163. package/dist/rag/chunking/index.js +2 -0
  164. package/dist/rag/chunking/index.js.map +1 -1
  165. package/dist/rag/chunking/markdown-chunker.d.ts.map +1 -1
  166. package/dist/rag/chunking/markdown-chunker.js.map +1 -1
  167. package/dist/rag/chunking/qa-pair-chunker.d.ts +23 -0
  168. package/dist/rag/chunking/qa-pair-chunker.d.ts.map +1 -0
  169. package/dist/rag/chunking/qa-pair-chunker.js +162 -0
  170. package/dist/rag/chunking/qa-pair-chunker.js.map +1 -0
  171. package/dist/rag/chunking/semantic-chunker.d.ts +19 -0
  172. package/dist/rag/chunking/semantic-chunker.d.ts.map +1 -0
  173. package/dist/rag/chunking/semantic-chunker.js +291 -0
  174. package/dist/rag/chunking/semantic-chunker.js.map +1 -0
  175. package/dist/rag/embeddings/embedder.d.ts.map +1 -1
  176. package/dist/rag/embeddings/embedder.js +6 -0
  177. package/dist/rag/embeddings/embedder.js.map +1 -1
  178. package/dist/rag/generation/context-builder.d.ts +7 -0
  179. package/dist/rag/generation/context-builder.d.ts.map +1 -1
  180. package/dist/rag/generation/context-builder.js +4 -1
  181. package/dist/rag/generation/context-builder.js.map +1 -1
  182. package/dist/rag/ingestion/metadata.d.ts +6 -1
  183. package/dist/rag/ingestion/metadata.d.ts.map +1 -1
  184. package/dist/rag/ingestion/metadata.js +6 -2
  185. package/dist/rag/ingestion/metadata.js.map +1 -1
  186. package/dist/rag/pipeline.d.ts.map +1 -1
  187. package/dist/rag/pipeline.js +34 -11
  188. package/dist/rag/pipeline.js.map +1 -1
  189. package/dist/rag/retrieval/index.d.ts +1 -0
  190. package/dist/rag/retrieval/index.d.ts.map +1 -1
  191. package/dist/rag/retrieval/index.js +1 -0
  192. package/dist/rag/retrieval/index.js.map +1 -1
  193. package/dist/rag/retrieval/parent-child-expanding-retriever.d.ts +31 -0
  194. package/dist/rag/retrieval/parent-child-expanding-retriever.d.ts.map +1 -0
  195. package/dist/rag/retrieval/parent-child-expanding-retriever.js +194 -0
  196. package/dist/rag/retrieval/parent-child-expanding-retriever.js.map +1 -0
  197. package/dist/rag/stores/in-memory-keyword-store.d.ts.map +1 -1
  198. package/dist/rag/stores/in-memory-keyword-store.js +3 -8
  199. package/dist/rag/stores/in-memory-keyword-store.js.map +1 -1
  200. package/dist/rag/stores/keyword-tokenizer.d.ts +4 -0
  201. package/dist/rag/stores/keyword-tokenizer.d.ts.map +1 -0
  202. package/dist/rag/stores/keyword-tokenizer.js +113 -0
  203. package/dist/rag/stores/keyword-tokenizer.js.map +1 -0
  204. package/dist/rag/stores/postgres/pg-vector-store.d.ts.map +1 -1
  205. package/dist/rag/stores/postgres/pg-vector-store.js +21 -8
  206. package/dist/rag/stores/postgres/pg-vector-store.js.map +1 -1
  207. package/dist/rag/stores/postgres/postgres-document-store.d.ts.map +1 -1
  208. package/dist/rag/stores/postgres/postgres-document-store.js +26 -13
  209. package/dist/rag/stores/postgres/postgres-document-store.js.map +1 -1
  210. package/dist/rag/stores/postgres/postgres-keyword-store.d.ts.map +1 -1
  211. package/dist/rag/stores/postgres/postgres-keyword-store.js +43 -43
  212. package/dist/rag/stores/postgres/postgres-keyword-store.js.map +1 -1
  213. package/dist/rag/stores/postgres/sql.d.ts +22 -0
  214. package/dist/rag/stores/postgres/sql.d.ts.map +1 -1
  215. package/dist/rag/stores/postgres/sql.js +42 -0
  216. package/dist/rag/stores/postgres/sql.js.map +1 -1
  217. package/dist/rag/stores/types.d.ts +5 -0
  218. package/dist/rag/stores/types.d.ts.map +1 -1
  219. package/dist/rag/types.d.ts +6 -0
  220. package/dist/rag/types.d.ts.map +1 -1
  221. package/dist/testing/agent.d.ts +11 -0
  222. package/dist/testing/agent.d.ts.map +1 -0
  223. package/dist/testing/agent.js +45 -0
  224. package/dist/testing/agent.js.map +1 -0
  225. package/dist/testing/history-contract.d.ts +12 -0
  226. package/dist/testing/history-contract.d.ts.map +1 -0
  227. package/dist/testing/history-contract.js +111 -0
  228. package/dist/testing/history-contract.js.map +1 -0
  229. package/dist/testing/index.d.ts +11 -0
  230. package/dist/testing/index.d.ts.map +1 -0
  231. package/dist/testing/index.js +16 -0
  232. package/dist/testing/index.js.map +1 -0
  233. package/dist/testing/models.d.ts +34 -0
  234. package/dist/testing/models.d.ts.map +1 -0
  235. package/dist/testing/models.js +74 -0
  236. package/dist/testing/models.js.map +1 -0
  237. package/dist/testing/tool-schema.d.ts +6 -0
  238. package/dist/testing/tool-schema.d.ts.map +1 -0
  239. package/dist/testing/tool-schema.js +29 -0
  240. package/dist/testing/tool-schema.js.map +1 -0
  241. package/dist/testing/tools.d.ts +21 -0
  242. package/dist/testing/tools.d.ts.map +1 -0
  243. package/dist/testing/tools.js +43 -0
  244. package/dist/testing/tools.js.map +1 -0
  245. package/dist/tools/base.d.ts +38 -7
  246. package/dist/tools/base.d.ts.map +1 -1
  247. package/dist/tools/base.js +238 -11
  248. package/dist/tools/base.js.map +1 -1
  249. package/dist/tools/builtin/advancedSearchTool.d.ts.map +1 -1
  250. package/dist/tools/builtin/advancedSearchTool.js +30 -4
  251. package/dist/tools/builtin/advancedSearchTool.js.map +1 -1
  252. package/dist/tools/builtin/ragSearchTool.d.ts +6 -3
  253. package/dist/tools/builtin/ragSearchTool.d.ts.map +1 -1
  254. package/dist/tools/builtin/ragSearchTool.js +8 -6
  255. package/dist/tools/builtin/ragSearchTool.js.map +1 -1
  256. package/dist/tools/executor.d.ts +30 -2
  257. package/dist/tools/executor.d.ts.map +1 -1
  258. package/dist/tools/executor.js +83 -15
  259. package/dist/tools/executor.js.map +1 -1
  260. package/dist/tools/index.d.ts +3 -1
  261. package/dist/tools/index.d.ts.map +1 -1
  262. package/dist/tools/registry.d.ts +15 -3
  263. package/dist/tools/registry.d.ts.map +1 -1
  264. package/dist/tools/registry.js +21 -2
  265. package/dist/tools/registry.js.map +1 -1
  266. package/package.json +58 -4
package/README.md CHANGED
@@ -1,73 +1,34 @@
1
1
  # FAgent Agent SDK
2
2
 
3
- FAgent is a composable TypeScript SDK for building LLM agents, tool-calling
4
- workflows, and RAG-powered chat systems.
3
+ FAgent Agent SDK 是一个可组合的 TypeScript Agent 运行时基础库,用于在第三方系统中接入:
5
4
 
6
- The published npm package is:
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
- Use it as the runtime core behind a web chat product, backend API, worker, or
13
- third-party integration layer. The SDK does not include a web UI, database
14
- migrations for your application, authentication, or deployment code.
15
-
16
- ## Capabilities
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
- ## Environment variables
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
- ### Suggested HTTP contract
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 handleChatRequest(body: {
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
- Recommended request body:
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
- ```json
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
- Recommended response body:
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
- ```json
143
- {
144
- "message": "Open the account settings page and choose Reset password.",
145
- "usage": {
146
- "promptTokens": 120,
147
- "completionTokens": 30,
148
- "totalTokens": 150
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
- ## Sessions and message history
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
- `Agent` is reusable. `sessionId` selects a conversation context.
302
+ 工具调用流式会额外产生:
156
303
 
157
304
  ```ts
158
- await agent.run('Hi', { sessionId: 'tenant_1:user_42:conversation_a' });
159
- await agent.run('Start another conversation', {
160
- sessionId: 'tenant_1:user_42:conversation_b',
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
- Runs from different sessions can proceed concurrently. Runs for the same session
165
- are serialized so message order remains stable.
334
+ 也可使用 base64:
166
335
 
167
- For production APIs, set `requireSessionId: true` and pass an explicit
168
- tenant/user/conversation scoped session id. The built-in
169
- `InMemoryMessageHistoryStore` is suitable for development and tests. Production
170
- systems should provide a persistent `MessageHistoryStore`.
336
+ ```ts
337
+ import { imageBase64Block } from '@fllf/agent-sdk';
338
+
339
+ const image = imageBase64Block('image/png', base64Data);
340
+ ```
171
341
 
172
- ## Tool calling
342
+ Provider 行为:
173
343
 
174
- Tools are defined with Zod schemas. The SDK converts the schema into an
175
- OpenAI-compatible tool schema and validates model-supplied arguments before
176
- running the tool.
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
- console.log(result.output);
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
- ## RAG integration
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
- RAG is exposed as an independent subsystem. Agent code only depends on
218
- `RagPipeline` through either:
421
+ const llm = new LLM({
422
+ provider: 'anthropic',
423
+ model: 'claude-opus-4-8',
424
+ });
219
425
 
220
- - `RagExecutor`: always retrieve before answering. Use this for knowledge-base
221
- QA.
222
- - `RagSearchTool`: let a tool-calling agent retrieve evidence only when the
223
- model decides it needs knowledge-base context.
426
+ const response = await llm.chat({
427
+ messages: [{ role: 'user', content: 'Say hello.' }],
428
+ maxTokens: 256,
429
+ });
224
430
 
225
- ### Build an in-memory RAG pipeline
431
+ console.log(response.content);
432
+ ```
226
433
 
227
- This example is suitable for local development, tests, or a minimal MVP. Replace
228
- the stores with Postgres, pgvector, Qdrant, Elasticsearch, or your own adapters
229
- for production.
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: new HybridRetriever({
276
- denseRetriever,
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
- `OpenAIEmbedder` and `OpenAICompatibleEmbedder` send embedding requests with
288
- `encoding_format: "float"` and validate that the returned embedding length
289
- matches `embedder.dimensions`. When using a third-party OpenAI-compatible
290
- embedding model whose dimension is not known by the SDK, pass `dimensions`
291
- explicitly and create the vector store with the same value:
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
- If you change the embedding model or dimensions, build a new vector collection
312
- or re-ingest documents. Vectors from different models or dimensions must not be
313
- mixed in the same index.
729
+ 未知 embedding 模型必须显式传 `dimensions`。更换 embedding 模型或维度时,应新建向量集合或重新 ingestion,不能混用不同维度/模型的向量。
730
+
731
+ 可用 `EmbeddingCache` 缓存 embedding:
314
732
 
315
- ### Ingest documents
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
- Your application should run ingestion from an admin API, background worker, or
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
- Important ingestion fields:
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
- - `source`: stable source path, URL, or document id shown in citations.
334
- - `tenantId`: tenant boundary.
335
- - `knowledgeBaseId`: knowledge-base boundary.
336
- - `acl`: access-control labels used at retrieval time.
337
- - `replaceExisting`: delete previous chunks and indexes for the same document id
338
- before writing new ones.
810
+ ```ts
811
+ import {
812
+ createPostgresRagSchemaSql,
813
+ PgVectorStore,
814
+ PostgresDocumentStore,
815
+ PostgresKeywordStore,
816
+ } from '@fllf/agent-sdk';
339
817
 
340
- ### Knowledge-base QA agent
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
- console.log(result.output);
367
- console.log(result.raw);
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
- `result.raw` is the `RagAnswer`:
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: unknown[];
387
- usage?: unknown;
892
+ retrieved: RetrievedChunk[];
893
+ usage?: TokenUsage;
388
894
  metadata?: Record<string, unknown>;
389
895
  };
390
896
  ```
391
897
 
392
- Recommended RAG chat response:
898
+ ### RAG 作为可选工具
393
899
 
394
- ```ts
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
- ## Structured output
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 { z } from 'zod';
435
- import { LLM } from '@fllf/agent-sdk';
935
+ import { AdvancedSearchTool } from '@fllf/agent-sdk';
436
936
 
437
- const llm = new LLM();
937
+ const tools = [new AdvancedSearchTool({ logger })];
938
+ ```
438
939
 
439
- const result = await llm.generateObject({
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
- console.log(result.value);
942
+ ```bash
943
+ TAVILY_API_KEY=...
944
+ SERPAPI_API_KEY=...
450
945
  ```
451
946
 
452
- ## Observability
947
+ 若两个 key 都未配置,工具会返回提示文本,不会抛出配置错误。该工具会真实调用外部搜索 API,第三方系统应自行处理网络权限、配额、审计和结果可信度。
453
948
 
454
- The SDK emits structured runtime events and does not print by default.
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
- Event types include:
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
- Forward events to your logging or tracing system:
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
- ## Production integration checklist
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
- The SDK provides runtime primitives. A complete product should add:
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
- - Persistent users, conversations, messages, and audit logs.
494
- - A persistent `MessageHistoryStore`.
495
- - Persistent RAG stores and a vector/search backend.
496
- - AuthN/AuthZ before `Agent.run()` or `RagPipeline.retrieve()`.
497
- - Tenant, knowledge-base, and ACL filters in every RAG request.
498
- - Background document ingestion and index rebuild jobs.
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
- ## Public API map
1087
+ ## 包导出
504
1088
 
505
- All public APIs are exported from the package root:
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
- Main areas:
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
- - `agent`: runtime composition.
522
- - `llm`: provider abstraction and structured output.
523
- - `history`: session-scoped message history interfaces and in-memory store.
524
- - `tools`: Zod-driven tools and tool execution.
525
- - `executors`: simple chat, tool calling, and RAG execution strategies.
526
- - `rag`: ingestion, chunking, embeddings, stores, retrieval, generation, and
527
- verification.
528
- - `observability`: observer interface and console observer.
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
- ## Development
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
- Examples:
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
- During local development, examples may import from `../src` so they can run
548
- without building first.
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
+ ```