@downcity/city 0.2.191 → 0.2.192
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/bin/index.d.ts +3 -2
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +1 -0
- package/bin/index.js.map +1 -1
- package/bin/pact/http.d.ts +4 -0
- package/bin/pact/http.d.ts.map +1 -1
- package/bin/pact/http.js.map +1 -1
- package/bin/pact/invoker/ai/CityModel.d.ts +38 -0
- package/bin/pact/invoker/ai/CityModel.d.ts.map +1 -0
- package/bin/pact/invoker/ai/CityModel.js +215 -0
- package/bin/pact/invoker/ai/CityModel.js.map +1 -0
- package/bin/pact/invoker/ai/index.d.ts +6 -13
- package/bin/pact/invoker/ai/index.d.ts.map +1 -1
- package/bin/pact/invoker/ai/index.js +12 -34
- package/bin/pact/invoker/ai/index.js.map +1 -1
- package/bin/pact/invoker/ai/types.d.ts +3 -2
- package/bin/pact/invoker/ai/types.d.ts.map +1 -1
- package/bin/pact/invoker/ai/types.js +1 -1
- package/bin/service/ai/ai-service-values.d.ts +73 -0
- package/bin/service/ai/ai-service-values.d.ts.map +1 -0
- package/bin/service/ai/ai-service-values.js +177 -0
- package/bin/service/ai/ai-service-values.js.map +1 -0
- package/bin/service/ai/ai-service.d.ts +8 -0
- package/bin/service/ai/ai-service.d.ts.map +1 -1
- package/bin/service/ai/ai-service.js +62 -228
- package/bin/service/ai/ai-service.js.map +1 -1
- package/bin/service/ai/helpers.d.ts +1 -0
- package/bin/service/ai/helpers.d.ts.map +1 -1
- package/bin/service/ai/helpers.js +22 -13
- package/bin/service/ai/helpers.js.map +1 -1
- package/bin/service/ai/language-model-stream.d.ts +12 -0
- package/bin/service/ai/language-model-stream.d.ts.map +1 -0
- package/bin/service/ai/language-model-stream.js +129 -0
- package/bin/service/ai/language-model-stream.js.map +1 -0
- package/bin/service/ai/model-routing.js +34 -0
- package/bin/service/ai/model-routing.js.map +1 -1
- package/bin/service/ai/provider.d.ts +9 -6
- package/bin/service/ai/provider.d.ts.map +1 -1
- package/bin/service/ai/provider.js +16 -5
- package/bin/service/ai/provider.js.map +1 -1
- package/bin/service/ai/types.d.ts +14 -1
- package/bin/service/ai/types.d.ts.map +1 -1
- package/bin/types/CityLanguageModelRuntime.d.ts +49 -0
- package/bin/types/CityLanguageModelRuntime.d.ts.map +1 -0
- package/bin/types/CityLanguageModelRuntime.js +8 -0
- package/bin/types/CityLanguageModelRuntime.js.map +1 -0
- package/bin/types/CityLanguageModelTransport.d.ts +49 -0
- package/bin/types/CityLanguageModelTransport.d.ts.map +1 -0
- package/bin/types/CityLanguageModelTransport.js +9 -0
- package/bin/types/CityLanguageModelTransport.js.map +1 -0
- package/bin/types/CityModelRuntime.d.ts +17 -0
- package/bin/types/CityModelRuntime.d.ts.map +1 -0
- package/bin/types/CityModelRuntime.js +8 -0
- package/bin/types/CityModelRuntime.js.map +1 -0
- package/bin/types/Metering.d.ts +4 -0
- package/bin/types/Metering.d.ts.map +1 -1
- package/bin/utils/CityLanguageModelCodec.d.ts +14 -0
- package/bin/utils/CityLanguageModelCodec.d.ts.map +1 -0
- package/bin/utils/CityLanguageModelCodec.js +108 -0
- package/bin/utils/CityLanguageModelCodec.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIService 值解析与类型守卫模块。
|
|
3
|
+
*
|
|
4
|
+
* 集中管理 Provider 输出、usage 和图片任务数据的纯函数转换,让 AIService 主模块
|
|
5
|
+
* 只保留路由、生命周期与持久化编排。
|
|
6
|
+
*/
|
|
7
|
+
import type { AsyncJobRecord } from "../../types/AsyncJob.js";
|
|
8
|
+
import type { AIProviderChargedOutput, AIProviderChargedResponse, AIProviderChargeLine } from "./charge.js";
|
|
9
|
+
import type { AIImageProviderCreateResult, AIImageProviderResult } from "./job-types.js";
|
|
10
|
+
/** Provider action 拆包后的统一输出。 */
|
|
11
|
+
export interface ResolvedProviderOutput {
|
|
12
|
+
/** Provider 对外输出。 */
|
|
13
|
+
output: unknown;
|
|
14
|
+
/** Provider 计算的可选扣费行。 */
|
|
15
|
+
charge?: AIProviderChargeLine | Promise<AIProviderChargeLine | undefined>;
|
|
16
|
+
}
|
|
17
|
+
/** 可转存到 Federation storage 的图片文件 part。 */
|
|
18
|
+
export type StoredImagePart = Record<string, unknown> & {
|
|
19
|
+
/** 固定文件 part 类型。 */
|
|
20
|
+
type: "file";
|
|
21
|
+
/** 可抓取的 HTTP(S) URL。 */
|
|
22
|
+
url: string;
|
|
23
|
+
};
|
|
24
|
+
/** 判断一个值是否为 HTTP Response。 */
|
|
25
|
+
export declare function isResponse(value: unknown): value is Response;
|
|
26
|
+
/** 判断一个值是否为普通对象。 */
|
|
27
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
28
|
+
/** 判断 Provider 是否返回了带账单 Response。 */
|
|
29
|
+
export declare function isProviderChargedResponse(value: unknown): value is AIProviderChargedResponse;
|
|
30
|
+
/** 判断 Provider 是否返回了带账单普通输出。 */
|
|
31
|
+
export declare function isProviderChargedOutput(value: unknown): value is AIProviderChargedOutput;
|
|
32
|
+
/** 判断一个值是否为 Promise-like。 */
|
|
33
|
+
export declare function isPromiseLike(value: unknown): value is PromiseLike<unknown>;
|
|
34
|
+
/** 读取可选字符串。 */
|
|
35
|
+
export declare function readOptionalString(value: unknown): string | undefined;
|
|
36
|
+
/** 读取可选数字。 */
|
|
37
|
+
export declare function readOptionalNumber(value: unknown): number | undefined;
|
|
38
|
+
/** 读取正数配置。 */
|
|
39
|
+
export declare function normalizePositiveNumber(value: unknown, fallback: number): number;
|
|
40
|
+
/** 安全解析 JSON 对象。 */
|
|
41
|
+
export declare function parseRecordJson(value: unknown): Record<string, unknown>;
|
|
42
|
+
/** 安全解析 UIMessage。 */
|
|
43
|
+
export declare function parseImageMessage(value: unknown): AIImageProviderResult["result"] | undefined;
|
|
44
|
+
/** 把 TableApi 普通行转成图片任务记录。 */
|
|
45
|
+
export declare function rowToAsyncJobRecord(row: Record<string, unknown>): AsyncJobRecord;
|
|
46
|
+
/** 保留已知 HTTP 错误状态,其它异常统一包装成上游错误。 */
|
|
47
|
+
export declare function imageActionError(error: unknown, fallback_message: string): Error;
|
|
48
|
+
/** 从输出对象中读取 Provider usage。 */
|
|
49
|
+
export declare function extractUsage(output: unknown): unknown;
|
|
50
|
+
/** 兼容常见 Provider usage 字段。 */
|
|
51
|
+
export declare function normalizeUsage(usage: unknown): {
|
|
52
|
+
/** 输入 token 数。 */
|
|
53
|
+
input_tokens?: number;
|
|
54
|
+
/** 输出 token 数。 */
|
|
55
|
+
output_tokens?: number;
|
|
56
|
+
/** 缓存命中的输入 token 数。 */
|
|
57
|
+
cached_tokens?: number;
|
|
58
|
+
/** 推理 token 数。 */
|
|
59
|
+
reasoning_tokens?: number;
|
|
60
|
+
};
|
|
61
|
+
/** 统计 UIMessage file parts 里的图片数量。 */
|
|
62
|
+
export declare function countImageOutputs(output: unknown): number | undefined;
|
|
63
|
+
/** 判断 file part URL 是否是可转存的远程 URL。 */
|
|
64
|
+
export declare function isStorableRemoteFilePart(part: unknown): part is StoredImagePart;
|
|
65
|
+
/** 读取 file part 的媒体类型。 */
|
|
66
|
+
export declare function readFilePartMediaType(part: Record<string, unknown>): string;
|
|
67
|
+
/** 读取 file part 的建议文件名。 */
|
|
68
|
+
export declare function readFilePartFilename(part: Record<string, unknown>): string | undefined;
|
|
69
|
+
/** 判断 Provider 是否返回了图片任务创建结果。 */
|
|
70
|
+
export declare function isImageProviderCreateResult(value: unknown): value is AIImageProviderCreateResult;
|
|
71
|
+
/** 判断 Provider 是否返回了图片任务查询结果。 */
|
|
72
|
+
export declare function isImageProviderResult(value: unknown): value is AIImageProviderResult;
|
|
73
|
+
//# sourceMappingURL=ai-service-values.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-service-values.d.ts","sourceRoot":"","sources":["../../../src/service/ai/ai-service-values.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIService 值解析与类型守卫模块。
|
|
3
|
+
*
|
|
4
|
+
* 集中管理 Provider 输出、usage 和图片任务数据的纯函数转换,让 AIService 主模块
|
|
5
|
+
* 只保留路由、生命周期与持久化编排。
|
|
6
|
+
*/
|
|
7
|
+
import { httpError } from "../../utils/helpers.js";
|
|
8
|
+
import { normalizeAIUsage } from "./helpers.js";
|
|
9
|
+
/** 判断一个值是否为 HTTP Response。 */
|
|
10
|
+
export function isResponse(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && "status" in value && "headers" in value;
|
|
12
|
+
}
|
|
13
|
+
/** 判断一个值是否为普通对象。 */
|
|
14
|
+
export function isRecord(value) {
|
|
15
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
16
|
+
}
|
|
17
|
+
/** 判断 Provider 是否返回了带账单 Response。 */
|
|
18
|
+
export function isProviderChargedResponse(value) {
|
|
19
|
+
return isRecord(value) && value.response instanceof Response;
|
|
20
|
+
}
|
|
21
|
+
/** 判断 Provider 是否返回了带账单普通输出。 */
|
|
22
|
+
export function isProviderChargedOutput(value) {
|
|
23
|
+
return isRecord(value) && "output" in value;
|
|
24
|
+
}
|
|
25
|
+
/** 判断一个值是否为 Promise-like。 */
|
|
26
|
+
export function isPromiseLike(value) {
|
|
27
|
+
return Boolean(value && typeof value === "object" && "then" in value && typeof value.then === "function");
|
|
28
|
+
}
|
|
29
|
+
/** 读取可选字符串。 */
|
|
30
|
+
export function readOptionalString(value) {
|
|
31
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
32
|
+
}
|
|
33
|
+
/** 读取可空字符串字段。 */
|
|
34
|
+
function readNullableString(value) {
|
|
35
|
+
return typeof value === "string" ? value : null;
|
|
36
|
+
}
|
|
37
|
+
/** 读取可空数字字符串字段。 */
|
|
38
|
+
function readNullableNumberString(value) {
|
|
39
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
40
|
+
return String(value);
|
|
41
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
42
|
+
}
|
|
43
|
+
/** 读取可选数字。 */
|
|
44
|
+
export function readOptionalNumber(value) {
|
|
45
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
46
|
+
return value;
|
|
47
|
+
if (typeof value !== "string" || !value.trim())
|
|
48
|
+
return undefined;
|
|
49
|
+
const parsed = Number(value);
|
|
50
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
51
|
+
}
|
|
52
|
+
/** 读取正数配置。 */
|
|
53
|
+
export function normalizePositiveNumber(value, fallback) {
|
|
54
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
55
|
+
}
|
|
56
|
+
/** 读取任务状态。 */
|
|
57
|
+
function readJobStatus(value) {
|
|
58
|
+
return value === "queued" || value === "running" || value === "fetching" || value === "succeeded" || value === "failed"
|
|
59
|
+
? value
|
|
60
|
+
: "failed";
|
|
61
|
+
}
|
|
62
|
+
/** 安全解析 JSON 对象。 */
|
|
63
|
+
export function parseRecordJson(value) {
|
|
64
|
+
if (typeof value !== "string" || !value.trim())
|
|
65
|
+
return {};
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(value);
|
|
68
|
+
return isRecord(parsed) ? parsed : {};
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** 安全解析 UIMessage。 */
|
|
75
|
+
export function parseImageMessage(value) {
|
|
76
|
+
const record = parseRecordJson(value);
|
|
77
|
+
return record.role === "assistant" && Array.isArray(record.parts)
|
|
78
|
+
? record
|
|
79
|
+
: undefined;
|
|
80
|
+
}
|
|
81
|
+
/** 把 TableApi 普通行转成图片任务记录。 */
|
|
82
|
+
export function rowToAsyncJobRecord(row) {
|
|
83
|
+
return {
|
|
84
|
+
job_id: String(row.job_id ?? ""),
|
|
85
|
+
job_type: String(row.job_type ?? ""),
|
|
86
|
+
status: readJobStatus(row.status),
|
|
87
|
+
input_json: String(row.input_json ?? "{}"),
|
|
88
|
+
state_json: readNullableString(row.state_json),
|
|
89
|
+
result_json: readNullableString(row.result_json),
|
|
90
|
+
error: readNullableString(row.error),
|
|
91
|
+
message: readNullableString(row.message),
|
|
92
|
+
poll_after_ms: readNullableNumberString(row.poll_after_ms),
|
|
93
|
+
city_id: readNullableString(row.city_id),
|
|
94
|
+
user_id: readNullableString(row.user_id),
|
|
95
|
+
service_id: readNullableString(row.service_id),
|
|
96
|
+
model_id: readNullableString(row.model_id),
|
|
97
|
+
created_at: String(row.created_at ?? ""),
|
|
98
|
+
updated_at: String(row.updated_at ?? ""),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** 保留已知 HTTP 错误状态,其它异常统一包装成上游错误。 */
|
|
102
|
+
export function imageActionError(error, fallback_message) {
|
|
103
|
+
if (error instanceof Error && typeof error.statusCode === "number")
|
|
104
|
+
return error;
|
|
105
|
+
return httpError(502, error instanceof Error ? error.message : fallback_message);
|
|
106
|
+
}
|
|
107
|
+
/** 从输出对象中读取 Provider usage。 */
|
|
108
|
+
export function extractUsage(output) {
|
|
109
|
+
if (!isRecord(output))
|
|
110
|
+
return undefined;
|
|
111
|
+
const metadata = isRecord(output.metadata) ? output.metadata : undefined;
|
|
112
|
+
if (metadata && "usage" in metadata)
|
|
113
|
+
return metadata.usage;
|
|
114
|
+
if (metadata && "usageMetadata" in metadata)
|
|
115
|
+
return metadata.usageMetadata;
|
|
116
|
+
if ("usage" in output)
|
|
117
|
+
return output.usage;
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
/** 兼容常见 Provider usage 字段。 */
|
|
121
|
+
export function normalizeUsage(usage) {
|
|
122
|
+
return normalizeAIUsage(usage);
|
|
123
|
+
}
|
|
124
|
+
/** 统计 UIMessage file parts 里的图片数量。 */
|
|
125
|
+
export function countImageOutputs(output) {
|
|
126
|
+
if (!isRecord(output) || !Array.isArray(output.parts))
|
|
127
|
+
return undefined;
|
|
128
|
+
const count = output.parts.filter((part) => {
|
|
129
|
+
if (!isRecord(part))
|
|
130
|
+
return false;
|
|
131
|
+
const type = String(part.type ?? "");
|
|
132
|
+
const media_type = String(part.mediaType ?? part.media_type ?? "");
|
|
133
|
+
return type === "file" && media_type.startsWith("image/");
|
|
134
|
+
}).length;
|
|
135
|
+
return count > 0 ? count : undefined;
|
|
136
|
+
}
|
|
137
|
+
/** 判断 file part URL 是否是可转存的远程 URL。 */
|
|
138
|
+
export function isStorableRemoteFilePart(part) {
|
|
139
|
+
if (!isRecord(part) || part.type !== "file")
|
|
140
|
+
return false;
|
|
141
|
+
const url = readOptionalString(part.url);
|
|
142
|
+
return Boolean(url && /^https?:\/\//iu.test(url));
|
|
143
|
+
}
|
|
144
|
+
/** 读取 file part 的媒体类型。 */
|
|
145
|
+
export function readFilePartMediaType(part) {
|
|
146
|
+
return readOptionalString(part.mediaType) ?? readOptionalString(part.media_type) ?? "application/octet-stream";
|
|
147
|
+
}
|
|
148
|
+
/** 读取 file part 的建议文件名。 */
|
|
149
|
+
export function readFilePartFilename(part) {
|
|
150
|
+
return readOptionalString(part.filename);
|
|
151
|
+
}
|
|
152
|
+
/** 判断 Provider 是否返回了图片任务创建结果。 */
|
|
153
|
+
export function isImageProviderCreateResult(value) {
|
|
154
|
+
if (!value || typeof value !== "object")
|
|
155
|
+
return false;
|
|
156
|
+
const record = value;
|
|
157
|
+
return typeof record.job_id === "string" && Boolean(record.job_id.trim()) && isImageJobStatus(record.status);
|
|
158
|
+
}
|
|
159
|
+
/** 判断 Provider 是否返回了图片任务查询结果。 */
|
|
160
|
+
export function isImageProviderResult(value) {
|
|
161
|
+
if (!value || typeof value !== "object")
|
|
162
|
+
return false;
|
|
163
|
+
const record = value;
|
|
164
|
+
if (typeof record.job_id !== "string" || !record.job_id.trim() || !isImageJobStatus(record.status))
|
|
165
|
+
return false;
|
|
166
|
+
if (record.status === "succeeded") {
|
|
167
|
+
const result = isRecord(record.result) ? record.result : undefined;
|
|
168
|
+
if (!result || result.role !== "assistant" || !Array.isArray(result.parts))
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
/** 判断图片任务状态。 */
|
|
174
|
+
function isImageJobStatus(value) {
|
|
175
|
+
return value === "queued" || value === "running" || value === "succeeded" || value === "failed";
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=ai-service-values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-service-values.js","sourceRoot":"","sources":["../../../src/service/ai/ai-service-values.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - POST /v1/ai/image/create — 创建图片生成任务
|
|
14
14
|
* - POST /v1/ai/image/result — 查询图片生成任务
|
|
15
15
|
* - POST /v1/ai/chat/completions — OpenAI 兼容端点
|
|
16
|
+
* - POST /v1/ai/language-model/stream — CityModel 原生 LanguageModelV3 端点
|
|
16
17
|
* - GET /v1/ai/models — 模型列表
|
|
17
18
|
*/
|
|
18
19
|
import { Service } from "../service.js";
|
|
@@ -47,6 +48,13 @@ export declare class AIService extends Service {
|
|
|
47
48
|
/** 按媒体输入解析最终模型,推理强度必须在该步骤之后解析。 */
|
|
48
49
|
private plan_text_execution;
|
|
49
50
|
private handleModality;
|
|
51
|
+
/**
|
|
52
|
+
* 执行 CityModel 原生 LanguageModelV3 调用。
|
|
53
|
+
*
|
|
54
|
+
* 路由、fallback、reasoning 和计费仍由 AIService 统一拥有;transport 模块只负责
|
|
55
|
+
* 调用最终模型并编码 SSE,避免把 Provider 决策泄漏到客户端。
|
|
56
|
+
*/
|
|
57
|
+
private handleLanguageModelStream;
|
|
50
58
|
private createImageJob;
|
|
51
59
|
private readImageJob;
|
|
52
60
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../../src/service/ai/ai-service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../../src/service/ai/ai-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EAEX,WAAW,EACZ,MAAM,YAAY,CAAC;AA4EpB,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAYrD,qBAAa,SAAU,SAAQ,OAAO;IACpC,YAAY;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAEhD,0CAA0C;IAC1C,OAAO,CAAC,eAAe,CAA+B;IAEtD,iBAAiB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,uCAAuC;IACvC,OAAO,CAAC,QAAQ,CAAC,6BAA6B,CAAS;gBAE3C,OAAO,GAAE,gBAAqB;IA+C1C,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC,WAAW,GAAG,WAAW,EAAE,CAAC,EAAE,GAAG,IAAI;IAKrD,UAAU,IAAI,WAAW,EAAE;IAI3B,SAAS,IAAI,OAAO;IAMpB,OAAO,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG;QAAE,KAAK,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAE;IAgB7G,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,2BAA2B;IAmCnC,OAAO,CAAC,kBAAkB;IAS1B,kCAAkC;IAClC,OAAO,CAAC,mBAAmB;YAkBb,cAAc;IAiC5B;;;;;OAKG;YACW,yBAAyB;YA8CzB,cAAc;YAgBd,YAAY;IAS1B;;OAEG;YACW,aAAa;IAuD3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAOjC;;OAEG;IACH,OAAO,CAAC,kCAAkC;IAc1C;;OAEG;YACW,iBAAiB;IAU/B;;;;;;;OAOG;YACW,2BAA2B;IAwDzC;;OAEG;YACW,cAAc;IAuB5B;;OAEG;YACW,eAAe;IAW7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,gBAAgB;YAcV,qBAAqB;IAoCnC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA0B3B;;;;;;;OAOG;YACW,QAAQ;IAMtB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;OAEG;YACW,YAAY;IA0D1B,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE;QAC/C,GAAG,EAAE,SAAS,CAAC;QACf,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;KACtC,GAAG,WAAW,EAAE;CAMlB"}
|
|
@@ -13,21 +13,25 @@
|
|
|
13
13
|
* - POST /v1/ai/image/create — 创建图片生成任务
|
|
14
14
|
* - POST /v1/ai/image/result — 查询图片生成任务
|
|
15
15
|
* - POST /v1/ai/chat/completions — OpenAI 兼容端点
|
|
16
|
+
* - POST /v1/ai/language-model/stream — CityModel 原生 LanguageModelV3 端点
|
|
16
17
|
* - GET /v1/ai/models — 模型列表
|
|
17
18
|
*/
|
|
18
19
|
import { Service } from "../service.js";
|
|
19
20
|
import { httpError } from "../../utils/helpers.js";
|
|
20
21
|
import { sqliteAsyncJobs } from "../async-job/schema.js";
|
|
21
|
-
import { normalizeAIUsage } from "./helpers.js";
|
|
22
22
|
import { attach_resolved_reasoning, resolve_model_reasoning, } from "./reasoning.js";
|
|
23
23
|
import { AIModelRegistry } from "./model-registry.js";
|
|
24
24
|
import { resolve_text_routing_plan } from "./model-routing.js";
|
|
25
25
|
import { claim_image_job, finish_image_job_fetch, release_image_job_claim, } from "./image-job-store.js";
|
|
26
26
|
import { settle_response_charge } from "./charge-runtime.js";
|
|
27
|
+
import { create_city_language_model_stream, decode_city_language_model_request, } from "./language-model-stream.js";
|
|
28
|
+
import { countImageOutputs, extractUsage, imageActionError, isImageProviderCreateResult, isImageProviderResult, isPromiseLike, isProviderChargedOutput, isProviderChargedResponse, isResponse, isStorableRemoteFilePart, normalizePositiveNumber, normalizeUsage, parseImageMessage, parseRecordJson, readFilePartFilename, readFilePartMediaType, readOptionalNumber, readOptionalString, rowToAsyncJobRecord, } from "./ai-service-values.js";
|
|
27
29
|
/** AIService 直接暴露的 SDK 通路模态列表。图片只通过 image/create + image/result 暴露。 */
|
|
28
30
|
const MODALITIES = ["text", "stream", "video", "tts", "asr"];
|
|
29
31
|
/** 用户侧默认以 text 模态排序模型 */
|
|
30
32
|
const DEFAULT_MODEL_MODE = "text";
|
|
33
|
+
/** CityModel 原生 LanguageModelV3 运行模式。 */
|
|
34
|
+
const LANGUAGE_MODEL_MODE = "language_model";
|
|
31
35
|
/** 图片任务的内部 action 列表。 */
|
|
32
36
|
const IMAGE_ACTION_MODES = ["image_create", "image_fetch"];
|
|
33
37
|
/** 图片生成任务在通用 async_jobs 表中的类型。 */
|
|
@@ -38,235 +42,14 @@ const IMAGE_FETCH_ACTION = "image/fetch";
|
|
|
38
42
|
const DEFAULT_IMAGE_MAX_PENDING_DURATION_MS = 2 * 60 * 60 * 1000;
|
|
39
43
|
/** 图片任务 pending 超时错误。 */
|
|
40
44
|
const IMAGE_PENDING_TIMEOUT_ERROR = "upstream timeout";
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
function isResponse(value) {
|
|
45
|
-
return typeof value === "object" && value !== null && "status" in value && "headers" in value;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* 判断一个值是否为普通对象。
|
|
49
|
-
*/
|
|
50
|
-
function isRecord(value) {
|
|
51
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* 判断 Provider 是否返回了带账单 Response。
|
|
55
|
-
*/
|
|
56
|
-
function isProviderChargedResponse(value) {
|
|
57
|
-
return isRecord(value) && value.response instanceof Response;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* 判断 Provider 是否返回了带账单普通输出。
|
|
61
|
-
*/
|
|
62
|
-
function isProviderChargedOutput(value) {
|
|
63
|
-
return isRecord(value) && "output" in value;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* 判断一个值是否为 Promise-like。
|
|
67
|
-
*/
|
|
68
|
-
function isPromiseLike(value) {
|
|
69
|
-
return Boolean(value && typeof value === "object" && "then" in value && typeof value.then === "function");
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* 读取可选字符串。
|
|
73
|
-
*/
|
|
74
|
-
function readOptionalString(value) {
|
|
75
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* 读取可空字符串字段。
|
|
79
|
-
*/
|
|
80
|
-
function readNullableString(value) {
|
|
81
|
-
return typeof value === "string" ? value : null;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* 读取可空数字字符串字段。
|
|
85
|
-
*/
|
|
86
|
-
function readNullableNumberString(value) {
|
|
87
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
88
|
-
return String(value);
|
|
89
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* 读取可选数字。
|
|
93
|
-
*/
|
|
94
|
-
function readOptionalNumber(value) {
|
|
95
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
96
|
-
return value;
|
|
97
|
-
if (typeof value !== "string" || !value.trim())
|
|
98
|
-
return undefined;
|
|
99
|
-
const parsed = Number(value);
|
|
100
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* 读取正数配置。
|
|
104
|
-
*/
|
|
105
|
-
function normalizePositiveNumber(value, fallback) {
|
|
106
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
107
|
-
? value
|
|
108
|
-
: fallback;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* 读取任务状态。
|
|
112
|
-
*/
|
|
113
|
-
function readJobStatus(value) {
|
|
114
|
-
return value === "queued" || value === "running" || value === "fetching" || value === "succeeded" || value === "failed"
|
|
115
|
-
? value
|
|
116
|
-
: "failed";
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* 安全解析 JSON 对象。
|
|
120
|
-
*/
|
|
121
|
-
function parseRecordJson(value) {
|
|
122
|
-
if (typeof value !== "string" || !value.trim())
|
|
123
|
-
return {};
|
|
124
|
-
try {
|
|
125
|
-
const parsed = JSON.parse(value);
|
|
126
|
-
return isRecord(parsed) ? parsed : {};
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return {};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* 安全解析 UIMessage。
|
|
134
|
-
*/
|
|
135
|
-
function parseImageMessage(value) {
|
|
136
|
-
const record = parseRecordJson(value);
|
|
137
|
-
return record.role === "assistant" && Array.isArray(record.parts)
|
|
138
|
-
? record
|
|
139
|
-
: undefined;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* 把 TableApi 普通行转成图片任务记录。
|
|
143
|
-
*/
|
|
144
|
-
function rowToAsyncJobRecord(row) {
|
|
145
|
-
return {
|
|
146
|
-
job_id: String(row.job_id ?? ""),
|
|
147
|
-
job_type: String(row.job_type ?? ""),
|
|
148
|
-
status: readJobStatus(row.status),
|
|
149
|
-
input_json: String(row.input_json ?? "{}"),
|
|
150
|
-
state_json: readNullableString(row.state_json),
|
|
151
|
-
result_json: readNullableString(row.result_json),
|
|
152
|
-
error: readNullableString(row.error),
|
|
153
|
-
message: readNullableString(row.message),
|
|
154
|
-
poll_after_ms: readNullableNumberString(row.poll_after_ms),
|
|
155
|
-
city_id: readNullableString(row.city_id),
|
|
156
|
-
user_id: readNullableString(row.user_id),
|
|
157
|
-
service_id: readNullableString(row.service_id),
|
|
158
|
-
model_id: readNullableString(row.model_id),
|
|
159
|
-
created_at: String(row.created_at ?? ""),
|
|
160
|
-
updated_at: String(row.updated_at ?? ""),
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* 保留已知 HTTP 错误状态,其它异常统一包装成上游错误。
|
|
165
|
-
*/
|
|
166
|
-
function imageActionError(error, fallback_message) {
|
|
167
|
-
if (error instanceof Error && typeof error.statusCode === "number") {
|
|
168
|
-
return error;
|
|
169
|
-
}
|
|
170
|
-
return httpError(502, error instanceof Error ? error.message : fallback_message);
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* 从输出对象中读取 provider usage。
|
|
174
|
-
*/
|
|
175
|
-
function extractUsage(output) {
|
|
176
|
-
if (!isRecord(output))
|
|
177
|
-
return undefined;
|
|
178
|
-
const metadata = isRecord(output.metadata) ? output.metadata : undefined;
|
|
179
|
-
if (metadata && "usage" in metadata)
|
|
180
|
-
return metadata.usage;
|
|
181
|
-
if (metadata && "usageMetadata" in metadata)
|
|
182
|
-
return metadata.usageMetadata;
|
|
183
|
-
if ("usage" in output)
|
|
184
|
-
return output.usage;
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* 兼容常见 provider usage 字段。
|
|
189
|
-
*/
|
|
190
|
-
function normalizeUsage(usage) {
|
|
191
|
-
return normalizeAIUsage(usage);
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* 统计 UIMessage file parts 里的图片数量。
|
|
195
|
-
*/
|
|
196
|
-
function countImageOutputs(output) {
|
|
197
|
-
if (!isRecord(output) || !Array.isArray(output.parts))
|
|
198
|
-
return undefined;
|
|
199
|
-
const count = output.parts.filter((part) => {
|
|
200
|
-
if (!isRecord(part))
|
|
201
|
-
return false;
|
|
202
|
-
const type = String(part.type ?? "");
|
|
203
|
-
const media_type = String(part.mediaType ?? part.media_type ?? "");
|
|
204
|
-
return type === "file" && media_type.startsWith("image/");
|
|
205
|
-
}).length;
|
|
206
|
-
return count > 0 ? count : undefined;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* 判断 file part URL 是否是可转存的远程 URL。
|
|
210
|
-
*/
|
|
211
|
-
function isStorableRemoteFilePart(part) {
|
|
212
|
-
if (!isRecord(part))
|
|
213
|
-
return false;
|
|
214
|
-
if (part.type !== "file")
|
|
215
|
-
return false;
|
|
216
|
-
const url = readOptionalString(part.url);
|
|
217
|
-
return Boolean(url && /^https?:\/\//iu.test(url));
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* 读取 file part 的媒体类型。
|
|
221
|
-
*/
|
|
222
|
-
function readFilePartMediaType(part) {
|
|
223
|
-
return readOptionalString(part.mediaType)
|
|
224
|
-
?? readOptionalString(part.media_type)
|
|
225
|
-
?? "application/octet-stream";
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* 读取 file part 的建议文件名。
|
|
229
|
-
*/
|
|
230
|
-
function readFilePartFilename(part) {
|
|
231
|
-
return readOptionalString(part.filename);
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* 判断 provider 是否返回了图片任务创建结果。
|
|
235
|
-
*/
|
|
236
|
-
function isImageProviderCreateResult(value) {
|
|
45
|
+
/** 判断 Provider runtime 是否返回了可直接调用的 LanguageModelV3。 */
|
|
46
|
+
function is_language_model_v3(value) {
|
|
237
47
|
if (!value || typeof value !== "object")
|
|
238
48
|
return false;
|
|
239
|
-
const
|
|
240
|
-
return
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* 判断 provider 是否返回了图片任务查询结果。
|
|
246
|
-
*/
|
|
247
|
-
function isImageProviderResult(value) {
|
|
248
|
-
if (!value || typeof value !== "object")
|
|
249
|
-
return false;
|
|
250
|
-
const record = value;
|
|
251
|
-
if (typeof record.job_id !== "string" || !record.job_id.trim())
|
|
252
|
-
return false;
|
|
253
|
-
if (!isImageJobStatus(record.status))
|
|
254
|
-
return false;
|
|
255
|
-
if (record.status === "succeeded") {
|
|
256
|
-
const result = isRecord(record.result) ? record.result : undefined;
|
|
257
|
-
if (!result || result.role !== "assistant" || !Array.isArray(result.parts))
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* 判断图片任务状态。
|
|
264
|
-
*/
|
|
265
|
-
function isImageJobStatus(value) {
|
|
266
|
-
return value === "queued" ||
|
|
267
|
-
value === "running" ||
|
|
268
|
-
value === "succeeded" ||
|
|
269
|
-
value === "failed";
|
|
49
|
+
const model = value;
|
|
50
|
+
return model.specificationVersion === "v3" &&
|
|
51
|
+
typeof model.doStream === "function" &&
|
|
52
|
+
typeof model.doGenerate === "function";
|
|
270
53
|
}
|
|
271
54
|
export class AIService extends Service {
|
|
272
55
|
/** 模型注册表 */
|
|
@@ -301,6 +84,10 @@ export class AIService extends Service {
|
|
|
301
84
|
this.action("chat/completions", async (ctx) => this.handleChatCompletions(ctx), {
|
|
302
85
|
auth: ["user", "admin"],
|
|
303
86
|
}).before((ctx) => this.precheck(ctx));
|
|
87
|
+
// CityModel 原生 LanguageModelV3 endpoint。
|
|
88
|
+
this.action("language-model/stream", async (ctx) => this.handleLanguageModelStream(ctx), {
|
|
89
|
+
auth: ["user", "admin"],
|
|
90
|
+
}).before((ctx) => this.precheck(ctx));
|
|
304
91
|
// 模型列表走同一路径,根据身份决定可见范围。
|
|
305
92
|
this.action("models", (ctx) => ({
|
|
306
93
|
items: AIService.listModels(this, {
|
|
@@ -342,6 +129,11 @@ export class AIService extends Service {
|
|
|
342
129
|
return model_id || undefined;
|
|
343
130
|
}
|
|
344
131
|
getAction(model, mode) {
|
|
132
|
+
if (mode === LANGUAGE_MODEL_MODE) {
|
|
133
|
+
return model.language_model
|
|
134
|
+
? (ctx) => model.language_model?.create_language_model(ctx)
|
|
135
|
+
: undefined;
|
|
136
|
+
}
|
|
345
137
|
if (mode === "image" || IMAGE_ACTION_MODES.includes(mode)) {
|
|
346
138
|
const has_image_actions = Boolean(model.actions.image_create && model.actions.image_fetch);
|
|
347
139
|
if (!has_image_actions)
|
|
@@ -437,6 +229,48 @@ export class AIService extends Service {
|
|
|
437
229
|
return new Response(JSON.stringify({ error: message }), { status, headers: { "content-type": "application/json" } });
|
|
438
230
|
}
|
|
439
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* 执行 CityModel 原生 LanguageModelV3 调用。
|
|
234
|
+
*
|
|
235
|
+
* 路由、fallback、reasoning 和计费仍由 AIService 统一拥有;transport 模块只负责
|
|
236
|
+
* 调用最终模型并编码 SSE,避免把 Provider 决策泄漏到客户端。
|
|
237
|
+
*/
|
|
238
|
+
async handleLanguageModelStream(ctx) {
|
|
239
|
+
const request = decode_city_language_model_request(ctx.input);
|
|
240
|
+
ctx.input = {
|
|
241
|
+
...ctx.input,
|
|
242
|
+
model: request.model_id,
|
|
243
|
+
...(request.reasoning_effort ? { reasoning_effort: request.reasoning_effort } : {}),
|
|
244
|
+
};
|
|
245
|
+
const initial_resolved = this.resolve({ model: request.model_id, mode: LANGUAGE_MODEL_MODE }, ctx.env);
|
|
246
|
+
const routing = this.plan_text_execution(initial_resolved, ctx, LANGUAGE_MODEL_MODE);
|
|
247
|
+
const resolved = routing.resolved;
|
|
248
|
+
const reasoning = resolved.model ? resolve_model_reasoning(resolved.model, ctx.input) : undefined;
|
|
249
|
+
this.attachResolvedModel(ctx, resolved.model, LANGUAGE_MODEL_MODE, routing);
|
|
250
|
+
attach_resolved_reasoning(ctx, reasoning);
|
|
251
|
+
const started_at = Date.now();
|
|
252
|
+
const output = await resolved.action(ctx);
|
|
253
|
+
if (!is_language_model_v3(output)) {
|
|
254
|
+
throw httpError(500, "Model language runtime did not return LanguageModelV3");
|
|
255
|
+
}
|
|
256
|
+
const provider_options = resolved.model?.language_model?.build_provider_options?.(ctx, output);
|
|
257
|
+
const execution = await create_city_language_model_stream({
|
|
258
|
+
model: output,
|
|
259
|
+
call: request.call,
|
|
260
|
+
provider_options,
|
|
261
|
+
signal: ctx.request?.signal,
|
|
262
|
+
});
|
|
263
|
+
const completion = execution.completion.then((part) => {
|
|
264
|
+
if (part)
|
|
265
|
+
this.attachOutputMetering(ctx, part, LANGUAGE_MODEL_MODE, started_at);
|
|
266
|
+
return part;
|
|
267
|
+
});
|
|
268
|
+
const charge = resolved.model?.bill
|
|
269
|
+
? completion.then((part) => part ? resolved.model?.bill?.(ctx, part) : undefined)
|
|
270
|
+
: undefined;
|
|
271
|
+
const charged_response = await this.handleCharge(ctx, charge, true, undefined, undefined, execution.response);
|
|
272
|
+
return charged_response ?? execution.response;
|
|
273
|
+
}
|
|
440
274
|
// ========== 图片任务通路 ==========
|
|
441
275
|
async createImageJob(ctx) {
|
|
442
276
|
const resolved = this.resolve({ model: this.normalizeModelId(ctx.input.model), mode: "image_create" }, ctx.env);
|