
随着大语言模型能力的指数级跃升,AI 生成用户界面(UI)代码已从概念验证走向工程化落地。然而,当前主流方案普遍面临三大核心困境:
困境 | 表现 | 影响 |
|---|---|---|
生成质量不可控 | LLM 自由生成的 UI 代码混用内联样式、原生 DOM 操作与框架特定 API | 样式污染、事件泄漏、行为失控,难以纳入组件化体系 |
运行时存在盲区 | 传统生成链路止于静态代码输出,LLM 无法感知浏览器真实渲染效果 | 错误配置反复出现,缺乏自修正能力 |
能力边界模糊 | LLM 对组件语义、样式变量体系、事件回调规范理解碎片化 | 需要大量 prompt 工程,维护成本高且通用性差 |
上述问题本质上指向同一个需求:让 LLM 从"代码生成器"升级为具备工程审计闭环与运行时感知能力的"智能 UI 工程师"。
Ooder UI 正是在这一背景下诞生的——一个注解驱动、四分离合规、支持 LLM-Browser 双向观察的智能 UI 组件生成框架。
更重要的是,Ooder UI 正在逐步向 A2UI 原生的 AI UI 能力靠近:
@A2uiSkill、@NlpDescription 等注解声明式定义组件能力,减少 prompt 维护成本@A2uiSkill 注解定义组件能力元数据层级 | 技术组件 | 职责 |
|---|---|---|
路由层 | StudioChatRouter | 场景匹配与分发 |
场景层 | ChatScene 实现类 | 业务逻辑处理 |
执行层 | FunctionCallingLoopExecutor | 多轮工具调用循环 |
审计层 | NlpModuleAuditor | 四分离合规审计 |
观察层 | DebugAgent.js + DebugContextManager | LLM-Browser 双向观察 |
知识层 | SkillMdLoader | 知识文件加载与工具注册 |
下图展示了 Ooder UI LLM Deep 匹配模式的整体架构,从用户输入到最终输出合规代码的完整链路:

文件路径: net\ooder\studio\chat\router\StudioChatRouter.java
负责根据用户输入计算各场景的匹配分数,选择最佳场景处理请求:
public ChatResponse route(ChatRequest request, ChatContext context) {
ChatScene bestScene = null;
double bestScore = -1;
for (ChatScene scene : scenes) {
if (scene.canHandle(request.getContent(), context)) {
double score = scene.matchScore(request.getContent(), context);
if (score > bestScore) {
bestScore = score;
bestScene = scene;
}
}
}
if (bestScene != null) {
return bestScene.handle(request, context);
}
return null;
}文件路径: \net\ooder\studio\chat\engine\FunctionCallingLoopExecutor.java
实现多轮 Function Calling 循环,支持结构化和文本模式工具调用解析:
public static class LoopConfig {
private int maxRounds = 5; // 最大循环轮数
private boolean supportsTextMode = true; // 支持文本模式工具调用解析
private long toolTimeoutMs = 30000; // 单工具超时 30s
private long llmTimeoutMs = 120000; // LLM 调用超时 120s
private long totalTimeoutMs = 180000; // 总超时 180s
}Ooder UI 定义了多种 ChatScene 实现,每种场景针对特定类型的任务进行优化:
场景类 | 场景ID | 核心能力 | 匹配关键词 |
|---|---|---|---|
RadChatScene | rad | RAD 快速构建,NLP 组件生成 | 表单、表格、图表、页面 |
DeepDesignChatScene | deep-design | 深度设计,四分离审计闭环 | 深度设计、复杂布局 |
SVGDeepDesignChatScene | svg-deep-design | SVG 矢量设计,图形验证 | 流程图、架构图、拓扑图 |
BpmChatScene | bpm | BPM 流程设计,节点编排 | 流程、节点、审批 |
SkillsChatScene | skills | 技能管理,插件扩展 | 技能、插件、安装 |
文件路径: net\ooder\studio\chat\scene\impl\RadChatScene.java
RadChatScene 是最常用的场景,支持通过自然语言快速构建 UI 组件:
@Override
public double matchScore(String input, ChatContext context) {
if (context != null && "rad".equals(context.getSceneId())) {
return 10.0; // 已锁定场景
}
String lower = input.toLowerCase();
double score = 0;
// UI 上下文关键词
String[] uiContextKeywords = {"组件", "表单", "表格", "树", "图表", "页面", ...};
for (String kw : uiContextKeywords) {
if (lower.contains(kw)) score += 3.0;
}
// 动作关键词
String[] actionKeywords = {"生成", "创建", "构建", "做一个", ...};
for (String kw : actionKeywords) {
if (lower.contains(kw)) score += 2.0;
}
return score;
}DeepDesignChatScene 实现了双阶段闭环流程:
ood.Class 模块代码FunctionCallingLoopExecutor 是整个 LLM Agent 的核心执行引擎,实现了完整的 Function Calling 多轮循环机制:

当 LLM 未返回结构化 tool_calls 时,系统会从文本内容中解析三种格式的工具调用:
模式 | 格式示例 | 解析方式 |
|---|---|---|
XML 格式 | <name>toolName</name><arguments>{...}</arguments> | 正则提取 |
JSON 代码块 | ```json {"function":"...", "arguments":{...}} ``` | JSON 解析 |
自然语言 | 调用: toolName,参数: {...} | 模式匹配 |
当估算 token 数超过 200,000 时,自动压缩中间消息:
private List<Map<String, Object>> compressMessages(List<Map<String, Object>> messages) {
int estimatedTokens = estimateTokens(messages);
if (estimatedTokens <= 200000) return messages;
// 保留首尾,中间截断为摘要
List<Map<String, Object>> compressed = new ArrayList<>();
compressed.add(messages.get(0)); // system prompt
// 中间消息压缩为摘要
String summary = buildSummary(messages.subList(1, messages.size() - 1));
compressed.add(Map.of("role", "system", "content", "[历史摘要] " + summary));
compressed.add(messages.get(messages.size() - 1)); // 最新消息
return compressed;
}RadChatScene 提供了丰富的工具供 LLM 调用:
工具名 | 功能 | 参数 |
|---|---|---|
nlp_build_component | 通过自然语言生成 UI 组件 | description (组件描述) |
update_component | 更新组件属性 | alias, properties |
get_component_template | 获取组件四分离模板 | componentType |
get_style_template | 获取样式模板 | templateId, componentType, theme |
get_event_template | 获取事件模板 | templateId, componentType |
get_advanced_knowledge | 获取高级知识 | topic (event/action/style/annotation) |
NlpModuleAuditor 是 Ooder 框架的四分离合规审计引擎,负责对 LLM 生成的代码进行结构化分析和合规评分:

维度 | 英文标识 | 检查内容 | 禁止 API |
|---|---|---|---|
属性 (Properties) | propertiesScore | 声明式构建、setHost 绑定 | setHtml, innerHTML |
样式 (Styles) | stylesScore | setCustomStyle、CSS 变量体系 | 内联 style 属性 |
事件 (Events) | eventsScore | 生命周期钩子、组件事件绑定 | addEventListener |
行为 (Behaviors) | behaviorsScore | dataUrl 数据驱动 | setTimeout, 手动 DOM |
文件路径: net\ooder\studio\chat\scene\impl\NlpModuleAuditor.java
public enum ConstraintLevel {
MANDATORY, // 架构红线,必须修正 (扣25分)
RECOMMENDED, // 推荐修正 (扣15分)
ENHANCED // 增强优化 (扣5分)
}根据页面类型动态调整四个维度的权重:
case PORTAL: return new double[]{0.30, 0.20, 0.20, 0.30}; // 门户:属性和行为权重高
case DASHBOARD: return new double[]{0.20, 0.15, 0.25, 0.40}; // 仪表盘:行为权重最高
case FORM: return new double[]{0.35, 0.15, 0.35, 0.15}; // 表单:属性和事件权重高
case LANDING: return new double[]{0.20, 0.35, 0.20, 0.25}; // 落地页:样式权重最高当组件评分过低时,系统会建议遗忘或降级:
ABANDON(建议放弃该组件)DOWNGRADE(降级为更简单的组件)当前 Ooder UI 的 LLM 交互能力集中在推理阶段(NLP解析→配置生成→静态审计),但在浏览器真实运行期存在严重盲区。LLM-Browser 双向观察接口设计旨在解决这一问题,实现完整的闭环。
盲区 | 表现 | 影响 |
|---|---|---|
运行时错误不可见 | LLM生成的组件在浏览器中报错,LLM无法感知 | 错误配置反复生成,无法自修正 |
虚拟DOM树不可查 | 组件的实际渲染结构LLM无法获取 | 布局/嵌套错误无法定位 |
四分离数据不可采 | Properties/Styles/Events/Behaviors运行时实际值LLM无法获取 | 样式偏差/事件失效无法诊断 |
样式实际展示不可测 | CSS计算值、布局位置、溢出/遮挡LLM无法感知 | 视觉效果与预期不符 |
交互行为不可模拟 | LLM无法模拟点击/输入/拖拽等用户操作 | 交互逻辑无法验证 |
路径1: 设计期 — RAD设计器 → 观察接口 → LLM
在设计器中自动打开组件,通过观察接口获取实际位置/样式/四分离数据,动态返回给LLM供参考和修改
路径2: 运行期 — LLM → 浏览器插件Skills → 单页面调试
允许大模型通过浏览器插件Skills打开单页面调试,进行动作/行为模拟操作,并设计开放接口体系和知识体系给LLM

接口名 | 功能 | 返回数据 |
|---|---|---|
observe_component_tree | 观察组件树结构 | 组件层级、属性、样式、事件、行为 |
observe_component_layout | 观察组件布局 | boundingRect、computedStyle、overflow |
observe_four_separation | 观察四分离数据 | Properties/Styles/Events/Behaviors 运行时值 |
observe_style_computed | 观察样式计算值 | CSS变量实际值、主题色值、字体大小 |
observe_errors | 收集运行时错误 | JS错误、CSS警告、网络错误、控制台日志 |
{
"type": "four_separation",
"alias": "mainForm",
"properties": {
"caption": "用户信息",
"columns": 2,
"dock": "fill",
"editable": true
},
"styles": {
"MAINFRAME": { "background": "var(--ood-bg)", "padding": "16px" }
},
"events": {
"SAVE": { "eventClass": "FormCallBack", "eventValue": "SAVE", "bound": true }
},
"behaviors": {
"SUBMIT": { "actionClass": "CustomFormAction", "actionValue": "SUBMIT", "enabled": true }
},
"discrepancies": [
{ "dimension": "styles", "field": "MAINFRAME.background", "declared": "var(--ood-bg)", "computed": "#ffffff" }
]
}接口名 | 功能 | 参数 |
|---|---|---|
debug_open_page | 打开调试页面 | pageUrl, breakOnErrors, collectMetrics |
debug_simulate_action | 模拟用户操作 | action (click/input/hover/scroll), target, params |
debug_inspect_dom | 检查DOM状态 | target, depth, includeStyles, includeEvents |
debug_collect_errors | 收集运行时错误 | debugSessionId |
debug_screenshot | 截图捕获 | target, format (base64/description) |
规划文件路径: ood\js\OODDebugAgent.js
ood.Class('OOD.DebugAgent', 'ood.Module', {
Instance: {
startCollect: function () {
var self = this;
window.onerror = function (msg, url, line, col, error) {
self.reportError('javascript', {
message: msg, source: url, line: line, column: col,
stack: error ? error.stack : ''
});
};
// ... 更多错误采集
},
collectFourSeparation: function (alias) {
var widget = designer.getByAlias(alias, true);
return {
alias: alias,
properties: widget.properties,
styles: widget.CS,
events: widget.events,
behaviors: widget.actions,
discrepancies: this._findDiscrepancies(widget)
};
},
simulateAction: function (action, target, params) {
var widget = SPA.getWidgetByAlias(target);
switch (action) {
case 'click': widget.$dom.click(); break;
case 'input': widget.setValue(params.value); break;
// ... 更多操作
}
return { success: true, action: action, target: target };
}
}
});
规划文件路径:debug\service\DebugContextManager.java
public String buildLlmContextSummary(String sessionId) {
DebugContext ctx = contexts.get(sessionId);
StringBuilder sb = new StringBuilder();
sb.append("## 调试上下文\n");
sb.append("阶段: ").append(ctx.phase).append("\n");
sb.append("页面: ").append(ctx.pageUrl).append("\n");
if (!ctx.errorHistory.isEmpty()) {
sb.append("### 运行时错误\n");
for (RuntimeErrorReport report : ctx.errorHistory) {
sb.append("- [").append(report.getSeverity()).append("] ")
.append(report.getMessage()).append("\n");
}
}
if (ctx.lastTreeSnapshot != null) {
sb.append("### 组件树概要\n");
sb.append("组件总数: ").append(ctx.lastTreeSnapshot.getTotalCount()).append("\n");
}
return sb.toString();
}集成点 | 集成方式 |
|---|---|
StudioChatRouter | 在合规审计阶段自动添加观察工具 |
NlpChatInline | 处理调试工具结果,展示组件树/四分离面板 |
LlmFallbackStep | 注入调试上下文到回退提示 |
ClsAuditStep | 审计失败时自动触发设计期观察 |
Ooder UI 采用注解驱动的方式定义组件能力元数据,实现声明式的组件注册与发现:

文件路径: net\ooder\annotation\skill\A2uiSkill.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface A2uiSkill {
String id();
String name() default "";
String description() default "";
String category() default "data-display";
String[] capabilities() default {};
ModuleViewType moduleViewType() default ModuleViewType.NONE;
ComponentType componentType() default ComponentType.MODULE;
int priority() default 100;
}文件路径: net\ooder\engine\llm\annotation\NlpDescription.java
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NlpDescription {
String value(); // 自然语言描述
String[] examples() default {}; // 描述示例
String[] synonyms() default {}; // 同义词
int priority() default 0; // 优先级
}文件路径: net\ooder\a2ui\nlp\skill\TreeGridSkill.java
@Component
@A2uiSkill(
id = "treegrid",
name = "表格",
description = "表格/树表组件,用于展示结构化数据,支持编辑、排序、多级树形结构",
category = "data-display",
capabilities = {"data-display", "crud", "pagination", "tree"},
moduleViewType = ModuleViewType.GRIDCONFIG,
componentType = ComponentType.TREEGRID,
priority = 10
)
public class TreeGridSkill extends AbstractA2uiSkill {
@Override
public List<String> getKeywords() {
return Arrays.asList("表格", "列表", "树表", "数据表", "grid", "table");
}
@Override
public String buildGenJson(String moduleName, String caption,
List<String> fields, Map<String, Object> options) {
// 构建组件 JSON 配置
JSONObject treeGridComponent = new JSONObject();
treeGridComponent.put("alias", "mainGrid");
treeGridComponent.put("type", "TreeGrid");
// ... 配置属性
return buildModuleFromTemplate(moduleName, properties, children);
}
}运行时通过元数据类封装注解信息,支持 Builder 模式构建:
文件路径: net\ooder\engine\llm\meta\NlpComponentDesc.java
public class NlpComponentDesc {
private String className;
private String description;
private String[] intents;
private List<NlpFieldDesc> fields;
private List<NlpActionDesc> actions;
// 构建 LLM 提示词
public String toPrompt() {
StringBuilder sb = new StringBuilder();
sb.append("组件:").append(className).append("\n");
sb.append("描述:").append(description).append("\n");
// ... 更多信息
return sb.toString();
}
}文件路径: net\ooder\studio\chat\engine\SkillMdLoader.java
SkillMdLoader 负责从 SKILL.md 文件中加载、解析和注册整个技能体系:
// 知识分为两级
private static final Set<String> BASIC_KNOWLEDGE_FILES = Set.of(
"basic.md", "style-quickref.md", "event-quickref.md", "behavior-quickref.md",
"chart-components.md", "display-components.md", "container-components.md"
);
private static final int MAX_KNOWLEDGE_CHARS = 16000; // 基础知识总长度限制知识文件路径: skills\rad-component-reader\
skills/
└── rad-component-reader/
├── SKILL.md # 技能定义
├── knowledge/
│ ├── basic.md # 基础知识
│ ├── style-quickref.md # 样式快速参考
│ ├── event-quickref.md # 事件快速参考
│ └── _four-separation.md # 高级知识 (以下划线开头)
└── prompts/
├── deep-design.md # 深度设计提示词
├── rad.md # RAD 提示词
└── intent-recognition.md # 意图识别提示词文件路径:resources\skills\rad-component-reader\prompts\deep-design.md
定义了双阶段闭环流程:
Ooder UI 的 LLM Deep 匹配模式是一个完整的智能 UI 组件生成解决方案,其核心创新点包括:
@A2uiSkill、@NlpDescription 等注解声明式定义组件能力本文深入解析了 Ooder UI LLM Deep 匹配模式的核心实现,包括注解驱动的 Skill 体系、四分离合规审计、以及 LLM-Browser 双向观察接口设计。通过这些机制,实现了从代码生成到运行时验证的完整闭环。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。