@opentiny/tiny-robot-kit 0.2.0-alpha.0

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/src/error.ts ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * 错误处理模块
3
+ * 用于统一处理各种错误情况并提供标准化的错误格式
4
+ */
5
+ import { AIAdapterError, ErrorType } from './types'
6
+
7
+ /**
8
+ * 创建标准化的AI适配器错误
9
+ * @param error 错误信息
10
+ * @returns 标准化的AI适配器错误
11
+ */
12
+ export function createError(error: Partial<AIAdapterError>): AIAdapterError {
13
+ return {
14
+ type: error.type || ErrorType.UNKNOWN_ERROR,
15
+ message: error.message || '未知错误',
16
+ statusCode: error.statusCode,
17
+ originalError: error.originalError,
18
+ }
19
+ }
20
+
21
+ interface Error {
22
+ response?: object
23
+ code?: string
24
+ message?: string
25
+ }
26
+
27
+ /**
28
+ * 处理API请求错误
29
+ * @param error 原始错误
30
+ * @returns 标准化的AI适配器错误
31
+ */
32
+ export function handleRequestError(error: Error): AIAdapterError {
33
+ // 网络错误
34
+ if (!error.response) {
35
+ return createError({
36
+ type: ErrorType.NETWORK_ERROR,
37
+ message: '网络连接错误,请检查您的网络连接',
38
+ originalError: error,
39
+ })
40
+ }
41
+
42
+ // 服务器返回的错误
43
+ if (error.response) {
44
+ const { status, data } = error.response
45
+
46
+ // 身份验证错误
47
+ if (status === 401 || status === 403) {
48
+ return createError({
49
+ type: ErrorType.AUTHENTICATION_ERROR,
50
+ message: '身份验证失败,请检查您的API密钥',
51
+ statusCode: status,
52
+ originalError: error,
53
+ })
54
+ }
55
+
56
+ // 速率限制错误
57
+ if (status === 429) {
58
+ return createError({
59
+ type: ErrorType.RATE_LIMIT_ERROR,
60
+ message: '超出API调用限制,请稍后再试',
61
+ statusCode: status,
62
+ originalError: error,
63
+ })
64
+ }
65
+
66
+ // 服务器错误
67
+ if (status >= 500) {
68
+ return createError({
69
+ type: ErrorType.SERVER_ERROR,
70
+ message: '服务器错误,请稍后再试',
71
+ statusCode: status,
72
+ originalError: error,
73
+ })
74
+ }
75
+
76
+ // 其他HTTP错误
77
+ return createError({
78
+ type: ErrorType.UNKNOWN_ERROR,
79
+ message: data?.error?.message || `请求失败,状态码: ${status}`,
80
+ statusCode: status,
81
+ originalError: error,
82
+ })
83
+ }
84
+
85
+ // 超时错误
86
+ if (error.code === 'ECONNABORTED') {
87
+ return createError({
88
+ type: ErrorType.TIMEOUT_ERROR,
89
+ message: '请求超时,请稍后再试',
90
+ originalError: error,
91
+ })
92
+ }
93
+
94
+ // 默认错误
95
+ return createError({
96
+ type: ErrorType.UNKNOWN_ERROR,
97
+ message: error.message || '发生未知错误',
98
+ originalError: error,
99
+ })
100
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { AIClient } from './client'
2
+
3
+ export { BaseModelProvider } from './providers/base'
4
+ export { OpenAIProvider } from './providers/openai'
5
+
6
+ export { formatMessages, extractTextFromResponse } from './utils'
7
+
8
+ export * from './vue'
9
+
10
+ export * from './types'
@@ -0,0 +1,62 @@
1
+ import { AIModelConfig, ChatCompletionRequest, ChatCompletionResponse, StreamHandler } from '../types'
2
+
3
+ /**
4
+ * 模型Provider基类
5
+ */
6
+ export abstract class BaseModelProvider {
7
+ protected config: AIModelConfig
8
+
9
+ /**
10
+ * @param config AI模型配置
11
+ */
12
+ constructor(config: AIModelConfig) {
13
+ this.config = config
14
+ }
15
+
16
+ /**
17
+ * 发送聊天请求并获取响应
18
+ * @param request 聊天请求参数
19
+ * @returns 聊天响应
20
+ */
21
+ abstract chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>
22
+
23
+ /**
24
+ * 发送流式聊天请求并通过处理器处理响应
25
+ * @param request 聊天请求参数
26
+ * @param handler 流式响应处理器
27
+ */
28
+ abstract chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>
29
+
30
+ /**
31
+ * 更新配置
32
+ * @param config 新的AI模型配置
33
+ */
34
+ updateConfig(config: AIModelConfig): void {
35
+ this.config = { ...this.config, ...config }
36
+ }
37
+
38
+ /**
39
+ * 获取当前配置
40
+ * @returns AI模型配置
41
+ */
42
+ getConfig(): AIModelConfig {
43
+ return { ...this.config }
44
+ }
45
+
46
+ /**
47
+ * 验证请求参数
48
+ * @param request 聊天请求参数
49
+ */
50
+ protected validateRequest(request: ChatCompletionRequest): void {
51
+ if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
52
+ throw new Error('请求必须包含至少一条消息')
53
+ }
54
+
55
+ // 验证每条消息的格式
56
+ for (const message of request.messages) {
57
+ if (!message.role || !message.content) {
58
+ throw new Error('每条消息必须包含角色和内容')
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * OpenAI提供商
3
+ * 用于与OpenAI API进行交互
4
+ */
5
+
6
+ import { BaseModelProvider } from './base'
7
+ import type { AIModelConfig, ChatCompletionRequest, ChatCompletionResponse, StreamHandler } from '../types'
8
+ import { handleRequestError } from '../error'
9
+ import { handleSSEStream } from '../utils'
10
+
11
+ export class OpenAIProvider extends BaseModelProvider {
12
+ private baseURL: string
13
+ private apiKey: string
14
+ private defaultModel: string = 'gpt-3.5-turbo'
15
+
16
+ /**
17
+ * @param config AI模型配置
18
+ */
19
+ constructor(config: AIModelConfig) {
20
+ super(config)
21
+
22
+ this.baseURL = config.apiUrl || 'https://api.openai.com/v1'
23
+ this.apiKey = config.apiKey || ''
24
+
25
+ // 设置默认模型
26
+ if (config.defaultModel) {
27
+ this.defaultModel = config.defaultModel
28
+ }
29
+
30
+ if (!this.apiKey) {
31
+ console.warn('API key is not provided. Authentication will likely fail.')
32
+ }
33
+ }
34
+
35
+ /**
36
+ * 发送聊天请求并获取响应
37
+ * @param request 聊天请求参数
38
+ * @returns 聊天响应
39
+ */
40
+ async chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse> {
41
+ try {
42
+ this.validateRequest(request)
43
+
44
+ const requestData = {
45
+ model: request.options?.model || this.config.defaultModel || this.defaultModel,
46
+ messages: request.messages,
47
+ ...request.options,
48
+ stream: false,
49
+ }
50
+
51
+ const response = await fetch(`${this.baseURL}/chat/completions`, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ Authorization: `Bearer ${this.apiKey}`,
56
+ },
57
+ body: JSON.stringify(requestData),
58
+ })
59
+
60
+ if (!response.ok) {
61
+ const errorText = await response.text()
62
+ throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`)
63
+ }
64
+
65
+ return await response.json()
66
+ } catch (error: unknown) {
67
+ // 处理错误
68
+ throw handleRequestError(error as { response: object })
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 发送流式聊天请求并通过处理器处理响应
74
+ * @param request 聊天请求参数
75
+ * @param handler 流式响应处理器
76
+ */
77
+ async chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void> {
78
+ const { signal, ...options } = request.options || {}
79
+
80
+ try {
81
+ // 验证请求参数
82
+ this.validateRequest(request)
83
+
84
+ const requestData = {
85
+ model: request.options?.model || this.config.defaultModel || this.defaultModel,
86
+ messages: request.messages,
87
+ ...options,
88
+ stream: true,
89
+ }
90
+
91
+ const response = await fetch(`${this.baseURL}/chat/completions`, {
92
+ method: 'POST',
93
+ headers: {
94
+ 'Content-Type': 'application/json',
95
+ Authorization: `Bearer ${this.apiKey}`,
96
+ Accept: 'text/event-stream',
97
+ },
98
+ body: JSON.stringify(requestData),
99
+ signal,
100
+ })
101
+
102
+ if (!response.ok) {
103
+ const errorText = await response.text()
104
+ throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`)
105
+ }
106
+
107
+ await handleSSEStream(response, handler, signal)
108
+ } catch (error: unknown) {
109
+ if (signal?.aborted) return
110
+ handler.onError(handleRequestError(error as { response: object }))
111
+ }
112
+ }
113
+
114
+ /**
115
+ * 更新配置
116
+ * @param config 新的AI模型配置
117
+ */
118
+ updateConfig(config: AIModelConfig): void {
119
+ super.updateConfig(config)
120
+
121
+ // 更新配置
122
+ if (config.apiUrl) {
123
+ this.baseURL = config.apiUrl
124
+ }
125
+
126
+ if (config.apiKey) {
127
+ this.apiKey = config.apiKey
128
+ }
129
+
130
+ if (config.defaultModel) {
131
+ this.defaultModel = config.defaultModel
132
+ }
133
+ }
134
+ }
package/src/types.ts ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * 消息角色类型
3
+ */
4
+ export type MessageRole = 'system' | 'user' | 'assistant'
5
+
6
+ /**
7
+ * 聊天消息接口
8
+ */
9
+ export interface ChatMessage {
10
+ role: MessageRole
11
+ content: string
12
+ name?: string
13
+ }
14
+
15
+ /**
16
+ * 聊天历史记录
17
+ */
18
+ export type ChatHistory = ChatMessage[]
19
+
20
+ /**
21
+ * 聊天完成请求选项
22
+ */
23
+ export interface ChatCompletionOptions {
24
+ model?: string
25
+ temperature?: number
26
+ top_p?: number
27
+ n?: number
28
+ stream?: boolean
29
+ max_tokens?: number
30
+ signal?: AbortSignal
31
+ }
32
+
33
+ /**
34
+ * 聊天完成请求参数
35
+ */
36
+ export interface ChatCompletionRequest {
37
+ messages: ChatMessage[]
38
+ options?: ChatCompletionOptions
39
+ }
40
+
41
+ /**
42
+ * 聊天完成响应消息
43
+ */
44
+ export interface ChatCompletionResponseMessage {
45
+ role: MessageRole
46
+ content: string
47
+ }
48
+
49
+ /**
50
+ * 聊天完成响应选择
51
+ */
52
+ export interface ChatCompletionResponseChoice {
53
+ index: number
54
+ message: ChatCompletionResponseMessage
55
+ finish_reason: string
56
+ }
57
+
58
+ /**
59
+ * 聊天完成响应使用情况
60
+ */
61
+ export interface ChatCompletionResponseUsage {
62
+ prompt_tokens: number
63
+ completion_tokens: number
64
+ total_tokens: number
65
+ }
66
+
67
+ /**
68
+ * 聊天完成响应
69
+ */
70
+ export interface ChatCompletionResponse {
71
+ id: string
72
+ object: string
73
+ created: number
74
+ model: string
75
+ choices: ChatCompletionResponseChoice[]
76
+ usage: ChatCompletionResponseUsage
77
+ }
78
+
79
+ /**
80
+ * 流式聊天完成响应增量
81
+ */
82
+ export interface ChatCompletionStreamResponseDelta {
83
+ content?: string
84
+ role?: MessageRole
85
+ }
86
+
87
+ /**
88
+ * 流式聊天完成响应选择
89
+ */
90
+ export interface ChatCompletionStreamResponseChoice {
91
+ index: number
92
+ delta: ChatCompletionStreamResponseDelta
93
+ finish_reason: string | null
94
+ }
95
+
96
+ /**
97
+ * 流式聊天完成响应
98
+ */
99
+ export interface ChatCompletionStreamResponse {
100
+ id: string
101
+ object: string
102
+ created: number
103
+ model: string
104
+ choices: ChatCompletionStreamResponseChoice[]
105
+ }
106
+
107
+ /**
108
+ * AI模型提供商类型
109
+ */
110
+ export type AIProvider = 'openai' | 'deepseek' | 'custom'
111
+
112
+ /**
113
+ * AI模型配置接口
114
+ */
115
+ export interface AIModelConfig {
116
+ provider: AIProvider
117
+ apiKey?: string
118
+ apiUrl?: string
119
+ apiVersion?: string
120
+ defaultModel?: string
121
+ defaultOptions?: ChatCompletionOptions
122
+ }
123
+
124
+ /**
125
+ * 错误类型
126
+ */
127
+ export enum ErrorType {
128
+ NETWORK_ERROR = 'network_error',
129
+ AUTHENTICATION_ERROR = 'authentication_error',
130
+ RATE_LIMIT_ERROR = 'rate_limit_error',
131
+ SERVER_ERROR = 'server_error',
132
+ MODEL_ERROR = 'model_error',
133
+ TIMEOUT_ERROR = 'timeout_error',
134
+ UNKNOWN_ERROR = 'unknown_error',
135
+ }
136
+
137
+ /**
138
+ * AI适配器错误
139
+ */
140
+ export interface AIAdapterError {
141
+ type: ErrorType
142
+ message: string
143
+ statusCode?: number
144
+ originalError?: object
145
+ }
146
+
147
+ /**
148
+ * 流式响应事件类型
149
+ */
150
+ export enum StreamEventType {
151
+ DATA = 'data',
152
+ ERROR = 'error',
153
+ DONE = 'done',
154
+ }
155
+
156
+ /**
157
+ * 流式响应处理器
158
+ */
159
+ export interface StreamHandler {
160
+ onData: (data: ChatCompletionStreamResponse) => void
161
+ onError: (error: AIAdapterError) => void
162
+ onDone: () => void
163
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * 工具函数模块
3
+ * 提供一些实用的辅助函数
4
+ */
5
+
6
+ import type { ChatMessage, ChatCompletionResponse, ChatCompletionStreamResponse, StreamHandler } from './types'
7
+
8
+ /**
9
+ * 处理SSE流式响应
10
+ * @param response fetch响应对象
11
+ * @param handler 流处理器
12
+ */
13
+ export async function handleSSEStream(response: Response, handler: StreamHandler, signal?: AbortSignal): Promise<void> {
14
+ // 获取ReadableStream
15
+ const reader = response.body?.getReader()
16
+ if (!reader) {
17
+ throw new Error('Response body is null')
18
+ }
19
+
20
+ // 处理流式数据
21
+ const decoder = new TextDecoder()
22
+ let buffer = ''
23
+
24
+ if (signal) {
25
+ signal.addEventListener(
26
+ 'abort',
27
+ () => {
28
+ reader.cancel().catch((err) => console.error('Error cancelling reader:', err))
29
+ },
30
+ { once: true },
31
+ )
32
+ }
33
+
34
+ try {
35
+ while (true) {
36
+ if (signal?.aborted) {
37
+ await reader.cancel()
38
+ break
39
+ }
40
+
41
+ const { done, value } = await reader.read()
42
+ if (done) break
43
+
44
+ // 解码二进制数据
45
+ const chunk = decoder.decode(value, { stream: true })
46
+ buffer += chunk
47
+
48
+ // 处理完整的SSE消息
49
+ const lines = buffer.split('\n\n')
50
+ buffer = lines.pop() || ''
51
+
52
+ for (const line of lines) {
53
+ if (line.trim() === '') continue
54
+ if (line.trim() === 'data: [DONE]') {
55
+ handler.onDone()
56
+ continue
57
+ }
58
+
59
+ try {
60
+ // 解析SSE消息
61
+ const dataMatch = line.match(/^data: (.+)$/m)
62
+ if (!dataMatch) continue
63
+
64
+ const data = JSON.parse(dataMatch[1]) as ChatCompletionStreamResponse
65
+ handler.onData(data)
66
+ } catch (error) {
67
+ console.error('Error parsing SSE message:', error)
68
+ }
69
+ }
70
+ }
71
+
72
+ if (buffer.trim() === 'data: [DONE]' || signal?.aborted) {
73
+ handler.onDone()
74
+ }
75
+ } catch (error) {
76
+ if (signal?.aborted) return
77
+ throw error
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 格式化消息
83
+ * 将各种格式的消息转换为标准的ChatMessage格式
84
+ * @param messages 消息数组
85
+ * @returns 标准格式的消息数组
86
+ */
87
+ export function formatMessages(messages: Array<ChatMessage | string>): ChatMessage[] {
88
+ return messages.map((msg) => {
89
+ // 如果已经是标准格式,直接返回
90
+ if (typeof msg === 'object' && 'role' in msg && 'content' in msg) {
91
+ return {
92
+ role: msg.role,
93
+ content: String(msg.content),
94
+ ...(msg.name ? { name: msg.name } : {}),
95
+ }
96
+ }
97
+
98
+ // 如果是字符串,默认为用户消息
99
+ if (typeof msg === 'string') {
100
+ return {
101
+ role: 'user',
102
+ content: msg,
103
+ }
104
+ }
105
+
106
+ // 其他情况,尝试转换为字符串
107
+ return {
108
+ role: 'user',
109
+ content: String(msg),
110
+ }
111
+ })
112
+ }
113
+
114
+ /**
115
+ * 从响应中提取文本内容
116
+ * @param response 聊天完成响应
117
+ * @returns 文本内容
118
+ */
119
+ export function extractTextFromResponse(response: ChatCompletionResponse): string {
120
+ if (!response.choices || !response.choices.length) {
121
+ return ''
122
+ }
123
+
124
+ return response.choices[0].message?.content || ''
125
+ }
@@ -0,0 +1 @@
1
+ export * from './useMessage'