
在大模型爆发的今天,90% 的开发者还停留在 “调用 API 聊天” 的阶段。真正能进入金融、医疗、政企生产环境的 AI 系统,必须满足:可计算、可审计、可扩展、可管控。
本文将带你从头到尾,完整实现一套 AI 智能体 + Skill 双层架构 + Java/Python 混合计算 + 金融级审计 的生产可用系统。全文包含完整架构图、接口定义、核心代码、启动流程、对话流程、工具封装,可直接作为你的项目 / 毕设 / 面试作品。
1.1 传统大模型应用的致命缺陷
在金融场景里,单纯调用大模型接口有四大死穴:
计算不准:DCF、VaR、波动率等必须精确,大模型经常幻觉。
不可管控:技能不可插拔、不可覆盖、不可审计。
性能差:全量加载 Prompt,Token 爆炸、启动慢。
不合规:没有日志、没有评估、没有风险校验。
这些问题,直接导致大模型无法进入金融核心业务。
1.2 我们的解决方案:AI 智能体 + Skill 工具链
本项目的设计哲学非常清晰:
LLM = 大脑:只负责理解、推理、决策。
Skill = 专业能力包:领域知识、执行流程、指令规范。
Tool = 执行手脚:精确计算、脚本调用、接口访问。
Agent = 指挥官:管理 Skill、调度 Tool、会话、日志。
一句话:
LLM 负责想,Skill 负责专业,Tool 负责干,Agent 负责管。
1.3 项目核心亮点
双层 Skill 存储架构:内置只读 + 外部可写,安全合规。
Skill 三阶段加载:发现→激活→执行,省 Token、启动快。
Agent 可插拔:动态挂载技能,一键切换金融顾问。
Java+Python 混合计算:Java 稳、Python 计算强。
全链路审计:对话日志 + AI 自动质检,满足金融监管。
SSE 流式输出:像 ChatGPT 一样丝滑体验。
多模态支持:图片、音频、文本统一处理。
DDD 领域驱动架构:可维护、可扩展、可替换。
2.1 项目模块结构图(Maven 多模块)
sk-agent(父工程)
├─ sk-agent-domain 领域模型层(实体:Skill、AuditLog)
├─ sk-agent-framework 框架层(Agent、SkillPackage接口)
├─ sk-agent-infrastructure 基础设施层(Skill加载、Python执行、仓储)
├─ sk-agent-agents 智能体层(金融顾问Agent、子Agent)
└─ sk-agent-api 接口层(HTTP、流式、多模态、健康检查) 依赖方向:api → agents → infrastructure → framework → domain
2.2 运行流程图
启动流程
启动 SpringBoot
扫描 @Component、@Service
并行加载双层 Skill
创建 Agent 并注入 Skill
注册到 AgentRegistry
启动 Web 服务,等待请求
对话流程
HTTP 请求 → /api/chat/stream
匹配 Agent
准备上下文(会话 ID、Tool)
Agent 调用 LLM 思考
LLM 选择 Skill/Tool
执行 Python 计算
流式返回结果
记录审计日志
3.1 Skill 实体类
@Data
@Builder
public class Skill {
private String name; // 技能唯一标识
private String description; // 技能描述
private String instructions; // 执行指令
private String fullContent; // 完整SKILL.md内容
} 3.2 审计日志实体
@Data
@Builder
public class AuditLog {
private String sessionId; // 会话ID
private String skillName; // 调用技能
private String userMessage; // 用户问题
private String agentResponse; // AI回答
private String evaluation; // AI质检结果
private LocalDateTime timestamp;// 时间
private long durationMs; // 耗时
} 设计意义:纯业务、无框架、无技术侵入,是整个系统最稳定的部分。
4.1 SkillPackage 接口(技能包标准)
public interface SkillPackage {
String getName();
String getDescription();
String getContent();
boolean isAvailable();
Map<String, Object> getProperties();
} 4.2 SkillPackage 实现类
@Data
@Builder
public class SkillPackageImpl implements SkillPackage {
private String name;
private String description;
private String content;
private boolean available;
private Map<String, Object> properties;
} 4.3 Agent 核心接口
public interface Agent {
String getId();
String getName();
List<SkillPackage> getSkillPackages();
void addSkillPackage(SkillPackage skillPackage);
Flux<String> processStream(String message, Map<String, Object> context);
boolean isAvailable();
} 4.4 BaseAgent 模板类(最核心代码)
@Slf4j
@Getter
@Setter
public abstract class BaseAgent implements Agent {
protected String id;
protected String name;
protected String description;
protected List<SkillPackage> skillPackages = new ArrayList<>();
protected ChatClient chatClientWithSkills;
protected File skillsDirectory;
public BaseAgent(String name, String desc, ChatClient chatClient, ChatClient.Builder builder) {
this.id = UUID.randomUUID().toString();
this.name = name;
this.description = desc;
if (builder != null) {
this.chatClientWithSkills = builder.defaultSystem(desc).build();
} else {
this.chatClientWithSkills = chatClient;
}
}
public void setSkillsDirectory(File skillsDirectory) {
this.skillsDirectory = skillsDirectory;
try {
var skillsTool = SkillsTool.builder()
.addSkillsDirectory(skillsDirectory.getAbsolutePath())
.build();
this.chatClientWithSkills = chatClientBuilder
.defaultSystem(description)
.defaultToolCallbacks(skillsTool)
.build();
} catch (Exception e) {
log.error("初始化SkillsTool失败", e);
}
}
@Override
public Flux<String> processStream(String message, Map<String, Object> context) {
if (!isAvailable()) {
return Flux.just("当前智能体不可用");
}
String sessionId = (String) context.get("sessionId");
Object tools = context.get("tools");
var prompt = chatClientWithSkills.prompt().user(message);
if (tools != null) prompt.tools(tools);
if (sessionId != null) {
prompt.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId));
}
return prompt.stream().content();
}
} 5.1 Skill 解析器(SKILL.md → Java 对象)
@Slf4j
public class SkillParser {
private static final Pattern PATTERN = Pattern.compile(
"^---\\s*[\\r\\n]+name:\\s*(.+?)\\s*[\\r\\n]+description:\\s*(.+?)\\s*[\\r\\n]+---\\s*[\\r\\n]+(.*)$",
Pattern.DOTALL
);
public static Skill parse(String content) {
if (content == null || content.isBlank()) return null;
Matcher matcher = PATTERN.matcher(content);
if (matcher.find()) {
return Skill.builder()
.name(matcher.group(1).trim())
.description(matcher.group(2).trim())
.instructions(matcher.group(3).trim())
.fullContent(content)
.build();
}
log.warn("技能解析失败");
return null;
}
} 5.2 双层 Skill 加载器(核心)
@Service
@Slf4j
public class SkillLoaderService {
private final FileSystemSkillRepository repository;
@PostConstruct
public void loadAllSkills() {
int classpathCount = repository.loadFromPath("classpath:skills/**/SKILL.md");
File dynamicDir = new File("data/skills");
int fileCount = 0;
if (dynamicDir.exists()) {
fileCount = repository.loadFromPath("file:data/skills/**/SKILL.md");
}
log.info("技能加载完成:内置{}个,外部{}个", classpathCount, fileCount);
}
} 5.3 Python 脚本执行器(Java 调 Python)
@Component
@Slf4j
public class PythonScriptExecutor {
private final String pythonCommand;
public PythonScriptExecutor() {
this.pythonCommand = detectPython();
}
private String detectPython() {
if (testCommand("python3")) return "python3";
if (testCommand("python")) return "python";
log.warn("未检测到Python环境");
return null;
}
public ScriptResult execute(String skillPath, String scriptName, String... args) {
Path script = Paths.get("skills", skillPath, "scripts", scriptName);
List<String> command = new ArrayList<>();
command.add(pythonCommand);
command.add("-u");
command.add(script.toString());
Collections.addAll(command, args);
try {
Process process = new ProcessBuilder(command)
.redirectErrorStream(true)
.start();
String output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
int exitCode = process.waitFor();
return exitCode == 0 ? ScriptResult.success(output) : ScriptResult.error(output);
} catch (Exception e) {
log.error("执行Python失败", e);
return ScriptResult.error(e.getMessage());
}
}
} 5.4 审计日志服务(AI 自动质检)
@Service
@Slf4j
public class AuditLogService {
private final EvaluationRepository repository;
private final ChatClient.Builder chatClientBuilder;
private final SkillLoaderService skillLoaderService;
public void logAndEvaluate(AuditLog log) {
Skill critic = skillLoaderService.findByName("critic-agent").orElse(null);
if (critic != null) {
String eval = chatClientBuilder.build()
.prompt()
.system(critic.getFullContent())
.user(String.format("用户:%s\nAI:%s", log.getUserMessage(), log.getAgentResponse()))
.call()
.content();
log.setEvaluation(eval);
}
repository.save(log);
}
} @Component
@Slf4j
public class SuperFinancialAdvisorAgent extends BaseAgent {
public SuperFinancialAdvisorAgent(
ChatClient.Builder chatClientBuilder,
ResourceLoader resourceLoader) {
super(
"全能金融顾问大师",
"我提供投资分析、风控计算、组合优化、技术指标分析",
chatClientBuilder.build(),
chatClientBuilder
);
try {
Resource resource = resourceLoader.getResource("classpath:skills/super-financial-advisor");
setSkillsDirectory(resource.getFile());
} catch (Exception e) {
log.error("技能目录加载失败", e);
}
}
} @RestController
@RequestMapping("/api/chat")
@Slf4j
public class ChatController {
private final AgentRegistry agentRegistry;
private final FinancialTools financialTools;
private final AuditLogService auditLogService;
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream(
@RequestParam String agentName,
@RequestParam String message,
@RequestParam(defaultValue = "default-session") String sessionId) {
SseEmitter emitter = new SseEmitter(180000L);
Agent agent = agentRegistry.getAgentByName(agentName).orElse(null);
Map<String, Object> context = new HashMap<>();
context.put("sessionId", sessionId);
context.put("tools", financialTools);
agent.processStream(message, context).subscribe(
chunk -> {
try {
emitter.send(SseEmitter.event().name("content").data(Map.of("chunk", chunk)));
} catch (IOException e) { }
},
error -> emitter.completeWithError(error),
() -> {
emitter.send(SseEmitter.event().name("done"));
emitter.complete();
}
);
return emitter;
}
} @Component
@Slf4j
public class FinancialTools {
private final PythonScriptExecutor executor;
@Tool(description = "DCF现金流折现估值,参数:cashFlows、discountRate、terminalGrowth、sharesOutstanding")
public String calculateDcf(double[] cashFlows, double discountRate, double terminalGrowth, double sharesOutstanding) {
return executor.executeDcfCalculator(cashFlows, discountRate, terminalGrowth, sharesOutstanding).getOutput();
}
@Tool(description = "计算技术指标 MA、RSI、MACD")
public String calculateTechnicalIndicators(double[] prices) {
return executor.executeTechnicalIndicators(prices).getOutput();
}
@Tool(description = "计算投资组合VaR风险价值")
public String calculateVar(double portfolioValue, double meanReturn, double stdDev, double confidenceLevel) {
return executor.executeVarCalculator(portfolioValue, meanReturn, stdDev, confidenceLevel).getOutput();
}
@Tool(description = "马科维茨投资组合优化")
public String optimizePortfolio(String[] assets, double[] expectedReturns, double riskFreeRate) {
return executor.executePortfolioOptimizer(assets, expectedReturns, riskFreeRate).getOutput();
}
}
配置 application.yml:OpenAI/Azure/ 通义千问
放入 skills 目录:SKILL.md + scripts
启动 SkAgentApiApplication
访问接口:
http://localhost:8080/api/chat/stream?agentName=全能金融顾问大师&message=分析贵州茅台DCF估值 本文完整实现了一套可直接上生产的 AI 智能体金融项目。
从 DDD 架构→Skill 标准化→Agent 调度→Python 混合计算→审计日志→流式接口,全部打通。
这套架构解决了大模型落地的所有痛点:
可计算:精确金融计算,不幻觉
可管控:Skill 可插拔、可覆盖、可审计
可扩展:模块解耦,支持多 Agent、多 Skill
合规安全:双层技能、全链路日志、风险质检
体验流畅:SSE 流式输出、多模态支持