Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ test-results
playwright-report
blob-report
playwright/.cache
package-lock.json
pnpm-lock.yaml
yarn.lock
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
{
"name": "root",
"private": true,
"type": "module",
"scripts": {
"dev": "pnpm -F docs docs:dev",
"build": "pnpm -F docs docs:build",
"build:lib": "pnpm -F @opentiny/tiny-robot build",
"dev:adapter": "pnpm -F @opentiny/tiny-robot-ai-adapter dev",
"build:adapter": "pnpm -F @opentiny/tiny-robot-ai-adapter build",
"dev:ai-mocker": "pnpm --filter ai-mocker dev",
"dev:demo": "npm run build:adapter && pnpm --filter tiny-robot-demo dev",
"format": "prettier --write --list-different \"packages/**/*.{ts,mts,tsx,vue}\"",
"type-check": "pnpm -F @opentiny/tiny-robot type-check",
"lint": "eslint . --fix",
Expand Down
100 changes: 100 additions & 0 deletions packages/ai-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# ai-adapter

封装与与技术栈无关的AI大模型的交互逻辑与数据处理,适配多种模型提供商,提供统一的API接口。

## API参考

### AIClient

主要客户端类,用于与AI模型交互。

#### 构造函数

```typescript
new AIClient(config: AIModelConfig)
```

#### 方法

- `chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>`
发送聊天请求并获取响应。

- `chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>`
发送流式聊天请求并通过处理器处理响应。


## 基本用法

### 创建客户端并发送消息

```typescript
import { AIClient } from 'ai-adapter';

// 创建客户端
const client = new AIClient({
provider: 'openai',
apiKey: 'your-api-key',
defaultModel: 'gpt-3.5-turbo'
});

// 发送消息并获取响应
async function chat() {
try {
const response = await client.chat({
messages: [
{ role: 'system', content: '你是一个有用的助手。' },
{ role: 'user', content: '你好,请介绍一下自己。' }
],
options: {
temperature: 0.7
}
});

console.log(response.choices[0].message.content);
} catch (error) {
console.error('聊天出错:', error);
}
}

chat();
```

### 使用流式响应

```typescript
import { AIClient } from 'ai-adapter';

const client = new AIClient({
provider: 'openai',
apiKey: 'your-api-key'
});

async function streamChat() {
try {
await client.chatStream({
messages: [
{ role: 'user', content: '写一个简短的故事。' }
],
options: {
stream: true
}
}, {
onData: (data) => {
// 处理流式数据
const content = data.choices[0]?.delta?.content || '';
process.stdout.write(content);
},
onError: (error) => {
console.error('流式响应错误:', error);
},
onDone: () => {
console.log('\n流式响应完成');
}
});
} catch (error) {
console.error('流式聊天出错:', error);
}
}

streamChat();
```
19 changes: 19 additions & 0 deletions packages/ai-adapter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@opentiny/tiny-robot-ai-adapter",
"version": "0.1.0",
"description": "AI大模型适配器",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^22.13.17",
"tsup": "^8.0.1",
"typescript": "^5.8.2"
}
}
101 changes: 101 additions & 0 deletions packages/ai-adapter/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* AI客户端类
* 负责根据配置选择合适的提供商并处理请求
*/

import type { AIModelConfig, ChatCompletionRequest, ChatCompletionResponse, StreamHandler } from './types'
import type { BaseModelProvider } from './providers/base'
import { OpenAIProvider } from './providers/openai'

/**
* AI客户端类
*/
export class AIClient {
private provider: BaseModelProvider
private config: AIModelConfig

/**
* 构造函数
* @param config AI模型配置
*/
constructor(config: AIModelConfig) {
this.config = config
this.provider = this.createProvider(config)
}

/**
* 创建提供商实例
* @param config AI模型配置
* @returns 提供商实例
*/
private createProvider(config: AIModelConfig): BaseModelProvider {
// 如果提供了自定义提供商实现,直接使用
if (config.provider === 'custom' && 'providerImplementation' in config) {
return (config as { providerImplementation: BaseModelProvider }).providerImplementation
}

// 根据提供商类型创建对应的提供商实例
switch (config.provider) {
case 'deepseek':
const defaultConfig = {
defaultModel: 'deepseek-chat',
apiUrl: 'https://api.deepseek.com/v1',
}
return new OpenAIProvider({ ...defaultConfig, ...config })
case 'openai':
default:
return new OpenAIProvider(config)
}
}

/**
* 发送聊天请求并获取响应
* @param request 聊天请求参数
* @returns 聊天响应
*/
async chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse> {
return this.provider.chat(request)
}

/**
* 发送流式聊天请求并通过处理器处理响应
* @param request 聊天请求参数
* @param handler 流式响应处理器
*/
async chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void> {
// 确保请求中启用了流式响应
const streamRequest = {
...request,
options: {
...request.options,
stream: true,
},
}

return this.provider.chatStream(streamRequest, handler)
}

/**
* 获取当前配置
* @returns AI模型配置
*/
getConfig(): AIModelConfig {
return { ...this.config }
}

/**
* 更新配置
* @param config 新的AI模型配置
*/
updateConfig(config: Partial<AIModelConfig>): void {
this.config = { ...this.config, ...config }

// 如果提供商类型发生变化,重新创建提供商实例
if (config.provider && config.provider !== this.config.provider) {
this.provider = this.createProvider(this.config)
} else {
// 否则只更新提供商配置
this.provider.updateConfig(this.config)
}
}
}
100 changes: 100 additions & 0 deletions packages/ai-adapter/src/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* 错误处理模块
* 用于统一处理各种错误情况并提供标准化的错误格式
*/
import { AIAdapterError, ErrorType } from './types'

/**
* 创建标准化的AI适配器错误
* @param error 错误信息
* @returns 标准化的AI适配器错误
*/
export function createError(error: Partial<AIAdapterError>): AIAdapterError {
return {
type: error.type || ErrorType.UNKNOWN_ERROR,
message: error.message || '未知错误',
statusCode: error.statusCode,
originalError: error.originalError,
}
}

interface Error {
response?: object
code?: string
message?: string
}

/**
* 处理API请求错误
* @param error 原始错误
* @returns 标准化的AI适配器错误
*/
export function handleRequestError(error: Error): AIAdapterError {
// 网络错误
if (!error.response) {
return createError({
type: ErrorType.NETWORK_ERROR,
message: '网络连接错误,请检查您的网络连接',
originalError: error,
})
}

// 服务器返回的错误
if (error.response) {
const { status, data } = error.response

// 身份验证错误
if (status === 401 || status === 403) {
return createError({
type: ErrorType.AUTHENTICATION_ERROR,
message: '身份验证失败,请检查您的API密钥',
statusCode: status,
originalError: error,
})
}

// 速率限制错误
if (status === 429) {
return createError({
type: ErrorType.RATE_LIMIT_ERROR,
message: '超出API调用限制,请稍后再试',
statusCode: status,
originalError: error,
})
}

// 服务器错误
if (status >= 500) {
return createError({
type: ErrorType.SERVER_ERROR,
message: '服务器错误,请稍后再试',
statusCode: status,
originalError: error,
})
}

// 其他HTTP错误
return createError({
type: ErrorType.UNKNOWN_ERROR,
message: data?.error?.message || `请求失败,状态码: ${status}`,
statusCode: status,
originalError: error,
})
}

// 超时错误
if (error.code === 'ECONNABORTED') {
return createError({
type: ErrorType.TIMEOUT_ERROR,
message: '请求超时,请稍后再试',
originalError: error,
})
}

// 默认错误
return createError({
type: ErrorType.UNKNOWN_ERROR,
message: error.message || '发生未知错误',
originalError: error,
})
}
8 changes: 8 additions & 0 deletions packages/ai-adapter/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { AIClient } from './client'

export { BaseModelProvider } from './providers/base'
export { OpenAIProvider } from './providers/openai'

export { formatMessages, extractTextFromResponse } from './utils'

export * from './types'
Loading