702 lines
18 KiB
Dart
702 lines
18 KiB
Dart
/// LLM可观测性相关数据模型
|
|
/// 用于管理后台查看和分析大模型调用日志
|
|
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
import '../../utils/date_time_parser.dart';
|
|
|
|
part 'llm_observability_models.g.dart';
|
|
|
|
/// 自定义时间戳转换器
|
|
class TimestampConverter implements JsonConverter<DateTime, dynamic> {
|
|
const TimestampConverter();
|
|
|
|
@override
|
|
DateTime fromJson(dynamic timestamp) {
|
|
return parseBackendDateTime(timestamp);
|
|
}
|
|
|
|
@override
|
|
dynamic toJson(DateTime timestamp) {
|
|
return timestamp.toIso8601String();
|
|
}
|
|
}
|
|
|
|
/// LLM调用日志
|
|
@JsonSerializable()
|
|
class LLMTrace extends Equatable {
|
|
final String id;
|
|
final String traceId;
|
|
final String provider;
|
|
final String model;
|
|
final String? userId;
|
|
final String? sessionId;
|
|
@JsonKey(name: 'createdAt')
|
|
@TimestampConverter()
|
|
final DateTime timestamp;
|
|
|
|
// 请求信息
|
|
final LLMRequest request;
|
|
|
|
// 响应信息
|
|
final LLMResponse? response;
|
|
|
|
// 性能指标
|
|
final LLMPerformanceMetrics? performance;
|
|
|
|
// 错误信息
|
|
final LLMError? error;
|
|
|
|
// 工具调用
|
|
final List<LLMToolCall>? toolCalls;
|
|
|
|
// 元数据
|
|
final Map<String, dynamic>? metadata;
|
|
|
|
// 状态
|
|
@JsonKey(defaultValue: LLMTraceStatus.pending)
|
|
final LLMTraceStatus status;
|
|
@JsonKey(defaultValue: false)
|
|
final bool isStreaming;
|
|
|
|
const LLMTrace({
|
|
required this.id,
|
|
required this.traceId,
|
|
required this.provider,
|
|
required this.model,
|
|
this.userId,
|
|
this.sessionId,
|
|
required this.timestamp,
|
|
required this.request,
|
|
this.response,
|
|
this.performance,
|
|
this.error,
|
|
this.toolCalls,
|
|
this.metadata,
|
|
this.status = LLMTraceStatus.pending,
|
|
this.isStreaming = false,
|
|
});
|
|
|
|
factory LLMTrace.fromJson(Map<String, dynamic> json) => _$LLMTraceFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMTraceToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [id, traceId, provider, model, userId, sessionId, timestamp, request, response, performance, error, toolCalls, metadata, status, isStreaming];
|
|
}
|
|
|
|
/// LLM请求信息
|
|
@JsonSerializable()
|
|
class LLMRequest extends Equatable {
|
|
final List<LLMMessage>? messages;
|
|
|
|
// 模型参数
|
|
final double? temperature;
|
|
final double? topP;
|
|
final int? topK;
|
|
final int? maxTokens;
|
|
final int? seed;
|
|
|
|
// 工具调用
|
|
final List<LLMTool>? tools;
|
|
final String? toolChoice;
|
|
|
|
// 格式设置
|
|
final String? responseFormat;
|
|
|
|
// 其他参数
|
|
final Map<String, dynamic>? additionalParameters;
|
|
|
|
const LLMRequest({
|
|
this.messages,
|
|
this.temperature,
|
|
this.topP,
|
|
this.topK,
|
|
this.maxTokens,
|
|
this.seed,
|
|
this.tools,
|
|
this.toolChoice,
|
|
this.responseFormat,
|
|
this.additionalParameters,
|
|
});
|
|
|
|
factory LLMRequest.fromJson(Map<String, dynamic> json) => _$LLMRequestFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMRequestToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [messages, temperature, topP, topK, maxTokens, seed, tools, toolChoice, responseFormat, additionalParameters];
|
|
}
|
|
|
|
/// LLM响应信息
|
|
@JsonSerializable()
|
|
class LLMResponse extends Equatable {
|
|
final String? id;
|
|
final String? content;
|
|
|
|
// Token使用情况
|
|
final LLMTokenUsage? tokenUsage;
|
|
|
|
// 完成原因
|
|
final String? finishReason;
|
|
|
|
// 工具调用结果
|
|
final List<LLMToolCallResult>? toolCallResults;
|
|
|
|
// 元数据
|
|
final Map<String, dynamic>? metadata;
|
|
|
|
// 流式数据
|
|
final List<String>? streamChunks;
|
|
|
|
const LLMResponse({
|
|
this.id,
|
|
this.content,
|
|
this.tokenUsage,
|
|
this.finishReason,
|
|
this.toolCallResults,
|
|
this.metadata,
|
|
this.streamChunks,
|
|
});
|
|
|
|
factory LLMResponse.fromJson(Map<String, dynamic> json) => _$LLMResponseFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMResponseToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [id, content, tokenUsage, finishReason, toolCallResults, metadata, streamChunks];
|
|
}
|
|
|
|
/// LLM消息
|
|
@JsonSerializable()
|
|
class LLMMessage extends Equatable {
|
|
final String role;
|
|
final String? content;
|
|
final String? name;
|
|
final Map<String, dynamic>? metadata;
|
|
|
|
const LLMMessage({
|
|
required this.role,
|
|
this.content,
|
|
this.name,
|
|
this.metadata,
|
|
});
|
|
|
|
factory LLMMessage.fromJson(Map<String, dynamic> json) => _$LLMMessageFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMMessageToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [role, content, name, metadata];
|
|
}
|
|
|
|
/// LLM工具定义
|
|
@JsonSerializable()
|
|
class LLMTool extends Equatable {
|
|
final String name;
|
|
final String? description;
|
|
final Map<String, dynamic>? parameters;
|
|
|
|
const LLMTool({
|
|
required this.name,
|
|
this.description,
|
|
this.parameters,
|
|
});
|
|
|
|
factory LLMTool.fromJson(Map<String, dynamic> json) => _$LLMToolFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMToolToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [name, description, parameters];
|
|
}
|
|
|
|
/// LLM工具调用
|
|
@JsonSerializable()
|
|
class LLMToolCall extends Equatable {
|
|
final String id;
|
|
final String name;
|
|
final Map<String, dynamic>? arguments;
|
|
final DateTime? timestamp;
|
|
|
|
const LLMToolCall({
|
|
required this.id,
|
|
required this.name,
|
|
this.arguments,
|
|
this.timestamp,
|
|
});
|
|
|
|
factory LLMToolCall.fromJson(Map<String, dynamic> json) => _$LLMToolCallFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMToolCallToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [id, name, arguments, timestamp];
|
|
}
|
|
|
|
/// LLM工具调用结果
|
|
@JsonSerializable()
|
|
class LLMToolCallResult extends Equatable {
|
|
final String toolCallId;
|
|
final String? result;
|
|
final LLMError? error;
|
|
|
|
const LLMToolCallResult({
|
|
required this.toolCallId,
|
|
this.result,
|
|
this.error,
|
|
});
|
|
|
|
factory LLMToolCallResult.fromJson(Map<String, dynamic> json) => _$LLMToolCallResultFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMToolCallResultToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [toolCallId, result, error];
|
|
}
|
|
|
|
/// Token使用情况
|
|
@JsonSerializable()
|
|
class LLMTokenUsage extends Equatable {
|
|
final int? promptTokens;
|
|
final int? completionTokens;
|
|
final int? totalTokens;
|
|
|
|
// 详细分解
|
|
final int? inputTokens;
|
|
final int? outputTokens;
|
|
final int? reasoningTokens;
|
|
final int? cachedTokens;
|
|
|
|
const LLMTokenUsage({
|
|
this.promptTokens,
|
|
this.completionTokens,
|
|
this.totalTokens,
|
|
this.inputTokens,
|
|
this.outputTokens,
|
|
this.reasoningTokens,
|
|
this.cachedTokens,
|
|
});
|
|
|
|
factory LLMTokenUsage.fromJson(Map<String, dynamic> json) => _$LLMTokenUsageFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMTokenUsageToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [promptTokens, completionTokens, totalTokens, inputTokens, outputTokens, reasoningTokens, cachedTokens];
|
|
}
|
|
|
|
/// 性能指标
|
|
@JsonSerializable()
|
|
class LLMPerformanceMetrics extends Equatable {
|
|
final int? requestLatencyMs;
|
|
final int? firstTokenLatencyMs;
|
|
final int? totalDurationMs;
|
|
|
|
// 吞吐量
|
|
final double? tokensPerSecond;
|
|
final double? charactersPerSecond;
|
|
|
|
// 队列时间
|
|
final int? queueTimeMs;
|
|
final int? processingTimeMs;
|
|
|
|
const LLMPerformanceMetrics({
|
|
this.requestLatencyMs,
|
|
this.firstTokenLatencyMs,
|
|
this.totalDurationMs,
|
|
this.tokensPerSecond,
|
|
this.charactersPerSecond,
|
|
this.queueTimeMs,
|
|
this.processingTimeMs,
|
|
});
|
|
|
|
factory LLMPerformanceMetrics.fromJson(Map<String, dynamic> json) => _$LLMPerformanceMetricsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMPerformanceMetricsToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [requestLatencyMs, firstTokenLatencyMs, totalDurationMs, tokensPerSecond, charactersPerSecond, queueTimeMs, processingTimeMs];
|
|
}
|
|
|
|
/// 错误信息
|
|
@JsonSerializable()
|
|
class LLMError extends Equatable {
|
|
final String? type;
|
|
final String? message;
|
|
final String? code;
|
|
final String? stackTrace;
|
|
final Map<String, dynamic>? details;
|
|
|
|
const LLMError({
|
|
this.type,
|
|
this.message,
|
|
this.code,
|
|
this.stackTrace,
|
|
this.details,
|
|
});
|
|
|
|
factory LLMError.fromJson(Map<String, dynamic> json) => _$LLMErrorFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMErrorToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [type, message, code, stackTrace, details];
|
|
}
|
|
|
|
/// LLM调用状态
|
|
enum LLMTraceStatus {
|
|
@JsonValue('pending')
|
|
pending,
|
|
@JsonValue('success')
|
|
success,
|
|
@JsonValue('error')
|
|
error,
|
|
@JsonValue('timeout')
|
|
timeout,
|
|
@JsonValue('cancelled')
|
|
cancelled,
|
|
}
|
|
|
|
/// 统计信息基类
|
|
@JsonSerializable()
|
|
class LLMStatistics extends Equatable {
|
|
final int totalCalls;
|
|
final int successfulCalls;
|
|
final int failedCalls;
|
|
final double successRate;
|
|
final double averageLatency;
|
|
final int totalTokens;
|
|
|
|
// 时间范围
|
|
final DateTime? startTime;
|
|
final DateTime? endTime;
|
|
|
|
// 详细统计
|
|
final Map<String, dynamic>? details;
|
|
|
|
const LLMStatistics({
|
|
required this.totalCalls,
|
|
required this.successfulCalls,
|
|
required this.failedCalls,
|
|
required this.successRate,
|
|
required this.averageLatency,
|
|
required this.totalTokens,
|
|
this.startTime,
|
|
this.endTime,
|
|
this.details,
|
|
});
|
|
|
|
factory LLMStatistics.fromJson(Map<String, dynamic> json) => _$LLMStatisticsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMStatisticsToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [totalCalls, successfulCalls, failedCalls, successRate, averageLatency, totalTokens, startTime, endTime, details];
|
|
}
|
|
|
|
/// 提供商统计
|
|
@JsonSerializable()
|
|
class ProviderStatistics extends Equatable {
|
|
final String provider;
|
|
final LLMStatistics statistics;
|
|
final List<ModelStatistics> models;
|
|
|
|
const ProviderStatistics({
|
|
required this.provider,
|
|
required this.statistics,
|
|
required this.models,
|
|
});
|
|
|
|
factory ProviderStatistics.fromJson(Map<String, dynamic> json) => _$ProviderStatisticsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$ProviderStatisticsToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [provider, statistics, models];
|
|
}
|
|
|
|
/// 模型统计
|
|
@JsonSerializable()
|
|
class ModelStatistics extends Equatable {
|
|
final String modelName;
|
|
final String provider;
|
|
final LLMStatistics statistics;
|
|
|
|
const ModelStatistics({
|
|
required this.modelName,
|
|
required this.provider,
|
|
required this.statistics,
|
|
});
|
|
|
|
factory ModelStatistics.fromJson(Map<String, dynamic> json) => _$ModelStatisticsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$ModelStatisticsToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [modelName, provider, statistics];
|
|
}
|
|
|
|
/// 用户统计
|
|
@JsonSerializable()
|
|
class UserStatistics extends Equatable {
|
|
final String userId;
|
|
final String? username;
|
|
final LLMStatistics statistics;
|
|
final List<String> topModels;
|
|
final List<String> topProviders;
|
|
|
|
const UserStatistics({
|
|
required this.userId,
|
|
this.username,
|
|
required this.statistics,
|
|
required this.topModels,
|
|
required this.topProviders,
|
|
});
|
|
|
|
factory UserStatistics.fromJson(Map<String, dynamic> json) => _$UserStatisticsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$UserStatisticsToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [userId, username, statistics, topModels, topProviders];
|
|
}
|
|
|
|
/// 错误统计
|
|
@JsonSerializable()
|
|
class ErrorStatistics extends Equatable {
|
|
final String errorType;
|
|
final int count;
|
|
final double percentage;
|
|
final List<String> topErrorMessages;
|
|
final List<String> affectedModels;
|
|
|
|
const ErrorStatistics({
|
|
required this.errorType,
|
|
required this.count,
|
|
required this.percentage,
|
|
required this.topErrorMessages,
|
|
required this.affectedModels,
|
|
});
|
|
|
|
factory ErrorStatistics.fromJson(Map<String, dynamic> json) => _$ErrorStatisticsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$ErrorStatisticsToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [errorType, count, percentage, topErrorMessages, affectedModels];
|
|
}
|
|
|
|
/// 性能统计
|
|
@JsonSerializable()
|
|
class PerformanceStatistics extends Equatable {
|
|
final double averageLatency;
|
|
final double medianLatency;
|
|
final double p95Latency;
|
|
final double p99Latency;
|
|
final double averageThroughput;
|
|
|
|
// 按时间分组的统计
|
|
final List<TimeBasedMetric> latencyTrends;
|
|
final List<TimeBasedMetric> throughputTrends;
|
|
|
|
const PerformanceStatistics({
|
|
required this.averageLatency,
|
|
required this.medianLatency,
|
|
required this.p95Latency,
|
|
required this.p99Latency,
|
|
required this.averageThroughput,
|
|
required this.latencyTrends,
|
|
required this.throughputTrends,
|
|
});
|
|
|
|
factory PerformanceStatistics.fromJson(Map<String, dynamic> json) => _$PerformanceStatisticsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$PerformanceStatisticsToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [averageLatency, medianLatency, p95Latency, p99Latency, averageThroughput, latencyTrends, throughputTrends];
|
|
}
|
|
|
|
/// 基于时间的指标
|
|
@JsonSerializable()
|
|
class TimeBasedMetric extends Equatable {
|
|
final DateTime timestamp;
|
|
final double value;
|
|
final String? label;
|
|
|
|
const TimeBasedMetric({
|
|
required this.timestamp,
|
|
required this.value,
|
|
this.label,
|
|
});
|
|
|
|
factory TimeBasedMetric.fromJson(Map<String, dynamic> json) => _$TimeBasedMetricFromJson(json);
|
|
Map<String, dynamic> toJson() => _$TimeBasedMetricToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [timestamp, value, label];
|
|
}
|
|
|
|
/// 系统健康状态
|
|
@JsonSerializable()
|
|
class SystemHealthStatus extends Equatable {
|
|
@JsonKey(defaultValue: HealthStatus.healthy)
|
|
final HealthStatus status;
|
|
final Map<String, ComponentHealth> components;
|
|
final String? message;
|
|
final DateTime? lastChecked;
|
|
|
|
const SystemHealthStatus({
|
|
this.status = HealthStatus.healthy,
|
|
required this.components,
|
|
this.message,
|
|
this.lastChecked,
|
|
});
|
|
|
|
factory SystemHealthStatus.fromJson(Map<String, dynamic> json) => _$SystemHealthStatusFromJson(json);
|
|
Map<String, dynamic> toJson() => _$SystemHealthStatusToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [status, components, message, lastChecked];
|
|
}
|
|
|
|
/// 组件健康状态
|
|
@JsonSerializable()
|
|
class ComponentHealth extends Equatable {
|
|
@JsonKey(defaultValue: HealthStatus.healthy)
|
|
final HealthStatus status;
|
|
final String? message;
|
|
final Map<String, dynamic>? metrics;
|
|
|
|
const ComponentHealth({
|
|
this.status = HealthStatus.healthy,
|
|
this.message,
|
|
this.metrics,
|
|
});
|
|
|
|
factory ComponentHealth.fromJson(Map<String, dynamic> json) => _$ComponentHealthFromJson(json);
|
|
Map<String, dynamic> toJson() => _$ComponentHealthToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [status, message, metrics];
|
|
}
|
|
|
|
/// 健康状态枚举
|
|
enum HealthStatus {
|
|
@JsonValue('healthy')
|
|
healthy,
|
|
@JsonValue('degraded')
|
|
degraded,
|
|
@JsonValue('unhealthy')
|
|
unhealthy,
|
|
@JsonValue('unknown')
|
|
unknown,
|
|
}
|
|
|
|
/// LLM日志搜索条件
|
|
@JsonSerializable()
|
|
class LLMTraceSearchCriteria extends Equatable {
|
|
final String? userId;
|
|
final String? provider;
|
|
final String? model;
|
|
final String? sessionId;
|
|
final bool? hasError;
|
|
final LLMTraceStatus? status;
|
|
final DateTime? startTime;
|
|
final DateTime? endTime;
|
|
|
|
// 分页
|
|
@JsonKey(defaultValue: 0)
|
|
final int page;
|
|
@JsonKey(defaultValue: 20)
|
|
final int size;
|
|
@JsonKey(defaultValue: 'timestamp')
|
|
final String sortBy;
|
|
@JsonKey(defaultValue: 'desc')
|
|
final String sortDir;
|
|
|
|
const LLMTraceSearchCriteria({
|
|
this.userId,
|
|
this.provider,
|
|
this.model,
|
|
this.sessionId,
|
|
this.hasError,
|
|
this.status,
|
|
this.startTime,
|
|
this.endTime,
|
|
this.page = 0,
|
|
this.size = 20,
|
|
this.sortBy = 'timestamp',
|
|
this.sortDir = 'desc',
|
|
});
|
|
|
|
factory LLMTraceSearchCriteria.fromJson(Map<String, dynamic> json) => _$LLMTraceSearchCriteriaFromJson(json);
|
|
Map<String, dynamic> toJson() => _$LLMTraceSearchCriteriaToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [userId, provider, model, sessionId, hasError, status, startTime, endTime, page, size, sortBy, sortDir];
|
|
}
|
|
|
|
/// API响应包装类
|
|
@JsonSerializable(genericArgumentFactories: true)
|
|
class ApiResponse<T> extends Equatable {
|
|
final bool success;
|
|
final String? message;
|
|
final T? data;
|
|
final String? error;
|
|
|
|
const ApiResponse({
|
|
required this.success,
|
|
this.message,
|
|
this.data,
|
|
this.error,
|
|
});
|
|
|
|
factory ApiResponse.fromJson(Map<String, dynamic> json, T Function(Object? json) fromJsonT) =>
|
|
_$ApiResponseFromJson(json, fromJsonT);
|
|
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
|
|
_$ApiResponseToJson(this, toJsonT);
|
|
|
|
@override
|
|
List<Object?> get props => [success, message, data, error];
|
|
}
|
|
|
|
/// 分页响应
|
|
@JsonSerializable(genericArgumentFactories: true)
|
|
class PagedResponse<T> extends Equatable {
|
|
final List<T> content;
|
|
final int page;
|
|
final int size;
|
|
final int totalElements;
|
|
final int totalPages;
|
|
@JsonKey(defaultValue: false)
|
|
final bool first;
|
|
@JsonKey(defaultValue: false)
|
|
final bool last;
|
|
|
|
const PagedResponse({
|
|
required this.content,
|
|
required this.page,
|
|
required this.size,
|
|
required this.totalElements,
|
|
required this.totalPages,
|
|
this.first = false,
|
|
this.last = false,
|
|
});
|
|
|
|
factory PagedResponse.fromJson(Map<String, dynamic> json, T Function(Object? json) fromJsonT) =>
|
|
_$PagedResponseFromJson(json, fromJsonT);
|
|
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
|
|
_$PagedResponseToJson(this, toJsonT);
|
|
|
|
@override
|
|
List<Object?> get props => [content, page, size, totalElements, totalPages, first, last];
|
|
}
|
|
|
|
/// 游标分页响应
|
|
@JsonSerializable(genericArgumentFactories: true)
|
|
class CursorPageResponse<T> extends Equatable {
|
|
final List<T> items;
|
|
final String? nextCursor;
|
|
@JsonKey(defaultValue: false)
|
|
final bool hasMore;
|
|
|
|
const CursorPageResponse({
|
|
required this.items,
|
|
this.nextCursor,
|
|
this.hasMore = false,
|
|
});
|
|
|
|
factory CursorPageResponse.fromJson(Map<String, dynamic> json, T Function(Object? json) fromJsonT) =>
|
|
_$CursorPageResponseFromJson(json, fromJsonT);
|
|
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
|
|
_$CursorPageResponseToJson(this, toJsonT);
|
|
|
|
@override
|
|
List<Object?> get props => [items, nextCursor, hasMore];
|
|
} |