66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
# MIT License
|
||
# Copyright (c) 2024
|
||
"""LLM 抽象与 Dummy 实现。"""
|
||
|
||
from abc import ABC, abstractmethod
|
||
from typing import Any, Dict, List
|
||
|
||
import yaml
|
||
|
||
from .schema import DSLSpec, EventRecord
|
||
|
||
PROMPT_TEMPLATE = """你是一名自动化工程师,请将以下事件序列归纳为可参数化的自动化 DSL。
|
||
事件序列使用 JSON 描述,每个事件包含 kind、control(AutomationId/Name/ClassName/ControlType/BoundingRect)等。
|
||
输出 YAML,字段包括:params、steps、assertions、retry_policy、waits,支持 steps 内的 if/else、for_each。
|
||
输出示例:
|
||
params:
|
||
text: "示例参数"
|
||
steps:
|
||
- action: click
|
||
target: {{AutomationId: "15", ControlType: "Edit"}}
|
||
- action: type
|
||
target: {{AutomationId: "15"}}
|
||
text: "{{text}}"
|
||
assertions:
|
||
- "输入框非空"
|
||
retry_policy: {{max_attempts: 2, interval: 1.0}}
|
||
waits: {{appear: 5.0, disappear: 5.0}}
|
||
现在请基于输入事件生成 YAML:"""
|
||
|
||
|
||
class LLMClient(ABC):
|
||
"""LLM 抽象接口。"""
|
||
|
||
@abstractmethod
|
||
def generate(self, events: List[EventRecord]) -> DSLSpec:
|
||
"""将事件序列转为 DSL 规格。"""
|
||
|
||
|
||
class DummyLLM(LLMClient):
|
||
"""离线 dummy,实现一个简单的规则映射。"""
|
||
|
||
def generate(self, events: List[EventRecord]) -> DSLSpec:
|
||
steps: List[Dict[str, Any]] = []
|
||
for ev in events:
|
||
ctrl = ev.control.dict(by_alias=True) if ev.control else {}
|
||
if ev.kind == "mouse_click":
|
||
steps.append({"action": "click", "target": ctrl})
|
||
elif ev.kind == "key_down" and ev.data.get("name"):
|
||
# 仅在按键时记录输入
|
||
steps.append({"action": "type", "target": ctrl, "text": ev.data.get("name")})
|
||
if not steps:
|
||
steps.append({"action": "assert_exists", "target": {"Name": "dummy"}})
|
||
spec = DSLSpec(
|
||
params={},
|
||
steps=steps,
|
||
assertions=["dummy generated"],
|
||
)
|
||
return spec
|
||
|
||
|
||
def render_prompt(events: List[EventRecord]) -> str:
|
||
"""把事件序列渲染到 prompt。"""
|
||
event_dicts = [ev.dict(by_alias=True) for ev in events]
|
||
return f"{PROMPT_TEMPLATE}\n\n{yaml.safe_dump(event_dicts, allow_unicode=True)}"
|
||
|