@fllf/agent-sdk 0.1.1 → 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 (270) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +844 -184
  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/embeddings/openai-compatible-embedder.d.ts +22 -4
  179. package/dist/rag/embeddings/openai-compatible-embedder.d.ts.map +1 -1
  180. package/dist/rag/embeddings/openai-compatible-embedder.js +108 -14
  181. package/dist/rag/embeddings/openai-compatible-embedder.js.map +1 -1
  182. package/dist/rag/generation/context-builder.d.ts +7 -0
  183. package/dist/rag/generation/context-builder.d.ts.map +1 -1
  184. package/dist/rag/generation/context-builder.js +4 -1
  185. package/dist/rag/generation/context-builder.js.map +1 -1
  186. package/dist/rag/ingestion/metadata.d.ts +6 -1
  187. package/dist/rag/ingestion/metadata.d.ts.map +1 -1
  188. package/dist/rag/ingestion/metadata.js +6 -2
  189. package/dist/rag/ingestion/metadata.js.map +1 -1
  190. package/dist/rag/pipeline.d.ts.map +1 -1
  191. package/dist/rag/pipeline.js +34 -11
  192. package/dist/rag/pipeline.js.map +1 -1
  193. package/dist/rag/retrieval/index.d.ts +1 -0
  194. package/dist/rag/retrieval/index.d.ts.map +1 -1
  195. package/dist/rag/retrieval/index.js +1 -0
  196. package/dist/rag/retrieval/index.js.map +1 -1
  197. package/dist/rag/retrieval/parent-child-expanding-retriever.d.ts +31 -0
  198. package/dist/rag/retrieval/parent-child-expanding-retriever.d.ts.map +1 -0
  199. package/dist/rag/retrieval/parent-child-expanding-retriever.js +194 -0
  200. package/dist/rag/retrieval/parent-child-expanding-retriever.js.map +1 -0
  201. package/dist/rag/stores/in-memory-keyword-store.d.ts.map +1 -1
  202. package/dist/rag/stores/in-memory-keyword-store.js +3 -8
  203. package/dist/rag/stores/in-memory-keyword-store.js.map +1 -1
  204. package/dist/rag/stores/keyword-tokenizer.d.ts +4 -0
  205. package/dist/rag/stores/keyword-tokenizer.d.ts.map +1 -0
  206. package/dist/rag/stores/keyword-tokenizer.js +113 -0
  207. package/dist/rag/stores/keyword-tokenizer.js.map +1 -0
  208. package/dist/rag/stores/postgres/pg-vector-store.d.ts.map +1 -1
  209. package/dist/rag/stores/postgres/pg-vector-store.js +21 -8
  210. package/dist/rag/stores/postgres/pg-vector-store.js.map +1 -1
  211. package/dist/rag/stores/postgres/postgres-document-store.d.ts.map +1 -1
  212. package/dist/rag/stores/postgres/postgres-document-store.js +26 -13
  213. package/dist/rag/stores/postgres/postgres-document-store.js.map +1 -1
  214. package/dist/rag/stores/postgres/postgres-keyword-store.d.ts.map +1 -1
  215. package/dist/rag/stores/postgres/postgres-keyword-store.js +43 -43
  216. package/dist/rag/stores/postgres/postgres-keyword-store.js.map +1 -1
  217. package/dist/rag/stores/postgres/sql.d.ts +22 -0
  218. package/dist/rag/stores/postgres/sql.d.ts.map +1 -1
  219. package/dist/rag/stores/postgres/sql.js +42 -0
  220. package/dist/rag/stores/postgres/sql.js.map +1 -1
  221. package/dist/rag/stores/types.d.ts +5 -0
  222. package/dist/rag/stores/types.d.ts.map +1 -1
  223. package/dist/rag/types.d.ts +6 -0
  224. package/dist/rag/types.d.ts.map +1 -1
  225. package/dist/testing/agent.d.ts +11 -0
  226. package/dist/testing/agent.d.ts.map +1 -0
  227. package/dist/testing/agent.js +45 -0
  228. package/dist/testing/agent.js.map +1 -0
  229. package/dist/testing/history-contract.d.ts +12 -0
  230. package/dist/testing/history-contract.d.ts.map +1 -0
  231. package/dist/testing/history-contract.js +111 -0
  232. package/dist/testing/history-contract.js.map +1 -0
  233. package/dist/testing/index.d.ts +11 -0
  234. package/dist/testing/index.d.ts.map +1 -0
  235. package/dist/testing/index.js +16 -0
  236. package/dist/testing/index.js.map +1 -0
  237. package/dist/testing/models.d.ts +34 -0
  238. package/dist/testing/models.d.ts.map +1 -0
  239. package/dist/testing/models.js +74 -0
  240. package/dist/testing/models.js.map +1 -0
  241. package/dist/testing/tool-schema.d.ts +6 -0
  242. package/dist/testing/tool-schema.d.ts.map +1 -0
  243. package/dist/testing/tool-schema.js +29 -0
  244. package/dist/testing/tool-schema.js.map +1 -0
  245. package/dist/testing/tools.d.ts +21 -0
  246. package/dist/testing/tools.d.ts.map +1 -0
  247. package/dist/testing/tools.js +43 -0
  248. package/dist/testing/tools.js.map +1 -0
  249. package/dist/tools/base.d.ts +38 -7
  250. package/dist/tools/base.d.ts.map +1 -1
  251. package/dist/tools/base.js +238 -11
  252. package/dist/tools/base.js.map +1 -1
  253. package/dist/tools/builtin/advancedSearchTool.d.ts.map +1 -1
  254. package/dist/tools/builtin/advancedSearchTool.js +30 -4
  255. package/dist/tools/builtin/advancedSearchTool.js.map +1 -1
  256. package/dist/tools/builtin/ragSearchTool.d.ts +6 -3
  257. package/dist/tools/builtin/ragSearchTool.d.ts.map +1 -1
  258. package/dist/tools/builtin/ragSearchTool.js +8 -6
  259. package/dist/tools/builtin/ragSearchTool.js.map +1 -1
  260. package/dist/tools/executor.d.ts +30 -2
  261. package/dist/tools/executor.d.ts.map +1 -1
  262. package/dist/tools/executor.js +83 -15
  263. package/dist/tools/executor.js.map +1 -1
  264. package/dist/tools/index.d.ts +3 -1
  265. package/dist/tools/index.d.ts.map +1 -1
  266. package/dist/tools/registry.d.ts +15 -3
  267. package/dist/tools/registry.d.ts.map +1 -1
  268. package/dist/tools/registry.js +21 -2
  269. package/dist/tools/registry.js.map +1 -1
  270. package/package.json +58 -4
package/README.md CHANGED
@@ -1,67 +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_EMBEDDING_MODEL=text-embedding-3-small
51
- ```
52
-
53
- OpenAI-compatible local runtime, such as Ollama, LM Studio, vLLM, or a gateway:
54
-
55
- ```bash
56
- LOCAL_BASE_URL=http://127.0.0.1:11434/v1
57
- LOCAL_MODEL=llama3
58
- LOCAL_API_KEY=local-key
59
- ```
60
-
61
- If no OpenAI key is configured, the default `LLM` auto-detection falls back to
62
- the local OpenAI-compatible provider.
63
-
64
- ## Basic chat integration
31
+ ## 快速开始
65
32
 
66
33
  ```ts
67
34
  import { Agent } from '@fllf/agent-sdk';
@@ -81,13 +48,10 @@ const result = await agent.run('How do I reset my password?', {
81
48
  });
82
49
 
83
50
  console.log(result.output);
51
+ console.log(result.usage);
84
52
  ```
85
53
 
86
- ### Suggested HTTP contract
87
-
88
- Your API layer owns authentication, authorization, persistence, rate limits, and
89
- request validation. A typical chat endpoint can adapt incoming requests to
90
- `Agent.run()`:
54
+ 第三方 HTTP 服务通常只需要把自己的认证、租户、会话和用户输入转换为 `Agent.run()`:
91
55
 
92
56
  ```ts
93
57
  import { Agent } from '@fllf/agent-sdk';
@@ -97,10 +61,10 @@ const agent = new Agent({
97
61
  requireSessionId: true,
98
62
  });
99
63
 
100
- export async function handleChatRequest(body: {
101
- conversationId: string;
102
- userId: string;
64
+ export async function handleChat(body: {
103
65
  tenantId: string;
66
+ userId: string;
67
+ conversationId: string;
104
68
  message: string;
105
69
  }) {
106
70
  const sessionId = `${body.tenantId}:${body.userId}:${body.conversationId}`;
@@ -110,6 +74,7 @@ export async function handleChatRequest(body: {
110
74
  metadata: {
111
75
  tenantId: body.tenantId,
112
76
  userId: body.userId,
77
+ conversationId: body.conversationId,
113
78
  },
114
79
  });
115
80
 
@@ -120,54 +85,269 @@ export async function handleChatRequest(body: {
120
85
  }
121
86
  ```
122
87
 
123
- 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 运行参数
124
137
 
125
- ```json
126
- {
127
- "conversationId": "conversation_1001",
128
- "userId": "user_42",
129
- "tenantId": "tenant_1",
130
- "message": "How do I reset my password?"
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
+ });
163
+
164
+ if (config.envWarnings.length > 0) {
165
+ // 按业务策略决定继续启动或失败退出。
131
166
  }
132
167
  ```
133
168
 
134
- 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 可并发。
260
+
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' });
135
273
 
136
- ```json
137
- {
138
- "message": "Open the account settings page and choose Reset password.",
139
- "usage": {
140
- "promptTokens": 120,
141
- "completionTokens": 30,
142
- "totalTokens": 150
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);
143
282
  }
144
283
  }
145
284
  ```
146
285
 
147
- ## 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
+ 流式行为:
148
296
 
149
- `Agent` is reusable. `sessionId` selects a conversation context.
297
+ - `SimpleChatExecutor` 支持流式,但要求模型实现 `stream()` 并声明 streaming capability。
298
+ - `ToolCallingExecutor` 支持工具调用流式;模型支持流式时聚合 tool call delta,模型不支持流式时会退化为一次非流式 `chat()` 并整体输出。
299
+ - `RagExecutor` 当前只实现非流式 `run()`。
300
+ - 流式成功完成后才提交本轮 user/assistant 消息;中途失败或 abort 时不会提交未完成的 simple-chat 轮次。
301
+
302
+ 工具调用流式会额外产生:
150
303
 
151
304
  ```ts
152
- await agent.run('Hi', { sessionId: 'tenant_1:user_42:conversation_a' });
153
- await agent.run('Start another conversation', {
154
- 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',
155
331
  });
156
332
  ```
157
333
 
158
- Runs from different sessions can proceed concurrently. Runs for the same session
159
- are serialized so message order remains stable.
334
+ 也可使用 base64:
335
+
336
+ ```ts
337
+ import { imageBase64Block } from '@fllf/agent-sdk';
338
+
339
+ const image = imageBase64Block('image/png', base64Data);
340
+ ```
341
+
342
+ Provider 行为:
160
343
 
161
- For production APIs, set `requireSessionId: true` and pass an explicit
162
- tenant/user/conversation scoped session id. The built-in
163
- `InMemoryMessageHistoryStore` is suitable for development and tests. Production
164
- systems should provide a persistent `MessageHistoryStore`.
344
+ - OpenAI/OpenAI-compatible:user 多模态映射为 `image_url`;assistant/tool 非文本内容会降级为文本占位。
345
+ - Anthropic:user 图片映射为 Anthropic image block;tool 结果可携带 `is_error`。
346
+ - 不支持 vision provider 会在 capability 检查或 provider 侧失败;第三方系统应按模型能力选择输入。
165
347
 
166
- ## Tool calling
348
+ ## 工具调用
167
349
 
168
- Tools are defined with Zod schemas. The SDK converts the schema into an
169
- OpenAI-compatible tool schema and validates model-supplied arguments before
170
- running the tool.
350
+ 工具通过 Zod schema 定义。SDK 会把 schema 转成 provider-neutral tool definition,并在执行前校验模型传入参数。
171
351
 
172
352
  ```ts
173
353
  import { z } from 'zod';
@@ -184,6 +364,8 @@ class AddTool extends Tool<typeof AddToolSchema, number> {
184
364
  name: 'add',
185
365
  description: 'Adds two numbers.',
186
366
  schema: AddToolSchema,
367
+ timeoutMs: 5000,
368
+ errorMode: 'return_to_model',
187
369
  });
188
370
  }
189
371
 
@@ -194,7 +376,7 @@ class AddTool extends Tool<typeof AddToolSchema, number> {
194
376
 
195
377
  const agent = new Agent({
196
378
  name: 'tool-agent',
197
- executor: new ToolCallingExecutor(),
379
+ executor: new ToolCallingExecutor({ maxIterations: 5 }),
198
380
  tools: [new AddTool()],
199
381
  requireSessionId: true,
200
382
  });
@@ -202,25 +384,252 @@ const agent = new Agent({
202
384
  const result = await agent.run('Calculate 19 + 23 with the tool.', {
203
385
  sessionId: 'tenant_1:user_42:conversation_tools',
204
386
  });
387
+ ```
205
388
 
206
- 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 });
408
+ ```
409
+
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';
420
+
421
+ const llm = new LLM({
422
+ provider: 'anthropic',
423
+ model: 'claude-opus-4-8',
424
+ });
425
+
426
+ const response = await llm.chat({
427
+ messages: [{ role: 'user', content: 'Say hello.' }],
428
+ maxTokens: 256,
429
+ });
430
+
431
+ console.log(response.content);
432
+ ```
433
+
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
+ });
207
592
  ```
208
593
 
209
- ## RAG integration
594
+ `SummarizingContextCompressor` 会复用当前 Agent 的 LLM,把被截断的旧消息压缩成一条摘要前缀。摘要失败时自动降级为纯截断,不中断主请求。
210
595
 
211
- RAG is exposed as an independent subsystem. Agent code only depends on
212
- `RagPipeline` through either:
596
+ ## RAG 接入
213
597
 
214
- - `RagExecutor`: always retrieve before answering. Use this for knowledge-base
215
- QA.
216
- - `RagSearchTool`: let a tool-calling agent retrieve evidence only when the
217
- model decides it needs knowledge-base context.
598
+ RAG 子系统通过 `RagPipeline` 暴露三个核心方法:
218
599
 
219
- ### Build an in-memory RAG pipeline
600
+ - `ingest(input)`: 文档解析、标准化、切块、embedding、写入文档库/向量索引/关键词索引。
601
+ - `retrieve(query, options)`: 只返回可引用证据。
602
+ - `answer(query, options)`: 检索、构建上下文、调用模型生成答案、校验引用。
220
603
 
221
- This example is suitable for local development, tests, or a minimal MVP. Replace
222
- the stores with Postgres, pgvector, Qdrant, Elasticsearch, or your own adapters
223
- for production.
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 或自有存储。
224
633
 
225
634
  ```ts
226
635
  import {
@@ -236,6 +645,7 @@ import {
236
645
  InMemoryKeywordStore,
237
646
  InMemoryVectorStore,
238
647
  OpenAIEmbedder,
648
+ ParentChildExpandingRetriever,
239
649
  RuleBasedVerifier,
240
650
  SparseRetriever,
241
651
  } from '@fllf/agent-sdk';
@@ -258,6 +668,17 @@ const sparseRetriever = new SparseRetriever({
258
668
  documentStore,
259
669
  });
260
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
+
261
682
  export const ragPipeline = new DefaultRagPipeline({
262
683
  loader: new AutoDocumentLoader(),
263
684
  normalizer: new DefaultDocumentNormalizer(),
@@ -266,22 +687,59 @@ export const ragPipeline = new DefaultRagPipeline({
266
687
  documentStore,
267
688
  vectorStore,
268
689
  keywordStore,
269
- retriever: new HybridRetriever({
270
- denseRetriever,
271
- sparseRetriever,
272
- }),
273
- contextBuilder: new DefaultContextBuilder({
274
- maxContextTokens: 3000,
275
- }),
690
+ retriever,
691
+ contextBuilder: new DefaultContextBuilder({ maxContextTokens: 3000 }),
276
692
  generator: new DefaultRagGenerator(),
277
693
  verifier: new RuleBasedVerifier(),
278
694
  });
279
695
  ```
280
696
 
281
- ### Ingest documents
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:
710
+
711
+ ```ts
712
+ import {
713
+ InMemoryVectorStore,
714
+ OpenAICompatibleEmbedder,
715
+ } from '@fllf/agent-sdk';
716
+
717
+ const embedder = new OpenAICompatibleEmbedder({
718
+ apiKey: process.env.OPENAI_API_KEY,
719
+ baseURL: 'https://gateway.example.test/v1',
720
+ model: 'bge-m3',
721
+ dimensions: 1024,
722
+ });
723
+
724
+ const vectorStore = new InMemoryVectorStore({
725
+ dimensions: embedder.dimensions,
726
+ });
727
+ ```
728
+
729
+ 未知 embedding 模型必须显式传 `dimensions`。更换 embedding 模型或维度时,应新建向量集合或重新 ingestion,不能混用不同维度/模型的向量。
282
730
 
283
- Your application should run ingestion from an admin API, background worker, or
284
- document synchronization job.
731
+ 可用 `EmbeddingCache` 缓存 embedding:
732
+
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
+ ```
741
+
742
+ ### Ingest 文档
285
743
 
286
744
  ```ts
287
745
  await ragPipeline.ingest({
@@ -294,16 +752,92 @@ await ragPipeline.ingest({
294
752
  });
295
753
  ```
296
754
 
297
- 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 存储
809
+
810
+ ```ts
811
+ import {
812
+ createPostgresRagSchemaSql,
813
+ PgVectorStore,
814
+ PostgresDocumentStore,
815
+ PostgresKeywordStore,
816
+ } from '@fllf/agent-sdk';
817
+
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
+ ```
298
831
 
299
- - `source`: stable source path, URL, or document id shown in citations.
300
- - `tenantId`: tenant boundary.
301
- - `knowledgeBaseId`: knowledge-base boundary.
302
- - `acl`: access-control labels used at retrieval time.
303
- - `replaceExisting`: delete previous chunks and indexes for the same document id
304
- before writing new ones.
832
+ 建表 SQL 默认:
305
833
 
306
- ### Knowledge-base QA agent
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
307
841
 
308
842
  ```ts
309
843
  import { Agent, RagExecutor } from '@fllf/agent-sdk';
@@ -329,11 +863,17 @@ const result = await knowledgeAgent.run('What is the refund window?', {
329
863
  },
330
864
  });
331
865
 
332
- console.log(result.output);
333
- 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
+ };
334
872
  ```
335
873
 
336
- `result.raw` is the `RagAnswer`:
874
+ `RagExecutor` 会从 `metadata` 读取 `tenantId`、`knowledgeBaseId`、`acl`、`documentIds`、`sources`、`metadataFilter`、`topK`、`minScore`、`maxContextTokens`。
875
+
876
+ `RagAnswer` 形状:
337
877
 
338
878
  ```ts
339
879
  type RagAnswer = {
@@ -349,27 +889,15 @@ type RagAnswer = {
349
889
  quote?: string;
350
890
  }>;
351
891
  confidence: 'high' | 'medium' | 'low';
352
- retrieved: unknown[];
353
- usage?: unknown;
892
+ retrieved: RetrievedChunk[];
893
+ usage?: TokenUsage;
354
894
  metadata?: Record<string, unknown>;
355
895
  };
356
896
  ```
357
897
 
358
- Recommended RAG chat response:
359
-
360
- ```ts
361
- return {
362
- message: result.output,
363
- citations: (result.raw as any).citations,
364
- confidence: (result.raw as any).confidence,
365
- usage: result.usage,
366
- };
367
- ```
368
-
369
- ### RAG as an optional search tool
898
+ ### RAG 作为可选工具
370
899
 
371
- Use `RagSearchTool` when the agent should decide whether to search the knowledge
372
- base.
900
+ 模型需要时才检索知识库:
373
901
 
374
902
  ```ts
375
903
  import {
@@ -386,6 +914,9 @@ const agent = new Agent({
386
914
  new RagSearchTool({
387
915
  pipeline: ragPipeline,
388
916
  defaultRetrieveOptions: {
917
+ tenantId: 'tenant_1',
918
+ knowledgeBaseId: 'kb_support',
919
+ acl: ['user:user_42', 'role:support'],
389
920
  topK: 5,
390
921
  },
391
922
  }),
@@ -394,30 +925,53 @@ const agent = new Agent({
394
925
  });
395
926
  ```
396
927
 
397
- ## Structured output
928
+ 安全边界:`RagSearchTool` 不把 `tenantId`、`knowledgeBaseId`、`acl` 暴露给模型参数,这些权限字段只能由服务端通过 `defaultRetrieveOptions` 设置。模型可传的仅是 `query`、`topK`、`minScore`、`documentIds`、`sources`、`metadataFilter` 等检索调优字段。
929
+
930
+ ## 内置搜索工具
931
+
932
+ `AdvancedSearchTool` 可接入 Tavily 和 SerpAPI:
398
933
 
399
934
  ```ts
400
- import { z } from 'zod';
401
- import { LLM } from '@fllf/agent-sdk';
935
+ import { AdvancedSearchTool } from '@fllf/agent-sdk';
402
936
 
403
- const llm = new LLM();
937
+ const tools = [new AdvancedSearchTool({ logger })];
938
+ ```
404
939
 
405
- const result = await llm.generateObject({
406
- messages: [
407
- { role: 'user', content: 'Return a short project summary.' },
408
- ],
409
- schema: z.object({
410
- summary: z.string(),
411
- confidence: z.number(),
412
- }),
413
- });
940
+ 环境变量:
414
941
 
415
- console.log(result.value);
942
+ ```bash
943
+ TAVILY_API_KEY=...
944
+ SERPAPI_API_KEY=...
945
+ ```
946
+
947
+ 若两个 key 都未配置,工具会返回提示文本,不会抛出配置错误。该工具会真实调用外部搜索 API,第三方系统应自行处理网络权限、配额、审计和结果可信度。
948
+
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
+ });
416
968
  ```
417
969
 
418
- ## Observability
970
+ `beforeRun` 失败会中止 run。`afterRun` 和 `onRunError` 失败不会替换主结果/主错误,会作为 warn 级 `agent:error` observer event 发出。
971
+
972
+ ## 可观测性
419
973
 
420
- The SDK emits structured runtime events and does not print by default.
974
+ SDK 默认不打印日志。接入方可通过 observer 收集事件。
421
975
 
422
976
  ```ts
423
977
  import { Agent, ConsoleObserver } from '@fllf/agent-sdk';
@@ -430,7 +984,7 @@ const agent = new Agent({
430
984
  await agent.run('Hello');
431
985
  ```
432
986
 
433
- Event types include:
987
+ 事件类型:
434
988
 
435
989
  - `agent:start`
436
990
  - `agent:end`
@@ -438,37 +992,101 @@ Event types include:
438
992
  - `llm:start`
439
993
  - `llm:end`
440
994
  - `llm:error`
995
+ - `llm:retry`
441
996
  - `tool:start`
442
997
  - `tool:end`
443
998
  - `tool:error`
444
999
 
445
- Forward events to your logging or tracing system:
1000
+ 自定义 observer:
446
1001
 
447
1002
  ```ts
448
1003
  const observer = {
1004
+ redactEvent(event) {
1005
+ return {
1006
+ ...event,
1007
+ metadata: undefined,
1008
+ };
1009
+ },
449
1010
  onEvent(event) {
450
1011
  logger.info({ event }, 'agent runtime event');
451
1012
  },
452
1013
  };
453
1014
  ```
454
1015
 
455
- ## 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` 事件。
456
1061
 
457
- The SDK provides runtime primitives. A complete product should add:
1062
+ ## 测试辅助工具
458
1063
 
459
- - Persistent users, conversations, messages, and audit logs.
460
- - A persistent `MessageHistoryStore`.
461
- - Persistent RAG stores and a vector/search backend.
462
- - AuthN/AuthZ before `Agent.run()` or `RagPipeline.retrieve()`.
463
- - Tenant, knowledge-base, and ACL filters in every RAG request.
464
- - Background document ingestion and index rebuild jobs.
465
- - Rate limits, model cost controls, and request timeouts.
466
- - Redaction or filtering for logs that may contain user messages or document
467
- snippets.
1064
+ 下游系统可使用 SDK 提供的测试工具编写稳定集成测试:
468
1065
 
469
- ## Public API map
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
+ 用途:
470
1079
 
471
- All public APIs are exported from the package root:
1080
+ - `ScriptedChatModel`: 队列化返回固定模型响应,并记录请求。
1081
+ - `FakeChatModel`: 回显最后一条 user 消息。
1082
+ - `FakeTool`: 校验输入并记录调用。
1083
+ - `createTestAgent()`: 快速创建测试 Agent。
1084
+ - `createToolSchemaSnapshot()` / `assertToolSchemaSnapshot()`: 对工具暴露 schema 做快照测试。
1085
+ - `assertMessageHistoryStoreContract()`: 校验自定义历史存储满足 session 隔离、窗口读取、防御性拷贝、可选 CAS 语义等契约。
1086
+
1087
+ ## 包导出
1088
+
1089
+ 推荐从根包导入稳定 API:
472
1090
 
473
1091
  ```ts
474
1092
  import {
@@ -482,33 +1100,75 @@ import {
482
1100
  } from '@fllf/agent-sdk';
483
1101
  ```
484
1102
 
485
- Main areas:
1103
+ 也支持子路径导入:
486
1104
 
487
- - `agent`: runtime composition.
488
- - `llm`: provider abstraction and structured output.
489
- - `history`: session-scoped message history interfaces and in-memory store.
490
- - `tools`: Zod-driven tools and tool execution.
491
- - `executors`: simple chat, tool calling, and RAG execution strategies.
492
- - `rag`: ingestion, chunking, embeddings, stores, retrieval, generation, and
493
- verification.
494
- - `observability`: observer interface and console observer.
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
+ ## 生产接入清单
1131
+
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 模型或维度后重建向量索引。
495
1141
 
496
- ## Development
1142
+ ## 本仓库开发命令
497
1143
 
498
1144
  ```bash
499
1145
  npm install
500
1146
  npm run typecheck
501
1147
  npm test
502
1148
  npm run build
1149
+ npm run test:exports
503
1150
  ```
504
1151
 
505
- Examples:
1152
+ 示例:
506
1153
 
507
1154
  ```bash
508
1155
  npm run example:simple-chat
1156
+ npm run example:streaming-chat
509
1157
  npm run example:tool-calling
1158
+ npm run example:tool-error-policy
510
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
511
1164
  ```
512
1165
 
513
- During local development, examples may import from `../src` so they can run
514
- 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
+ ```