feat(ai): 业务上下文感知与 A2UI 链路统一

- 新增代码内业务上下文元数据层(实体字典 + 三大闭环工作流)
- 新增 get_business_context / get_entity_schema / get_pending_tasks 运行时工具
- SYSTEM_PROMPT 与技能目录改为先查业务流程/待办再执行
- A2UI 增加 ai_a2ui_submissions 幂等表、表单过期、ui.artifact 事件
- 提交回灌携带 submissionId / fieldErrors / 下一步建议
- 前端 uiArtifacts 归一化与过期表单禁用
This commit is contained in:
2026-08-06 15:23:23 +08:00
parent d9c541dacc
commit 24e0ecbdaf
44 changed files with 2125 additions and 41 deletions

View File

@@ -0,0 +1,90 @@
import { Injectable } from '@nestjs/common';
import type { AgentToolContext, ToolDef, ToolInputResult } from '../agent-tools/agent-tool.types';
import { rejectUnknownKeys, optionalString } from '../agent-tools/tools/tool-input';
import type { BusinessContextService } from './business-context.service';
interface GetBusinessContextInput {
workflowKey?: string;
}
/**
* 让 Agent 获取当前角色可见的业务流程、实体字典与依赖规则。
*/
@Injectable()
export class GetBusinessContextTool implements ToolDef<GetBusinessContextInput> {
readonly name = 'get_business_context';
readonly skillKey = 'assistant';
readonly requiredPermission = 'ai:chat:use';
readonly description =
'获取当前账号可见的业务流程(学生教学/住宿计费/教室租赁)、实体字典、阶段依赖与下一步建议。写入或导入前先调用本工具确认前置数据要求。';
readonly inputSchema = {
type: 'object',
properties: {
workflowKey: {
type: 'string',
description: '可选聚焦某个闭环student_teaching / dormitory_billing / classroom_rental',
maxLength: 50,
},
},
additionalProperties: false,
};
constructor(private readonly service: BusinessContextService) {}
validate(input: Record<string, unknown>): ToolInputResult<GetBusinessContextInput> {
const invalid = rejectUnknownKeys(input, ['workflowKey']);
if (invalid) return invalid;
const workflowKey = optionalString(input.workflowKey, 'workflowKey', 50);
if (!workflowKey.ok) return workflowKey;
return { ok: true, value: { workflowKey: workflowKey.value } };
}
async execute(input: GetBusinessContextInput, context: AgentToolContext): Promise<unknown> {
return this.service.getBusinessContext(
{ permissions: context.permissions, isSuperAdmin: context.isSuperAdmin },
input.workflowKey,
);
}
}
/**
* 让 Agent 获取指定业务实体的字段字典与关系,用于生成准确表单。
*/
@Injectable()
export class GetEntitySchemaTool implements ToolDef<{ entityKey: string }> {
readonly name = 'get_entity_schema';
readonly skillKey = 'assistant';
readonly requiredPermission = 'ai:chat:use';
readonly description =
'获取指定业务实体student/class/room/occupancy/expense/bill/deposit/classroom/organization/rental 等的字段字典、枚举来源与关系render_form 前可按需调用以生成正确字段。';
readonly inputSchema = {
type: 'object',
properties: {
entityKey: { type: 'string', minLength: 1, maxLength: 50 },
},
required: ['entityKey'],
additionalProperties: false,
};
constructor(private readonly service: BusinessContextService) {}
validate(input: Record<string, unknown>): ToolInputResult<{ entityKey: string }> {
const invalid = rejectUnknownKeys(input, ['entityKey']);
if (invalid) return invalid;
if (typeof input.entityKey !== 'string' || !input.entityKey.trim()) {
return { ok: false, error: 'entityKey 必须是字符串' };
}
const entityKey = input.entityKey.trim();
if (entityKey.length > 50) {
return { ok: false, error: 'entityKey 长度不能超过 50' };
}
return { ok: true, value: { entityKey } };
}
async execute(input: { entityKey: string }, context: AgentToolContext): Promise<unknown> {
return this.service.getEntitySchema(
{ permissions: context.permissions, isSuperAdmin: context.isSuperAdmin },
input.entityKey,
);
}
}