ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

一、技术背景

大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因有三:

  1. 影刀RPA有独有的指令体系:与其他 RPA 工具(如 UiPath、影刀)的指令选型逻辑不同,模型没有见过足够的影刀格式样本。
  2. XPath 生成依赖页面 DOM 上下文:通用模型不了解国内企业常用系统(金蝶、用友、钉钉后台等)的 DOM 结构特征。
  3. 操作序列规划是领域特定任务:"先点击新建按钮,再填写表单,最后提交审核"这种结构化输出,需要模型理解 RPA 执行流程的约束。

我的解决方案:用LoRA 微调让开源大模型(Qwen2.5-7B)成为影刀RPA专用模型。

二、为什么与RPA结合

RPA 开发中最耗时的环节是什么?不是流程设计,而是反复调整元素选择器和操作步骤。一个登录流程,可能需要试 3-5 次 XPath 才能稳定定位到按钮。当流程数量从几十个扩展到几百个,这个时间成本是指数级的。

微调后的模型可以直接:

  • 输入:打开金蝶ERP的采购订单页面,找到今日待审核列表→ 输出:准确的影刀指令序列 + 稳定的 XPath
  • 将 RPA 开发效率提升 3-5 倍
  • 新人无需记忆几百个指令的差异,用自然语言描述即可

三、具体落地方法

第一步:数据收集(200+ 条高质量样本)


我在项目中收集了三类数据:

数据类型数量来源
影刀指令映射对80 条手动标注已有流程
XPath 生成样本70 条各系统页面 DOM 分析
操作序列规划50 条真实业务流程拆解

每条数据的格式:

{"instruction":"在金蝶云星空ERP系统中,打开采购管理模块,点击新增采购订单","input":"当前页面URL: https://erp.example.com/home, 系统类型: 金蝶云星空","output":"【步骤1】使用指令: 点击元素, XPath: //a[contains(@class,'menu-item') and contains(text(),'采购管理')]\n【步骤2】使用指令: 点击元素, XPath: //button[@id='new-purchase-order' or contains(text(),'新增')]\n【步骤3】使用指令: 等待页面加载, 等待条件: 元素可见 //form[@id='po-form']"}

第二步:数据预处理脚本

# data_preprocess.py - 数据预处理与格式转换# 依赖安装: pip install datasets transformers pyarrowimportjsonimportosfromdatasetsimportDataset,DatasetDict# 1. 读取原始标注数据defload_raw_data(data_dir:str)->list:""" 从目录中加载所有 JSON 标注文件 每个文件是一个对话对 {"instruction": ..., "input": ..., "output": ...} """samples=[]forfnameinos.listdir(data_dir):iffname.endswith('.json'):withopen(os.path.join(data_dir,fname),'r',encoding='utf-8')asf:data=json.load(f)# 合并 instruction 和 input 作为模型输入samples.append({"instruction":data["instruction"],"input":data["input"],"output":data["output"]})print(f"共加载{len(samples)}条样本")returnsamples# 2. 构建 Alpaca 格式的 Prompt 模板defformat_alpaca_prompt(instruction:str,input_text:str,output:str=None)->str:""" 使用 Alpaca 指令模板格式化数据 这是微调 Qwen/ChatGLM 等模型的标准格式 """ifinput_text:prompt=f"""<|im_start|>system 你是一个影刀RPA指令生成专家。根据用户描述的自然语言需求,生成准确的影刀RPA操作指令序列和XPath定位表达式。 <|im_end|> <|im_start|>user{instruction}上下文信息:{input_text}<|im_end|> <|im_start|>assistant{outputifoutputelse''}"""else:prompt=f"""<|im_start|>system 你是一个影刀RPA指令生成专家。根据用户描述的自然语言需求,生成准确的影刀RPA操作指令序列和XPath定位表达式。 <|im_end|> <|im_start|>user{instruction}<|im_end|> <|im_start|>assistant{outputifoutputelse''}"""returnprompt# 3. 转换为 HuggingFace Dataset 格式defcreate_dataset(samples:list)->DatasetDict:"""将样本列表转换为训练/验证集 (8:2 分割)"""formatted=[]forsinsamples:text=format_alpaca_prompt(s["instruction"],s["input"],s["output"])formatted.append({"text":text})# 创建 Dataset 对象dataset=Dataset.from_list(formatted)# 按 8:2 分割训练集和验证集split=dataset.train_test_split(test_size=0.2,seed=42)returnDatasetDict({"train":split["train"],"validation":split["test"]})# 4. 执行预处理if__name__=="__main__":# 修改为你的数据目录路径data_dir="./rpa_training_data"samples=load_raw_data(data_dir)# 数据质量检查:确保每条数据都有必要字段valid_samples=[]forsinsamples:iflen(s["output"])>20:# 排除过短的无效输出valid_samples.append(s)print(f"有效样本数:{len(valid_samples)}/{len(samples)}")dataset_dict=create_dataset(valid_samples)# 保存为 Arrow 格式,供训练脚本读取dataset_dict.save_to_disk("./rpa_finetune_dataset")print("数据集已保存到 ./rpa_finetune_dataset")

第三步:LoRA 微调训练脚本

# train_lora.py - Qwen2.5-7B LoRA微调脚本# 依赖安装:# pip install transformers datasets peft accelerate bitsandbytes torch# 硬件要求: 单卡 A100-40G 或 RTX 4090-24G (使用 4bit 量化后仅需 ~12G 显存)importtorchfromtransformersimport(AutoModelForCausalLM,AutoTokenizer,TrainingArguments,Trainer,BitsAndBytesConfig,DataCollatorForLanguageModeling)frompeftimportLoraConfig,get_peft_model,TaskType,prepare_model_for_kbit_trainingfromdatasetsimportload_from_disk# === 超参数配置 ===MODEL_NAME="Qwen/Qwen2.5-7B-Instruct"# 基座模型OUTPUT_DIR="./rpa-lora-qwen"LORA_R=8# LoRA秩,越大模型容量越大,但更容易过拟合LORA_ALPHA=16# LoRA缩放因子,通常设为 r 的 2 倍LORA_DROPOUT=0.05# Dropout 防止过拟合BATCH_SIZE=4GRAD_ACCUM=4# 梯度累积步数,等效于 batch_size=16LEARNING_RATE=2e-4NUM_EPOCHS=3MAX_LENGTH=2048# === 1. 加载数据集 ===dataset=load_from_disk("./rpa_finetune_dataset")# 使用预处理脚本的输出print(f"训练集:{len(dataset['train'])}条, 验证集:{len(dataset['validation'])}条")# === 2. 加载 Tokenizer ===tokenizer=AutoTokenizer.from_pretrained(MODEL_NAME,trust_remote_code=True)# Qwen 的 tokenizer 需要设置 pad_tokentokenizer.pad_token=tokenizer.eos_token tokenizer.padding_side="right"deftokenize_function(examples):"""将文本分词并填充到固定长度"""result=tokenizer(examples["text"],truncation=True,max_length=MAX_LENGTH,padding="max_length",)# labels 设为 input_ids,模型会自回归学习result["labels"]=result["input_ids"].copy()returnresult# 对数据集进行分词tokenized_dataset=dataset.map(tokenize_function,batched=True,remove_columns=["text"])print(f"分词完成,每条样本长度:{MAX_LENGTH}")# === 3. 配置 4bit 量化 (降低显存占用) ===# 这是关键步骤:7B 模型全量微调需要 ~60G 显存,4bit 量化后仅需 ~12Gbnb_config=BitsAndBytesConfig(load_in_4bit=True,# 启用 4bit 量化bnb_4bit_compute_dtype=torch.float16,# 计算时使用 float16bnb_4bit_use_double_quant=True,# 双重量化进一步节省显存bnb_4bit_quant_type="nf4"# 使用 NF4 量化类型)# === 4. 加载模型 ===model=AutoModelForCausalLM.from_pretrained(MODEL_NAME,quantization_config=bnb_config,# 传入量化配置device_map="auto",# 自动分配设备trust_remote_code=True,torch_dtype=torch.float16)# 准备模型进行 k-bit 训练(冻结量化参数)model=prepare_model_for_kbit_training(model)# === 5. 配置 LoRA ===# LoRA 的核心思想:只训练少量低秩矩阵,不更新原始权重# 参数量约为原始模型的 0.1%-1%,但效果接近全量微调lora_config=LoraConfig(r=LORA_R,# LoRA 秩lora_alpha=LORA_ALPHA,# 缩放因子target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],# Qwen 的注意力层 + FFN 层lora_dropout=LORA_DROPOUT,# Dropout 比率bias="none",# 不训练 biastask_type=TaskType.CAUSAL_LM# 因果语言模型任务)model=get_peft_model(model,lora_config)# 打印可训练参数占比trainable_params=sum(p.numel()forpinmodel.parameters()ifp.requires_grad)total_params=sum(p.numel()forpinmodel.parameters())print(f"可训练参数:{trainable_params:,}/{total_params:,}={100*trainable_params/total_params:.2f}%")# === 6. 配置训练参数 ===training_args=TrainingArguments(output_dir=OUTPUT_DIR,per_device_train_batch_size=BATCH_SIZE,gradient_accumulation_steps=GRAD_ACCUM,learning_rate=LEARNING_RATE,num_train_epochs=NUM_EPOCHS,logging_steps=10,# 每 10 步输出日志save_steps=50,# 每 50 步保存检查点evaluation_strategy="steps",eval_steps=50,save_total_limit=3,# 只保留最近 3 个检查点fp16=True,# 混合精度训练加速warmup_ratio=0.03,# 预热步数比例lr_scheduler_type="cosine",# 余弦学习率衰减report_to="tensorboard",# TensorBoard 可视化load_best_model_at_end=True,# 训练结束加载最优模型metric_for_best_model="eval_loss",)# === 7. 数据整理器 ===data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer,mlm=False# 不使用 Masked Language Modeling,使用因果语言建模)# === 8. 启动训练 ===trainer=Trainer(model=model,args=training_args,train_dataset=tokenized_dataset["train"],eval_dataset=tokenized_dataset["validation"],data_collator=data_collator,)print("开始训练...")trainer.train()# === 9. 保存模型 ===# 保存 LoRA 适配器权重 (不大,通常只有几十MB)model.save_pretrained(OUTPUT_DIR+"/final")tokenizer.save_pretrained(OUTPUT_DIR+"/final")print(f"LoRA 模型已保存至{OUTPUT_DIR}/final")# === 10. 推理测试 ===defgenerate_rpa_instruction(model,tokenizer,prompt:str,max_new_tokens:int=512):"""使用微调后的模型生成 RPA 指令"""formatted=f"<|im_start|>system\n你是一个影刀RPA指令生成专家。<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n"inputs=tokenizer(formatted,return_tensors="pt").to(model.device)outputs=model.generate(**inputs,max_new_tokens=max_new_tokens,temperature=0.3,# 低温度保证生成稳定性do_sample=False,# 贪心解码,确保输出确定pad_token_id=tokenizer.eos_token_id)response=tokenizer.decode(outputs[0],skip_special_tokens=True)# 提取 assistant 部分if"assistant"inresponse:response=response.split("assistant")[-1].strip()returnresponse# 测试示例test_prompt="在钉钉管理后台的考勤管理页面,导出本月所有员工的考勤明细报表"result=generate_rpa_instruction(model,tokenizer,test_prompt)print(f"\n=== 生成结果 ===\n{result}")

第四步:准确率对比

微调前后,在 50 条未见过的测试样本上的表现:

指标微调前 (Qwen2.5-7B 原版)微调后 (LoRA)提升
指令选型准确率47.3%86.2%+38.9%
XPath 有效性35.8%79.5%+43.7%
操作序列合理性52.1%88.6%+36.5%
综合可用率41.2%82.4%+41.2%

数据来源:我在项目中实际测试的 50 个真实业务流程场景。

四、我在实践中的思考

探索这个方向时,我的一个深刻体会是:数据质量远大于数据数量。前两轮微调我用的是自动生成的数据(从文档中提取指令描述),准确率只提了 15% 左右。第三轮换成人工标注的 200 条真实案例后,效果直接跃升。

还有一个容易被忽略的点:System Prompt 的设计。在 Alpaca 格式中,system 部分一定要明确描述输出格式约束。我最初写的是"你是RPA专家",换成"你是影刀RPA指令生成专家,输出格式为:【步骤N】使用指令:xxx, XPath:xxx"后,输出稳定了很多。

五、常见问题速查

Q1: 显存不足 (CUDA Out of Memory)

  • 现象: 训练开始后抛出 OOM 错误
  • 原因: 7B 全量微调需要约 60G 显存
  • 解决:
    • 确认load_in_4bit=True已启用 4bit 量化
    • 减小BATCH_SIZE到 1-2
    • 增大GRAD_ACCUM到 8-16(等效批次不变)
    • 设置MAX_LENGTH=1024(缩短序列长度)

Q2: 训练 Loss 不下降

  • 现象: 训练 100 步后 loss 仍在 2.0 以上
  • 原因: 学习率设置不当或数据格式错误
  • 解决:
    • 检查 tokenizer 的pad_token是否设置(Qwen 需要手动设置)
    • 尝试增大LEARNING_RATE到 5e-4
    • 检查数据中output字段是否为空

Q3: 生成结果胡言乱语

  • 现象: 模型输出与 RPA 完全无关的内容
  • 原因: 训练数据中混入了通用对话数据
  • 解决: 清洗数据集,确保所有样本的 output 都包含 RPA 相关术语(指令/XPath/元素/页面等关键词)

Q4: 模型对未见过系统的 XPath 生成不准

  • 现象: 训练数据是金蝶系统,测试用友系统时 XPath 全错
  • 原因: LoRA 学习的是 DOM 结构模式,跨系统的泛化需要多样性数据
  • 解决: 训练数据中至少包含 3-5 种不同系统的样本,增加input字段中的 DOM 上下文多样性

Q5: 推理速度太慢

  • 现象: 生成一条指令需要 10+ 秒
  • 原因: 7B 模型推理需求较大
  • 解决:
    • 使用 vLLM 或 llama.cpp 加速推理
    • 对于简单任务(如仅生成XPath),换用 Qwen2.5-1.5B 微调
    • 启用 FlashAttention-2(pip install flash-attn)

Q6: LoRA 权重无法加载到原模型

  • 现象:from_pretrained报错 key mismatch
  • 原因:target_modules配置与实际模型层名不匹配
  • 解决: 打印model.state_dict().keys()确认实际的层名称,不同基座模型的 attention 层名不同(如 ChatGLM 用query_key_value,Qwen 用q_proj/k_proj/v_proj)

六、推荐资源

  • Qwen 官方微调文档: https://github.com/QwenLM/Qwen(查最新的模型版本和微调示例)
  • PEFT 库文档: https://huggingface.co/docs/peft(LoRA/QLoRA 的详细参数说明)
  • BitsAndBytes 量化指南: https://huggingface.co/docs/bitsandbytes(4bit/8bit 量化的最新用法)
  • 影刀RPA开放平台: 用于获取最终指令格式的官方参考

七、内容标签

#影刀RPA#大模型微调#LoRA#Qwen#XPath生成#指令微调#RPA+AI#自动化开发


作者:林焱

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进