会话级动态表单渲染与实时校验联动
在复杂 Agent 交互中,纯文本对话往往无法胜任高精度、多字段的业务参数录入。比如订机票、填写运维工单或配置数据看板时,如果大模型一段一段地追问用户“您的出发时间是哪天?”、“您需要几张票?”,不仅沟通冗长低效,而且用户很难随时纠正之前的输入。
现代生成式交互的一个核心突破,就是会话级动态表单(Conversational Dynamic Forms):大模型根据当前对话上下文与意图,动态吐出一份符合 JSON Schema 规范的交互式表单组件,用户在对话流内直接修改控件,表单值与大模型进行双向实时校验联动。
动态表单 JSON Schema 契约设计
大模型输出的表单描述协议必须轻量、严谨且易于前端解析。我们定义如下的标准 FormSchema:
export type FieldType = 'text' | 'number' | 'select' | 'date-picker' | 'switch' | 'slider'; export interface FormFieldSchema { key: string; label: string; type: FieldType; required?: boolean; defaultValue?: any; placeholder?: string; options?: Array<{ label: string; value: any }>; validation?: { min?: number; max?: number; pattern?: string; errorMessage?: string; }; dependencies?: { field: string; condition: 'equals' | 'greaterThan'; value: any; }; } export interface DynamicFormSchema { formId: string; title: string; description?: string; submitButtonText?: string; fields: FormFieldSchema[]; }响应式表单渲染引擎与状态机
在前端组件中,我们需要基于 Schema 动态递归构建表单控件,并实现字段联动与异步校验:
import React, { useState, useCallback } from 'react'; interface Props { schema: DynamicFormSchema; onSubmit: (values: Record<string, any>) => void; onFieldChange?: (key: string, value: any, currentValues: Record<string, any>) => void; } export const ConversationalForm: React.FC<Props> = ({ schema, onSubmit, onFieldChange }) => { const [formData, setFormData] = useState<Record<string, any>>(() => { const initial: Record<string, any> = {}; for (const field of schema.fields) { initial[field.key] = field.defaultValue ?? ''; } return initial; }); const [errors, setErrors] = useState<Record<string, string>>({}); const validateField = (field: FormFieldSchema, value: any): string | null => { if (field.required && (value === '' || value === null || value === undefined)) { return `${field.label} 是必填项`; } if (field.validation?.pattern && typeof value === 'string') { const reg = new RegExp(field.validation.pattern); if (!reg.test(value)) { return field.validation.errorMessage || `${field.label} 格式不正确`; } } return null; }; const handleInputChange = (key: string, value: any) => { const updatedValues = { ...formData, [key]: value }; setFormData(updatedValues); const targetField = schema.fields.find(f => f.key === key); if (targetField) { const err = validateField(targetField, value); setErrors(prev => ({ ...prev, [key]: err || '' })); } onFieldChange?.(key, value, updatedValues); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const newErrors: Record<string, string> = {}; let hasError = false; for (const field of schema.fields) { const err = validateField(field, formData[field.key]); if (err) { newErrors[field.key] = err; hasError = true; } } setErrors(newErrors); if (!hasError) { onSubmit(formData); } }; return ( <form onSubmit={handleSubmit} className="conversational-form-card"> <div className="form-header"> <h4>{schema.title}</h4> {schema.description && <p className="form-desc">{schema.description}</p>} </div> <div className="form-body"> {schema.fields.map(field => { // 处理联动依赖显隐 if (field.dependencies) { const parentVal = formData[field.dependencies.field]; if (field.dependencies.condition === 'equals' && parentVal !== field.dependencies.value) { return null; } } return ( <div key={field.key} className="form-item"> <label className="form-label"> {field.required && <span className="required-mark">* </span>} {field.label} </label> {field.type === 'text' && ( <input type="text" value={formData[field.key]} placeholder={field.placeholder} onChange={e => handleInputChange(field.key, e.target.value)} className={`form-input ${errors[field.key] ? 'input-error' : ''}`} /> )} {field.type === 'select' && ( <select value={formData[field.key]} onChange={e => handleInputChange(field.key, e.target.value)} className="form-select" > <option value="">请选择</option> {field.options?.map(opt => ( <option key={opt.value} value={opt.value}> {opt.label} </option> ))} </select> )} {errors[field.key] && <span className="error-tip">{errors[field.key]}</span>} </div> ); })} </div> <button type="submit" className="submit-action-btn"> {schema.submitButtonText || '确认提交'} </button> </form> ); };与大模型意图的主动回流与回显
当用户在会话流中点击“确认提交”后,表单的状态被冻结为只读卡片(保留修改记录),同时将表单数据封装为结构化 Message 发送给后端 Agent:
- Agent 收到后无需再从自然语言中反向提取实体,直接以结构化 JSON 调用数据库或下游 API;
- 如果某个参数不合规,Agent 可以在下一轮对话中返回一个微调后的增量 Diff Schema,原表单高亮红色字段,让用户直接就地修正。
从纯文本输入到结构化动态表单,是生成式人机协同迈向工业级生产力的关键一步。