@ubean/ai 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -0
- package/README.zh-CN.md +251 -0
- package/package.json +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# @ubean/ai
|
|
2
|
+
|
|
3
|
+
**English | [中文](./README.zh-CN.md)**
|
|
4
|
+
|
|
5
|
+
AI large-model integration for the ubean framework — a thin orchestration layer over the Vercel AI SDK.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { defineAgent, defineAgentTool, v } from '@ubean/ai';
|
|
9
|
+
|
|
10
|
+
const weather = defineAgentTool({
|
|
11
|
+
name: 'get_weather',
|
|
12
|
+
description: 'Get the weather for a city',
|
|
13
|
+
input: v.object({ city: v.string() }),
|
|
14
|
+
execute: async ({ city }) => ({ city, temperature: 21 })
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const agent = defineAgent({ model: 'deepseek-chat', tools: { weather } });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Table of Contents
|
|
21
|
+
|
|
22
|
+
- [@ubean/ai](#ubeanai)
|
|
23
|
+
- [Package Positioning](#package-positioning)
|
|
24
|
+
- [Install](#install)
|
|
25
|
+
- [Quick Start](#quick-start)
|
|
26
|
+
- [Agent Kernel](#agent-kernel)
|
|
27
|
+
- [Vue Runtime](#vue-runtime)
|
|
28
|
+
- [Gateway Presets](#gateway-presets)
|
|
29
|
+
- [Vite Plugin](#vite-plugin)
|
|
30
|
+
- [Type Safety](#type-safety)
|
|
31
|
+
- [Architecture Notes](#architecture-notes)
|
|
32
|
+
|
|
33
|
+
## Package Positioning
|
|
34
|
+
|
|
35
|
+
| Layer | Responsibility |
|
|
36
|
+
| ----------------------- | ----------------------------------------------------------------------------------- |
|
|
37
|
+
| `@ubean/ai` (kernel) | Provider registry, agent definition, tool definition, streaming chunk protocol |
|
|
38
|
+
| `@ubean/ai/runtime/vue` | Vue composables that consume the SSE stream an agent exposes |
|
|
39
|
+
| `@ubean/ai/gateway` | Provider presets only — base URLs and ids for common hosted providers |
|
|
40
|
+
| `@ubean/ai/vite` | Module wiring: registers providers, auto-imports the composables, adds optimizeDeps |
|
|
41
|
+
|
|
42
|
+
**Included:**
|
|
43
|
+
|
|
44
|
+
- `defineProvider` / `configureAI` / `resolveModel` — resolve a `model` string to a concrete provider + model id.
|
|
45
|
+
- `defineAgent` — declares system prompt, model, tools, step budget and sampling knobs; returns a `UbeanAgent` with `run()`, `stream()` and `asHandler()`.
|
|
46
|
+
- `defineAgentTool` — a type-safe identity helper that keeps the Valibot schema's inferred input type on `execute(args, ctx)`.
|
|
47
|
+
- `v` — a namespace re-export of **valibot**, used to author tool input schemas.
|
|
48
|
+
- `useChat` / `useAgent` / `useAIProvider` — the Vue runtime.
|
|
49
|
+
- Provider presets for DeepSeek, OpenRouter, OmniRoute, OpenAI, Anthropic, Google and Groq.
|
|
50
|
+
|
|
51
|
+
**NOT included** (deliberately):
|
|
52
|
+
|
|
53
|
+
- No gateway service. `@ubean/ai/gateway` ships provider _presets_, not routing, fallback or cost optimization — put an external gateway (for example OmniRoute) in front if you need those.
|
|
54
|
+
- No UI layer. Markdown rendering, code highlighting and message bubbles are out of scope; pick your own renderer in the `useChat` consumer.
|
|
55
|
+
- No agent loop implementation. The tool-calling loop, retries and streaming come from the Vercel AI SDK (`ai`); ubean only declares and orchestrates.
|
|
56
|
+
- No HTTP route files. The `asHandler()` result is a plain Hono handler — mount it in `src/routes/` yourself, the way `@ubean/routes` documents API routes.
|
|
57
|
+
|
|
58
|
+
**Runtime dependency whitelist (main entry):** `@valibot/to-json-schema`, `valibot`. The AI SDK itself (`ai`, `@ai-sdk/openai-compatible`) is loaded lazily.
|
|
59
|
+
|
|
60
|
+
**Optional peers:** `ai`, `@ai-sdk/openai-compatible`, `hono`, `vite`, `vue`. Every peer is optional — the package builds and imports without them. If the AI SDK is missing when an agent actually runs, the call rejects with an install hint:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[@ubean/ai] The AI SDK packages are not installed. Install them with:
|
|
64
|
+
pnpm add ai @ai-sdk/openai-compatible (or npm/yarn/bun equivalent).
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# the ubean-side orchestration layer
|
|
71
|
+
pnpm add @ubean/ai
|
|
72
|
+
|
|
73
|
+
# the AI SDK runtime — required at agent execution time
|
|
74
|
+
pnpm add ai @ai-sdk/openai-compatible
|
|
75
|
+
|
|
76
|
+
# peers you only need if you use that part of the surface
|
|
77
|
+
pnpm add vue # @ubean/ai/runtime/vue
|
|
78
|
+
pnpm add hono # UbeanAgent.asHandler()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
**1. Enable the module**
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// ubean.config.ts
|
|
87
|
+
import { defineConfig } from 'ubean';
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
ai: true // or ai: { defaultProvider: 'deepseek', defaultModel: 'deepseek-chat' }
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**2. Define a tool**
|
|
95
|
+
|
|
96
|
+
Tool input is a Valibot schema, so `execute` receives a fully inferred argument object.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// src/server/ai/tools.ts
|
|
100
|
+
import { defineAgentTool, v } from '@ubean/ai';
|
|
101
|
+
|
|
102
|
+
export const searchDocs = defineAgentTool({
|
|
103
|
+
name: 'search_docs',
|
|
104
|
+
description: 'Search the documentation index for a query',
|
|
105
|
+
input: v.object({
|
|
106
|
+
query: v.string(),
|
|
107
|
+
limit: v.optional(v.pipe(v.number(), v.integer()), 5)
|
|
108
|
+
}),
|
|
109
|
+
execute: async ({ query, limit }, ctx) => {
|
|
110
|
+
ctx.signal?.throwIfAborted();
|
|
111
|
+
return { query, limit, hits: [] };
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**3. Define an agent**
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// src/server/ai/agent.ts
|
|
120
|
+
import { defineAgent } from '@ubean/ai';
|
|
121
|
+
import { searchDocs } from './tools';
|
|
122
|
+
|
|
123
|
+
export const supportAgent = defineAgent<{ answer: string }>({
|
|
124
|
+
model: 'deepseek-chat',
|
|
125
|
+
system: 'You are a documentation assistant. Cite the docs you used.',
|
|
126
|
+
tools: { searchDocs },
|
|
127
|
+
maxSteps: 5
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**4. Expose it over HTTP**
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// src/routes/api/chat.ts
|
|
135
|
+
import { supportAgent } from '~/server/ai/agent';
|
|
136
|
+
|
|
137
|
+
export const POST = supportAgent.asHandler();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`asHandler()` reads the request body as an `AgentInput` and responds with a Server-Sent Events stream of `AgentStreamChunk` frames.
|
|
141
|
+
|
|
142
|
+
## Agent Kernel
|
|
143
|
+
|
|
144
|
+
`defineAgent(config)` accepts a model string, a system prompt, a tool set, and optional sampling knobs (`temperature`, `maxTokens`, `headers`, `provider`, `context`). It returns a `UbeanAgent`:
|
|
145
|
+
|
|
146
|
+
| Member | Description |
|
|
147
|
+
| --------------- | ---------------------------------------------------------------------------------------- |
|
|
148
|
+
| `config` | The resolved agent configuration. |
|
|
149
|
+
| `run(input)` | Await the full result: `{ text, output?, toolResults, usage? }`. |
|
|
150
|
+
| `stream(input)` | `AsyncIterable<AgentStreamChunk>` — text deltas, tool calls, tool results, errors, done. |
|
|
151
|
+
| `asHandler()` | A Hono `Handler` that streams the agent over SSE. |
|
|
152
|
+
|
|
153
|
+
Providers are resolved by `model` string. Register one with `defineProvider`, or set a default through `configureAI`:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { configureAI, defineProvider, resolveModel } from '@ubean/ai';
|
|
157
|
+
|
|
158
|
+
defineProvider({
|
|
159
|
+
id: 'my-proxy',
|
|
160
|
+
kind: 'openai-compatible',
|
|
161
|
+
baseURL: 'https://gateway.internal/v1',
|
|
162
|
+
apiKey: process.env.MY_PROXY_KEY,
|
|
163
|
+
modelAliases: { chat: 'deepseek-chat' }
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
configureAI({ defaultProvider: 'my-proxy', defaultModel: 'chat' });
|
|
167
|
+
|
|
168
|
+
const { provider, modelId } = resolveModel('chat');
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`defineProvider` is idempotent per `id` and ignores ids containing `/` (those are treated as explicit `provider/model` strings). When no provider matches, `resolveModel` throws:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
[@ubean/ai] No provider resolved for "<model>". Define a provider via defineProvider() or configure a default with configureAI().
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If nothing is configured, an environment fallback provider (`id: 'env'`, `kind: 'openai-compatible'`) is used, taking its key from `DEEPSEEK_API_KEY || UBEAN_AI_API_KEY || OPENAI_API_KEY` and its base URL from `UBEAN_AI_API_BASE` (default `https://api.deepseek.com/v1`).
|
|
178
|
+
|
|
179
|
+
## Vue Runtime
|
|
180
|
+
|
|
181
|
+
`@ubean/ai/runtime/vue` is a self-built, SSR-safe SSE consumer. It does not depend on `@ai-sdk/vue`.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { useChat } from '@ubean/ai/runtime/vue';
|
|
185
|
+
|
|
186
|
+
const { messages, input, setInput, sendMessage, status, error, isStreaming, abort, reload } = useChat({
|
|
187
|
+
url: '/api/chat'
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
| Composable | Returns |
|
|
192
|
+
| ------------------ | --------------------------------------------------------------------------------------------- |
|
|
193
|
+
| `useChat(options)` | `{ messages, status, error, input, data, setInput, sendMessage, abort, reload, isStreaming }` |
|
|
194
|
+
| `useAgent(agent)` | `{ isStreaming, error, result, chunks, run, stream }` — drives an in-process `UbeanAgent` |
|
|
195
|
+
| `useAIProvider()` | `{ defaultProvider?, defaultModel? }` — the runtime provider configuration |
|
|
196
|
+
|
|
197
|
+
`useChat` defaults its `url` to the relative `./chat`, POSTs `{ ...body, messages }`, and parses `AgentStreamChunk` frames into `ChatMessage[]`. Set the runtime provider defaults with `setAIProviderConfig({ defaultProvider, defaultModel })`.
|
|
198
|
+
|
|
199
|
+
## Gateway Presets
|
|
200
|
+
|
|
201
|
+
`@ubean/ai/gateway` exports provider definitions you can register directly:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { defineProvider } from '@ubean/ai';
|
|
205
|
+
import { deepseek, openrouter, allGatewayProviders } from '@ubean/ai/gateway';
|
|
206
|
+
|
|
207
|
+
for (const provider of allGatewayProviders) defineProvider(provider);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| Export | Provider id | Base URL |
|
|
211
|
+
| ------------ | ------------ | ------------------------------ |
|
|
212
|
+
| `deepseek` | `deepseek` | `https://api.deepseek.com/v1` |
|
|
213
|
+
| `openrouter` | `openrouter` | `https://openrouter.ai/api/v1` |
|
|
214
|
+
| `omniRoute` | `omniroute` | `http://localhost:8080/v1` |
|
|
215
|
+
| `openai` | `openai` | `https://api.openai.com/v1` |
|
|
216
|
+
| `anthropic` | `anthropic` | — |
|
|
217
|
+
| `google` | `google` | — |
|
|
218
|
+
| `groq` | `groq` | — |
|
|
219
|
+
|
|
220
|
+
> This subpath provides presets only. It does not implement gateway-level routing, fallback or cost optimization — that belongs to an external gateway such as OmniRoute.
|
|
221
|
+
|
|
222
|
+
## Vite Plugin
|
|
223
|
+
|
|
224
|
+
The `ai` field in `ubean.config.ts` wires the module for you. When you need explicit control, use the plugin directly:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { ubeanAiPlugin, defineAiConfig } from '@ubean/ai/vite';
|
|
228
|
+
|
|
229
|
+
export default defineConfig({
|
|
230
|
+
plugins: [ubeanAiPlugin(defineAiConfig({ defaultProvider: 'deepseek' }))]
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The plugin registers the provider presets via `defineProvider`, registers a component resolver for `useChat` / `useAgent` / `useAIProvider` from `@ubean/ai/runtime/vue`, generates `virtual:ubean-ai/runtime`, and adds the runtime packages to `optimizeDeps.include`. It does **not** start a dev gateway proxy.
|
|
235
|
+
|
|
236
|
+
## Type Safety
|
|
237
|
+
|
|
238
|
+
- `AgentTool<TInput>` carries the Valibot schema, so `execute(args, ctx)` is inferred from `input` — no casts, no manual generics.
|
|
239
|
+
- `defineAgentTool` is an identity function whose only job is to preserve that inference.
|
|
240
|
+
- `defineAgent<T>(config)` types `AgentRunResult<T>.output` and `AgentRunResult<T>.text`.
|
|
241
|
+
- `AgentStreamChunk` is a discriminated union on `type` (`'text-delta' | 'tool-call' | 'tool-result' | 'error' | 'done'`), so exhaustive switches work in the `stream()` consumer.
|
|
242
|
+
- `UbeanAgent.asHandler()` returns a Hono `Handler`, which keeps route typing intact when mounted from `src/routes/`.
|
|
243
|
+
|
|
244
|
+
## Architecture Notes
|
|
245
|
+
|
|
246
|
+
- **Thin over the AI SDK.** ubean owns declaration and orchestration; the agent loop, tool-calling protocol and streaming primitives come from `ai`.
|
|
247
|
+
- **Lazy AI SDK import.** Missing peers never break the module graph — they fail at execution with an actionable message.
|
|
248
|
+
- **`v` is valibot.** The namespace re-export exists so tool schemas and the agent config can come from one import.
|
|
249
|
+
- **Streaming protocol is the contract.** `AgentStreamChunk` is what both `stream()` and the Vue runtime speak, which is why `useChat` can consume any agent's SSE endpoint.
|
|
250
|
+
- **Rendering is out of scope.** The runtime returns structured messages and chunks; bring your own markdown/code renderer.
|
|
251
|
+
- **No gateway implementation.** Presets are data; routing policy stays outside the framework.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# @ubean/ai
|
|
2
|
+
|
|
3
|
+
**[English](./README.md) | 中文**
|
|
4
|
+
|
|
5
|
+
ubean 框架的 AI 大模型集成 —— 一层薄编排,底层是 Vercel AI SDK。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { defineAgent, defineAgentTool, v } from '@ubean/ai';
|
|
9
|
+
|
|
10
|
+
const weather = defineAgentTool({
|
|
11
|
+
name: 'get_weather',
|
|
12
|
+
description: 'Get the weather for a city',
|
|
13
|
+
input: v.object({ city: v.string() }),
|
|
14
|
+
execute: async ({ city }) => ({ city, temperature: 21 })
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const agent = defineAgent({ model: 'deepseek-chat', tools: { weather } });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 目录
|
|
21
|
+
|
|
22
|
+
- [@ubean/ai](#ubeanai)
|
|
23
|
+
- [包定位](#包定位)
|
|
24
|
+
- [安装](#安装)
|
|
25
|
+
- [快速开始](#快速开始)
|
|
26
|
+
- [Agent 内核](#agent-内核)
|
|
27
|
+
- [Vue 运行时](#vue-运行时)
|
|
28
|
+
- [Gateway 预设](#gateway-预设)
|
|
29
|
+
- [Vite 插件](#vite-插件)
|
|
30
|
+
- [类型安全](#类型安全)
|
|
31
|
+
- [架构说明](#架构说明)
|
|
32
|
+
|
|
33
|
+
## 包定位
|
|
34
|
+
|
|
35
|
+
| 分层 | 职责 |
|
|
36
|
+
| ----------------------- | --------------------------------------------------------------- |
|
|
37
|
+
| `@ubean/ai`(内核) | provider 注册表、agent 定义、工具定义、流式 chunk 协议 |
|
|
38
|
+
| `@ubean/ai/runtime/vue` | 消费 agent 暴露的 SSE 流的 Vue composable |
|
|
39
|
+
| `@ubean/ai/gateway` | 仅提供 provider 预设 —— 常见托管服务的 base URL 与 id |
|
|
40
|
+
| `@ubean/ai/vite` | 模块接线:注册 provider、自动导入 composable、补 `optimizeDeps` |
|
|
41
|
+
|
|
42
|
+
**包含:**
|
|
43
|
+
|
|
44
|
+
- `defineProvider` / `configureAI` / `resolveModel` —— 把 `model` 字符串解析为具体的 provider + model id。
|
|
45
|
+
- `defineAgent` —— 声明 system prompt、model、工具集、步数预算与采样参数;返回带 `run()`、`stream()`、`asHandler()` 的 `UbeanAgent`。
|
|
46
|
+
- `defineAgentTool` —— 类型安全的恒等辅助函数,让 Valibot schema 推导出的输入类型保留到 `execute(args, ctx)` 上。
|
|
47
|
+
- `v` —— **valibot** 的命名空间重导出,用于编写工具输入 schema。
|
|
48
|
+
- `useChat` / `useAgent` / `useAIProvider` —— Vue 运行时。
|
|
49
|
+
- DeepSeek、OpenRouter、OmniRoute、OpenAI、Anthropic、Google、Groq 的 provider 预设。
|
|
50
|
+
|
|
51
|
+
**不包含**(刻意为之):
|
|
52
|
+
|
|
53
|
+
- 不提供网关服务。`@ubean/ai/gateway` 给的是 provider _预设_,不是路由、回退或成本优化 —— 需要这些能力请在前面接外部网关(例如 OmniRoute)。
|
|
54
|
+
- 不含 UI 层。Markdown 渲染、代码高亮、消息气泡都不在本包范围内;在 `useChat` 的消费侧自选渲染器。
|
|
55
|
+
- 不实现 agent 循环。工具调用循环、重试与流式原语来自 Vercel AI SDK(`ai`);ubean 只负责声明与编排。
|
|
56
|
+
- 不生成 HTTP 路由文件。`asHandler()` 的结果就是一个普通 Hono handler —— 由你挂到 `src/routes/`,与 `@ubean/routes` 记录的 API 路由写法一致。
|
|
57
|
+
|
|
58
|
+
**运行时依赖白名单(主入口):** `@valibot/to-json-schema`、`valibot`。AI SDK 本体(`ai`、`@ai-sdk/openai-compatible`)为懒加载。
|
|
59
|
+
|
|
60
|
+
**可选 peer:** `ai`、`@ai-sdk/openai-compatible`、`hono`、`vite`、`vue`。所有 peer 均为可选 —— 缺少它们时包依然能构建和导入。若 agent 真正执行时 AI SDK 缺失,调用会带安装提示直接 reject:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[@ubean/ai] The AI SDK packages are not installed. Install them with:
|
|
64
|
+
pnpm add ai @ai-sdk/openai-compatible (or npm/yarn/bun equivalent).
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 安装
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# ubean 侧的编排层
|
|
71
|
+
pnpm add @ubean/ai
|
|
72
|
+
|
|
73
|
+
# AI SDK 运行时 —— agent 执行时必需
|
|
74
|
+
pnpm add ai @ai-sdk/openai-compatible
|
|
75
|
+
|
|
76
|
+
# 只在用到对应能力时才需要的 peer
|
|
77
|
+
pnpm add vue # @ubean/ai/runtime/vue
|
|
78
|
+
pnpm add hono # UbeanAgent.asHandler()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 快速开始
|
|
82
|
+
|
|
83
|
+
**1. 启用模块**
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// ubean.config.ts
|
|
87
|
+
import { defineConfig } from 'ubean';
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
ai: true // 或 ai: { defaultProvider: 'deepseek', defaultModel: 'deepseek-chat' }
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**2. 定义工具**
|
|
95
|
+
|
|
96
|
+
工具输入是 Valibot schema,因此 `execute` 收到的参数对象类型是完整推导出来的。
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// src/server/ai/tools.ts
|
|
100
|
+
import { defineAgentTool, v } from '@ubean/ai';
|
|
101
|
+
|
|
102
|
+
export const searchDocs = defineAgentTool({
|
|
103
|
+
name: 'search_docs',
|
|
104
|
+
description: 'Search the documentation index for a query',
|
|
105
|
+
input: v.object({
|
|
106
|
+
query: v.string(),
|
|
107
|
+
limit: v.optional(v.pipe(v.number(), v.integer()), 5)
|
|
108
|
+
}),
|
|
109
|
+
execute: async ({ query, limit }, ctx) => {
|
|
110
|
+
ctx.signal?.throwIfAborted();
|
|
111
|
+
return { query, limit, hits: [] };
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**3. 定义 agent**
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// src/server/ai/agent.ts
|
|
120
|
+
import { defineAgent } from '@ubean/ai';
|
|
121
|
+
import { searchDocs } from './tools';
|
|
122
|
+
|
|
123
|
+
export const supportAgent = defineAgent<{ answer: string }>({
|
|
124
|
+
model: 'deepseek-chat',
|
|
125
|
+
system: 'You are a documentation assistant. Cite the docs you used.',
|
|
126
|
+
tools: { searchDocs },
|
|
127
|
+
maxSteps: 5
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**4. 通过 HTTP 暴露**
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// src/routes/api/chat.ts
|
|
135
|
+
import { supportAgent } from '~/server/ai/agent';
|
|
136
|
+
|
|
137
|
+
export const POST = supportAgent.asHandler();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`asHandler()` 把请求体读作 `AgentInput`,并以 Server-Sent Events 流的形式返回 `AgentStreamChunk` 帧。
|
|
141
|
+
|
|
142
|
+
## Agent 内核
|
|
143
|
+
|
|
144
|
+
`defineAgent(config)` 接受 model 字符串、system prompt、工具集,以及可选的采样参数(`temperature`、`maxTokens`、`headers`、`provider`、`context`)。返回的 `UbeanAgent`:
|
|
145
|
+
|
|
146
|
+
| 成员 | 说明 |
|
|
147
|
+
| --------------- | ------------------------------------------------------------------------------- |
|
|
148
|
+
| `config` | 解析后的 agent 配置。 |
|
|
149
|
+
| `run(input)` | 等待完整结果:`{ text, output?, toolResults, usage? }`。 |
|
|
150
|
+
| `stream(input)` | `AsyncIterable<AgentStreamChunk>` —— 文本增量、工具调用、工具结果、错误、结束。 |
|
|
151
|
+
| `asHandler()` | 通过 SSE 流式输出该 agent 的 Hono `Handler`。 |
|
|
152
|
+
|
|
153
|
+
provider 由 `model` 字符串解析。用 `defineProvider` 注册,或用 `configureAI` 设置默认值:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { configureAI, defineProvider, resolveModel } from '@ubean/ai';
|
|
157
|
+
|
|
158
|
+
defineProvider({
|
|
159
|
+
id: 'my-proxy',
|
|
160
|
+
kind: 'openai-compatible',
|
|
161
|
+
baseURL: 'https://gateway.internal/v1',
|
|
162
|
+
apiKey: process.env.MY_PROXY_KEY,
|
|
163
|
+
modelAliases: { chat: 'deepseek-chat' }
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
configureAI({ defaultProvider: 'my-proxy', defaultModel: 'chat' });
|
|
167
|
+
|
|
168
|
+
const { provider, modelId } = resolveModel('chat');
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`defineProvider` 按 `id` 幂等,并会忽略含 `/` 的 id(这类字符串被当作显式的 `provider/model`)。没有匹配到 provider 时,`resolveModel` 抛出:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
[@ubean/ai] No provider resolved for "<model>". Define a provider via defineProvider() or configure a default with configureAI().
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
若完全未配置,则使用环境变量回退 provider(`id: 'env'`、`kind: 'openai-compatible'`):密钥取自 `DEEPSEEK_API_KEY || UBEAN_AI_API_KEY || OPENAI_API_KEY`,base URL 取自 `UBEAN_AI_API_BASE`(默认 `https://api.deepseek.com/v1`)。
|
|
178
|
+
|
|
179
|
+
## Vue 运行时
|
|
180
|
+
|
|
181
|
+
`@ubean/ai/runtime/vue` 是自研的、SSR 安全的 SSE 消费者,不依赖 `@ai-sdk/vue`。
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { useChat } from '@ubean/ai/runtime/vue';
|
|
185
|
+
|
|
186
|
+
const { messages, input, setInput, sendMessage, status, error, isStreaming, abort, reload } = useChat({
|
|
187
|
+
url: '/api/chat'
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
| Composable | 返回值 |
|
|
192
|
+
| ------------------ | --------------------------------------------------------------------------------------------- |
|
|
193
|
+
| `useChat(options)` | `{ messages, status, error, input, data, setInput, sendMessage, abort, reload, isStreaming }` |
|
|
194
|
+
| `useAgent(agent)` | `{ isStreaming, error, result, chunks, run, stream }` —— 驱动进程内的 `UbeanAgent` |
|
|
195
|
+
| `useAIProvider()` | `{ defaultProvider?, defaultModel? }` —— 运行时 provider 配置 |
|
|
196
|
+
|
|
197
|
+
`useChat` 的 `url` 默认是相对路径 `./chat`,POST `{ ...body, messages }`,并把 `AgentStreamChunk` 帧解析为 `ChatMessage[]`。运行时 provider 默认值通过 `setAIProviderConfig({ defaultProvider, defaultModel })` 设置。
|
|
198
|
+
|
|
199
|
+
## Gateway 预设
|
|
200
|
+
|
|
201
|
+
`@ubean/ai/gateway` 导出的 provider 定义可直接注册:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { defineProvider } from '@ubean/ai';
|
|
205
|
+
import { deepseek, openrouter, allGatewayProviders } from '@ubean/ai/gateway';
|
|
206
|
+
|
|
207
|
+
for (const provider of allGatewayProviders) defineProvider(provider);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| 导出 | provider id | Base URL |
|
|
211
|
+
| ------------ | ------------ | ------------------------------ |
|
|
212
|
+
| `deepseek` | `deepseek` | `https://api.deepseek.com/v1` |
|
|
213
|
+
| `openrouter` | `openrouter` | `https://openrouter.ai/api/v1` |
|
|
214
|
+
| `omniRoute` | `omniroute` | `http://localhost:8080/v1` |
|
|
215
|
+
| `openai` | `openai` | `https://api.openai.com/v1` |
|
|
216
|
+
| `anthropic` | `anthropic` | — |
|
|
217
|
+
| `google` | `google` | — |
|
|
218
|
+
| `groq` | `groq` | — |
|
|
219
|
+
|
|
220
|
+
> 该子路径只提供预设。它不实现网关级的路由、回退或成本优化 —— 那些属于外部网关(如 OmniRoute)。
|
|
221
|
+
|
|
222
|
+
## Vite 插件
|
|
223
|
+
|
|
224
|
+
`ubean.config.ts` 里的 `ai` 字段会替你完成接线。需要显式控制时直接使用插件:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { ubeanAiPlugin, defineAiConfig } from '@ubean/ai/vite';
|
|
228
|
+
|
|
229
|
+
export default defineConfig({
|
|
230
|
+
plugins: [ubeanAiPlugin(defineAiConfig({ defaultProvider: 'deepseek' }))]
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
插件会通过 `defineProvider` 注册 provider 预设,为来自 `@ubean/ai/runtime/vue` 的 `useChat` / `useAgent` / `useAIProvider` 注册组件解析器,生成 `virtual:ubean-ai/runtime`,并把运行时包加入 `optimizeDeps.include`。它**不会**启动 dev 网关代理。
|
|
235
|
+
|
|
236
|
+
## 类型安全
|
|
237
|
+
|
|
238
|
+
- `AgentTool<TInput>` 携带 Valibot schema,因此 `execute(args, ctx)` 的参数由 `input` 推导 —— 无需断言,也无需手写泛型。
|
|
239
|
+
- `defineAgentTool` 是恒等函数,唯一职责就是保留这份推导。
|
|
240
|
+
- `defineAgent<T>(config)` 为 `AgentRunResult<T>.output` 与 `AgentRunResult<T>.text` 提供类型。
|
|
241
|
+
- `AgentStreamChunk` 是按 `type` 判别的联合类型(`'text-delta' | 'tool-call' | 'tool-result' | 'error' | 'done'`),因此 `stream()` 消费侧可以做穷尽分支。
|
|
242
|
+
- `UbeanAgent.asHandler()` 返回 Hono `Handler`,从 `src/routes/` 挂载时路由类型保持完整。
|
|
243
|
+
|
|
244
|
+
## 架构说明
|
|
245
|
+
|
|
246
|
+
- **薄封装 AI SDK。** ubean 负责声明与编排;agent 循环、工具调用协议与流式原语来自 `ai`。
|
|
247
|
+
- **AI SDK 懒加载。** 缺少 peer 不会破坏模块图 —— 只会在执行时以可操作的提示失败。
|
|
248
|
+
- **`v` 就是 valibot。** 命名空间重导出让工具 schema 与 agent 配置来自同一次 import。
|
|
249
|
+
- **流式协议即契约。** `AgentStreamChunk` 是 `stream()` 与 Vue 运行时共同使用的语言,`useChat` 因此能消费任意 agent 的 SSE 端点。
|
|
250
|
+
- **渲染不在范围内。** 运行时返回结构化的消息与 chunk;markdown/代码渲染器由你自备。
|
|
251
|
+
- **不实现网关。** 预设只是数据;路由策略留在框架之外。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubean/ai",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "AI large-model integration for the ubean framework: defineAgent + defineAgentTool + Vue runtime (thin orchestration over the Vercel AI SDK)",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@ubean/build": "0.6.
|
|
31
|
+
"@ubean/build": "0.6.4",
|
|
32
32
|
"@valibot/to-json-schema": "^1.8.0",
|
|
33
33
|
"valibot": "^1.5.0"
|
|
34
34
|
},
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"@types/node": "^26.6.5",
|
|
37
37
|
"hono": "4.13.13",
|
|
38
38
|
"typescript": "npm:typescript-native-bridge@latest",
|
|
39
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
40
|
-
"vite-plus": "1.
|
|
41
|
-
"vue": "^3.5.
|
|
39
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
|
|
40
|
+
"vite-plus": "1.3.0",
|
|
41
|
+
"vue": "^3.5.44"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@ai-sdk/openai-compatible": "^1.0.0 || ^2.0.0 || ^3.0.0",
|