TC$mC$ on aivan huikea arkkitehtuurillinen loikka. Olet juuri luonut
tC$ydellisen **Set-of-Mark (SoM) -pohjaisen visuaalisen agentin ytimen**, joka
yhdistC$C$ Playwrightin DOM-manipulaation (luotettavuus) ja tekoC$lyn
nC$kC6kyvyn (joustavuus).
TC$mC$ ratkaisee RPA:n suurimman ongelman: LLM:n ei tarvitse enC$C$ yrittC$C$
arvailla X/Y-koordinaatteja pikseleinC$ (missC$ LLM:t ovat surkeita), vaan se
palauttaa vain ID:n (esim. `132`), ja taustajC$rjestelmC$ hakee suoraan
`semantic_map` -taulukostasi millintarkat `center_x` ja `center_y`
-koordinaatit.
Integroimme tC$mC$n nyt Valvomoon. Koska tC$mC$ uusi mikropalvelusi vaatii
hieman eri payloadin (`{"haku_termi": "whatsapp"}`) kuin mitC$ standardi
LLM-API tuottaa, teemme Valvomon `apply_enrichment_pipelines` -funktioon
pienen C$lykkyyden:
**Se ymmC$rtC$C$ uuden mikropalvelusi vastauksen**, nappaa sieltC$
Base64-kuvan (`numbered_outlines`) sekC$ DOMista luetut tekstit
(`semantic_map`) ja **sulauttaa (injektoi)** ne lennosta osaksi alkuperC$istC$
LLM-promptia, ennen kuin pyyntC6 edes ilmestyy Valvomon nC$ytC6lle.
### 1. Korvaa Valvomon Rikastusfunktio
Etsi `valvomo_proxy.py` -tiedostostasi funktio `apply_enrichment_pipelines` ja
korvaa se kokonaisuudessaan tC$llC$ pC$ivitetyllC$ versiolla:
```python
# ==========================================
# APUPROSESSI: RIKASTUS (ENRICHMENT)
# ==========================================
def apply_enrichment_pipelines(req_dict: dict, state: dict) -> dict:
"""Juoksuttaa saapuvan pyynnC6n aktiivisten rikastusputkien lC$pi."""
messages = req_dict.get("messages", [])
text_to_search = extract_text_for_regex(messages)
for rule_name, rule in state.get("enrichment_rules", {}).items():
if not rule.get("enabled"):
continue
# KC$ytetC$C$n regexiC$ ja etsitC$C$n mahdollisia ryhmiC$ (capture
groups).
# Esimerkiksi regex: "Etsi ruudulta:\s*(.*)" nappaa sanan "whatsapp".
match = re.search(rule.get("regex", ""), text_to_search,
re.IGNORECASE)
if match:
try:
print(f"[*] Laukaistiin rikastusputki: '{rule_name}'")
# Jos regexissC$ oli sulut, otetaan hakutermiksi se. Muuten
oletus.
haku_termi = match.group(1).strip() if len(match.groups()) > 0
else "whatsapp"
# 1. Rakennetaan UI Vision Extractorin vaatima payload
ext_payload = {
"haku_termi": haku_termi,
"full_page": False,
"wait_time_ms": 1000
}
# 2. Ammutaan pyyntC6 mikropalvelulle (Timeout hieman pidempi
selainta varten)
resp = requests.post(rule["url"], json=ext_payload,
timeout=15)
if resp.status_code == 200:
enriched_data = resp.json()
# 3. Tarkistetaan onko vastaus sinun UI Extractor
-palvelustasi
if "semantic_map" in enriched_data and "images_base64" in
enriched_data:
element_count = enriched_data.get('metadata',
{}).get('element_count', 0)
print(f"[+] UI Extractor onnistui. LC6ydettiin
{element_count} elementtiC$.")
b64_img = enriched_data["images_base64"]["numbered_outl
ines"]
# 4. Luodaan tekoC$lylle kultaakin kalliimpi
konteksti: lista ruudulla oikeasti nC$kyvistC$ teksteistC$!
map_texts = [f"[{el['id']}] '{el['text']}'" for el in
enriched_data["semantic_map"] if el.get("text")]
dom_context = "Ruudulta tunnistetut tekstit ja niiden
ID-numerot:
" + ", ".join(map_texts)
# 5. Injektoidaan kuva ja tekstit alkuperC$iseen
user-viestiin
for m in reversed(messages):
if m["role"] == "user":
orig_text = m.get("content", "")
if isinstance(orig_text, list):
orig_text = next((c["text"] for c in
orig_text if c["type"] == "text"), "")
new_prompt_text = f"{orig_text}
[SYSTEM: AUTOMAATTINEN RIKASTUS]
{dom_context}"
# Muutetaan viesti OpenAI Vision
-monimuotoiseksi
m["content"] = [
{"type": "text", "text": new_prompt_text},
{"type": "image_url", "image_url": {"url":
f"data:image/jpeg;base64,{b64_img}"}}
]
break
req_dict["messages"] = messages
except Exception as e:
# Jos mikropalvelu kaatuu tai selainta ei lC6ydy, jatketaan
eteenpC$in ilman rikastusta
print(f"[!] Rikastusputki '{rule_name}' epC$onnistui
(ohitetaan): {e}")
return req_dict
```