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
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.github.timemachinelab.sfchain.controller;

import io.github.timemachinelab.sfchain.core.logging.AICallLog;
import io.github.timemachinelab.sfchain.core.logging.AICallLogManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

/**
* AI调用日志查询控制器
*/
@RestController
@RequestMapping("/sf/api/ai-logs")
public class AICallLogController {

@Autowired
private AICallLogManager logManager;

/**
* 获取所有日志
*/
@GetMapping
public ResponseEntity<List<AICallLog>> getAllLogs() {
return ResponseEntity.ok(logManager.getAllLogs());
}

/**
* 根据调用ID获取日志
*/
@GetMapping("/{callId}")
public ResponseEntity<AICallLog> getLog(@PathVariable String callId) {
AICallLog log = logManager.getLog(callId);
return log != null ? ResponseEntity.ok(log) : ResponseEntity.notFound().build();
}

/**
* 根据操作类型获取日志
*/
@GetMapping("/operation/{operationType}")
public ResponseEntity<List<AICallLog>> getLogsByOperation(@PathVariable String operationType) {
return ResponseEntity.ok(logManager.getLogsByOperation(operationType));
}

/**
* 根据模型名称获取日志
*/
@GetMapping("/model/{modelName}")
public ResponseEntity<List<AICallLog>> getLogsByModel(@PathVariable String modelName) {
return ResponseEntity.ok(logManager.getLogsByModel(modelName));
}

/**
* 获取统计信息
*/
@GetMapping("/statistics")
public ResponseEntity<AICallLogManager.LogStatistics> getStatistics() {
return ResponseEntity.ok(logManager.getStatistics());
}

/**
* 清空所有日志
*/
@DeleteMapping
public ResponseEntity<Map<String, String>> clearLogs() {
logManager.clearLogs();
return ResponseEntity.ok(Map.of("message", "所有日志已清空"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ public ResponseEntity<Object> getModel(@PathVariable String modelName) {
/**
* 创建或更新模型配置
*/
@PostMapping("/{modelName}")
@PostMapping("/save")
public ResponseEntity<Map<String, Object>> saveModel(
@PathVariable String modelName,
@Valid @RequestBody ModelConfigData config) {
Map<String, Object> result = new HashMap<>();
String modelName = config.getModelName();
try {
// 验证模型配置
boolean validationResult = validateModelConfig(modelName, config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,22 @@ public ResponseEntity<Object> getOperation(@PathVariable String operationType) {
}

/**
* 保存操作配置
* 保存操作配置 - 改为统一的save接口,从请求体获取operationType
*/
@PostMapping("/{operationType}")
@PostMapping("/save")
public ResponseEntity<Map<String, Object>> saveOperationConfig(
@PathVariable String operationType,
@Valid @RequestBody OperationConfigData config) {
Map<String, Object> result = new HashMap<>();
try {
// 从config对象中获取operationType
String operationType = config.getOperationType();

if (operationType == null || operationType.trim().isEmpty()) {
result.put("success", false);
result.put("message", "操作类型不能为空");
return ResponseEntity.badRequest().body(result);
}

persistenceManager.saveOperationConfig(operationType, config);

result.put("success", true);
Expand All @@ -110,12 +118,52 @@ public ResponseEntity<Map<String, Object>> saveOperationConfig(

return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("保存操作配置失败: {} - {}", operationType, e.getMessage());
log.error("保存操作配置失败: {}", e.getMessage());
result.put("success", false);
result.put("message", "保存失败: " + e.getMessage());
return ResponseEntity.badRequest().body(result);
}
}

/**
* 获取单个操作配置 - 改为POST请求体参数
*/
@PostMapping("/get")
public ResponseEntity<Object> getOperation(@RequestBody Map<String, String> request) {
try {
String operationType = request.get("operationType");

if (operationType == null || operationType.trim().isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of("error", "操作类型不能为空"));
}

Optional<OperationConfigData> operationOpt = persistenceManager.getOperationConfig(operationType);

if (operationOpt.isEmpty()) {
return ResponseEntity.notFound().build();
}

OperationConfigData operation = operationOpt.get();

// 如果有关联模型,获取模型信息
if (operation.getModelName() != null) {
Map<String, ModelConfigData> models = persistenceManager.getAllModelConfigs();
ModelConfigData model = models.get(operation.getModelName());

Map<String, Object> result = new HashMap<>();
result.put("operation", operation);
result.put("associatedModel", model);
return ResponseEntity.ok(result);
}

return ResponseEntity.ok(operation);
} catch (Exception e) {
log.error("获取操作配置失败: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "获取操作配置失败: " + e.getMessage()));
}
}

/**
* 批量设置操作模型映射
Expand Down Expand Up @@ -179,15 +227,22 @@ public ResponseEntity<Map<String, Object>> setOperationMappings(
}

/**
* 设置单个操作模型映射
* 设置单个操作模型映射 - 改为POST请求体参数
*/
@PostMapping("/{operationType}/mapping")
@PostMapping("/mapping")
public ResponseEntity<Map<String, Object>> setOperationMapping(
@PathVariable String operationType,
@RequestBody Map<String, String> request) {
Map<String, Object> result = new HashMap<>();
try {
String operationType = request.get("operationType");
String modelName = request.get("modelName");

if (operationType == null || operationType.trim().isEmpty()) {
result.put("success", false);
result.put("message", "操作类型不能为空");
return ResponseEntity.badRequest().body(result);
}

if (modelName == null || modelName.trim().isEmpty()) {
result.put("success", false);
result.put("message", "模型名称不能为空");
Expand Down Expand Up @@ -218,7 +273,7 @@ public ResponseEntity<Map<String, Object>> setOperationMapping(

return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("设置操作映射失败: {} - {}", operationType, e.getMessage());
log.error("设置操作映射失败: {}", e.getMessage());
result.put("success", false);
result.put("message", "设置失败: " + e.getMessage());
return ResponseEntity.badRequest().body(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.alibaba.fastjson.JSONObject;
import io.github.timemachinelab.sfchain.annotation.AIOp;
import io.github.timemachinelab.sfchain.core.logging.AICallLog;
import io.github.timemachinelab.sfchain.core.logging.AICallLogManager;
import io.github.timemachinelab.sfchain.core.openai.OpenAICompatibleModel;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -12,6 +14,8 @@
import javax.annotation.PostConstruct;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.UUID;

import static io.github.timemachinelab.sfchain.constants.AIOperationConstant.JSON_REPAIR_OP;

Expand Down Expand Up @@ -130,42 +134,93 @@ public OUTPUT execute(INPUT input) {
* @param modelName 指定的模型名称,为null时使用默认模型
* @return 输出结果
*/
// 在BaseAIOperation类中添加以下字段和方法

@Autowired
private AICallLogManager logManager;

// 在execute方法中添加详细日志记录
public OUTPUT execute(INPUT input, String modelName) {
String callId = UUID.randomUUID().toString();
LocalDateTime startTime = LocalDateTime.now();
long startMillis = System.currentTimeMillis();

AICallLog.AICallLogBuilder logBuilder = AICallLog.builder()
.callId(callId)
.operationType(annotation.value())
.callTime(startTime)
.input(input)
.modelName(modelName)
.frequency(1)
.lastAccessTime(startTime);

try {
// 获取模型
AIModel model = getModel(modelName);
logBuilder.modelName(model.getName());

// 构建提示词
String prompt = buildPrompt(input);
logBuilder.prompt(prompt);

// 获取操作配置
AIOperationRegistry.OperationConfig config = operationRegistry.getOperationConfig(annotation.value());

// 合并注解配置和运行时配置
// 合并配置
Integer finalMaxTokens = config.getMaxTokens() > 0 ? Integer.valueOf(config.getMaxTokens()) : (annotation.defaultMaxTokens() > 0 ? annotation.defaultMaxTokens() : null);
Double finalTemperature = config.getTemperature() >= 0 ? Double.valueOf(config.getTemperature()) : (annotation.defaultTemperature() >= 0 ? annotation.defaultTemperature() : null);
Boolean finalJsonOutput = config.isRequireJsonOutput() || annotation.requireJsonOutput();
boolean finalThinking = config.isSupportThinking() || annotation.supportThinking();

// 调用AI模型 - 根据模型类型选择合适的方法
// 记录请求参数
AICallLog.AIRequestParams requestParams = AICallLog.AIRequestParams.builder()
.maxTokens(finalMaxTokens)
.temperature(finalTemperature)
.jsonOutput(finalJsonOutput)
.thinking(finalThinking)
.build();
logBuilder.requestParams(requestParams);

// 调用AI模型
String response;
if (model instanceof OpenAICompatibleModel openAIModel) {

if (finalThinking) {
// 使用思考模式
response = openAIModel.generateWithThinking(prompt, finalMaxTokens, finalTemperature);
} else {
// 使用普通模式
response = openAIModel.generate(prompt, finalMaxTokens, finalTemperature, finalJsonOutput);
}
} else {
// 对于其他类型的模型,使用基础接口
response = model.generate(prompt);
}

logBuilder.rawResponse(response);

// 解析响应
return parseResponse(response, input);
OUTPUT result = parseResponse(response, input);

// 记录成功日志
long duration = System.currentTimeMillis() - startMillis;
AICallLog log = logBuilder
.status(AICallLog.CallStatus.SUCCESS)
.duration(duration)
.output(result)
.build();

logManager.addLog(log);

return result;

} catch (Exception e) {
// 记录失败日志
long duration = System.currentTimeMillis() - startMillis;
AICallLog callLog = logBuilder
.status(AICallLog.CallStatus.FAILED)
.duration(duration)
.errorMessage(e.getMessage())
.build();

logManager.addLog(callLog);

log.error("执行AI操作失败: {} - {}", annotation.value(), e.getMessage(), e);
throw new RuntimeException("AI操作执行失败: " + e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.github.timemachinelab.sfchain.core.logging;

import lombok.Builder;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.Map;

/**
* AI调用日志实体
*/
@Data
@Builder
public class AICallLog {

/** 调用ID */
private String callId;

/** 操作类型 */
private String operationType;

/** 模型名称 */
private String modelName;

/** 调用时间 */
private LocalDateTime callTime;

/** 执行耗时(毫秒) */
private long duration;

/** 调用状态 */
private CallStatus status;

/** 原始输入参数 */
private Object input;

/** 构建的提示词 */
private String prompt;

/** AI请求参数 */
private AIRequestParams requestParams;

/** 模型原始返回结果 */
private String rawResponse;

/** 最终输出结果 */
private Object output;

/** 错误信息(如果有) */
private String errorMessage;

/** 调用频次(用于LFU) */
private int frequency;

/** 最后访问时间(用于LFU) */
private LocalDateTime lastAccessTime;

public enum CallStatus {
SUCCESS, FAILED, TIMEOUT
}

@Data
@Builder
public static class AIRequestParams {
private Integer maxTokens;
private Double temperature;
private Boolean jsonOutput;
private Boolean thinking;
private Map<String, Object> additionalParams;
}
}
Loading
Loading