Spaces:
Paused
Paused
| """ | |
| workflow_engine.py β Motor de workflow YAWL-inspired para a Trindade Pipeline. | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Conceitos YAWL implementados | |
| βββββββββββββββββββββββββββββ | |
| Task : unidade atΓ΄mica de execuΓ§Γ£o (um turno de LLM ou operaΓ§Γ£o) | |
| Condition : expressΓ£o Python avaliada contra o ExecutionContext | |
| XOR-split : exatamente um arco de saΓda dispara (primeira condiΓ§Γ£o verdadeira) | |
| AND-split : todos os arcos disparam (execuΓ§Γ£o paralela β futuro) | |
| OR-split : um ou mais arcos disparam (subconjunto verdadeiro) | |
| Loop : task repete enquanto condiΓ§Γ£o for verdadeira | |
| SubNet : task que encapsula um sub-workflow inteiro | |
| Fluxo de controle | |
| ββββββββββββββββββ | |
| WorkflowEngine.start() β retorna a primeira TaskDef | |
| WorkflowEngine.advance(ctx) β avalia transiΓ§Γ΅es do nΓ³ atual, retorna prΓ³ximo | |
| WorkflowEngine.is_terminal() β True se chegou ao nΓ³ END | |
| O ExecutionContext Γ© um dict simples que o pipeline preenche apΓ³s cada task. | |
| O motor lΓͺ esse dict para avaliar as condiΓ§Γ΅es β nΓ£o conhece LLMs, HTTP, nem JSON. | |
| SeguranΓ§a do eval | |
| ββββββββββββββββββ | |
| CondiΓ§Γ΅es sΓ£o avaliadas com eval() em namespace restrito: | |
| β’ Apenas builtins seguros (len, int, float, bool, str, min, max, abs, round) | |
| β’ Mais todas as chaves do ExecutionContext | |
| β’ Nenhum acesso a __import__, open, os, sys, etc. | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| """ | |
| from __future__ import annotations | |
| import copy | |
| from dataclasses import dataclass, field | |
| from enum import Enum | |
| from typing import Any, Dict, List, Optional | |
| import yaml | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TIPOS DE SPLIT | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class SplitType(str, Enum): | |
| XOR = "XOR" # exatamente um arco (default) | |
| AND = "AND" # todos os arcos (paralelo) | |
| OR = "OR" # um ou mais arcos | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TRANSIΓΓO (arco de saΓda de um nΓ³) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Transition: | |
| target: str | |
| condition: Optional[str] = None # None = default/unconditional | |
| label: str = "" | |
| def evaluate(self, ctx: Dict[str, Any]) -> bool: | |
| """Avalia a condiΓ§Γ£o contra o contexto. None β sempre True.""" | |
| if self.condition is None: | |
| return True | |
| return _safe_eval(self.condition, ctx) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TASK DEFINITION | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TaskDef: | |
| """ | |
| DefiniΓ§Γ£o declarativa de uma task no workflow. | |
| Campos relevantes para o executor (pipeline_v33.py): | |
| id : identificador ΓΊnico do nΓ³ | |
| task_type : "reasoning" | "final" | "audit_reasoning" | | |
| "audit_final" | "normalize" | "validate" | | |
| "loop_recovery" | "end" | |
| phase : chave em PAYLOAD_CONFIG (ex. "STEP1", "AUDIT_REASONING") | |
| provider : slug do provider (ex. "groq", "openrouter") | |
| model : model id (sobrescreve o default do PAYLOAD_CONFIG) | |
| stop_tokens : se True, usa STOP_TOKENS_REASONING | |
| is_final : agente final (escreve JSON completo) | |
| loop : TaskLoop se esta task pode ser repetida | |
| O executor nΓ£o interpreta nenhum outro campo β passa como `task.params`. | |
| """ | |
| id: str | |
| task_type: str | |
| phase: str | |
| provider: str = "groq" | |
| model: Optional[str] = None | |
| stop_tokens: bool = False | |
| is_final: bool = False | |
| loop: Optional["TaskLoop"] = None | |
| transitions: List[Transition] = field(default_factory=list) | |
| params: Dict[str, Any] = field(default_factory=dict) | |
| description: str = "" | |
| prompt: Optional[str] = None | |
| instructions: Optional[str] = None | |
| output_keys: List[str] = field(default_factory=list) | |
| def is_terminal(self) -> bool: | |
| return self.task_type == "end" | |
| def next_tasks(self, ctx: Dict[str, Any], split: SplitType = SplitType.XOR) -> List[str]: | |
| """Retorna ids dos prΓ³ximos nΓ³s dado o contexto atual.""" | |
| results: List[str] = [] | |
| for t in self.transitions: | |
| if t.evaluate(ctx): | |
| results.append(t.target) | |
| if split == SplitType.XOR: | |
| break # XOR: para no primeiro verdadeiro | |
| return results | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # LOOP DEFINITION | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TaskLoop: | |
| """ | |
| Define comportamento de loop para uma task. | |
| while_condition : expressΓ£o avaliada ANTES de cada iteraΓ§Γ£o | |
| until_condition : expressΓ£o avaliada APΓS cada iteraΓ§Γ£o (do-while) | |
| max_iterations : teto de seguranΓ§a (evita loop infinito) | |
| """ | |
| while_condition: Optional[str] = None | |
| until_condition: Optional[str] = None | |
| max_iterations: int = 3 | |
| def should_enter(self, ctx: Dict[str, Any]) -> bool: | |
| if self.while_condition: | |
| return _safe_eval(self.while_condition, ctx) | |
| return True | |
| def should_continue(self, ctx: Dict[str, Any], iteration: int) -> bool: | |
| if iteration >= self.max_iterations: | |
| return False | |
| if self.until_condition: | |
| return not _safe_eval(self.until_condition, ctx) | |
| if self.while_condition: | |
| return _safe_eval(self.while_condition, ctx) | |
| return False | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # EXECUTION CONTEXT | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class ExecutionContext(dict): | |
| """ | |
| Dict especializado que representa o estado de execuΓ§Γ£o do workflow. | |
| O motor lΓͺ este dict para avaliar condiΓ§Γ΅es. | |
| O executor escreve nele apΓ³s cada task. | |
| Chaves convencionais (o motor conhece) | |
| βββββββββββββββββββββββββββββββββββββββ | |
| reasoning_chars : int β chars de reasoning da ΓΊltima fase intermediΓ‘ria | |
| json_valid : bool β True se o JSON final passou parse | |
| audit_passed : bool β True se o ΓΊltimo ciclo de audit retornou AUDIT_PASS | |
| loop_count : int β tentativas de recovery de loop | |
| atom_count : int β total de Γ‘tomos extraΓdos | |
| correction_count : int β ciclos de correΓ§Γ£o do audit | |
| schema_errors : int β erros de schema apΓ³s validate_and_fix | |
| phase_failed : bool β True se a fase atual falhou (content=None) | |
| current_phase : str β id do nΓ³ em execuΓ§Γ£o | |
| """ | |
| def update_phase(self, phase_id: str, **kwargs: Any) -> None: | |
| self["current_phase"] = phase_id | |
| self.update(kwargs) | |
| def increment(self, key: str, by: int = 1) -> int: | |
| self[key] = self.get(key, 0) + by | |
| return self[key] | |
| def snapshot(self) -> Dict[str, Any]: | |
| return dict(self) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # WORKFLOW ENGINE | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class WorkflowEngine: | |
| """ | |
| MΓ‘quina de estados finitos YAWL-inspired. | |
| Uso tΓpico no pipeline_v33.py | |
| βββββββββββββββββββββββββββββββ | |
| engine = WorkflowEngine.from_yaml("trindade_workflow.yaml") | |
| ctx = ExecutionContext(defaults) | |
| task = engine.start() | |
| while not task.is_terminal: | |
| result = executor.run(task, memory) | |
| ctx.update(result) | |
| task = engine.advance(ctx) | |
| """ | |
| def __init__(self, tasks: Dict[str, TaskDef], start_id: str) -> None: | |
| self._tasks = tasks | |
| self._start_id = start_id | |
| self._current = start_id | |
| self._wrapper_key = "output" # sobrescrito por from_dict | |
| self._id_key = "id" # sobrescrito por from_dict | |
| def wrapper_key(self) -> str: | |
| """Chave de wrapper do resultado final (ex: 'manifestacao_juridica').""" | |
| return self._wrapper_key | |
| def id_key(self) -> str: | |
| """Chave de id dentro do wrapper (ex: 'id_manifestacao').""" | |
| return self._id_key | |
| # ββ NavegaΓ§Γ£o βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def start(self) -> TaskDef: | |
| self._current = self._start_id | |
| return self._tasks[self._current] | |
| def current(self) -> TaskDef: | |
| return self._tasks[self._current] | |
| def advance(self, ctx: ExecutionContext) -> TaskDef: | |
| """ | |
| Avalia as transiΓ§Γ΅es do nΓ³ atual e move para o prΓ³ximo. | |
| Retorna a TaskDef do prΓ³ximo nΓ³ (pode ser END). | |
| """ | |
| current_task = self._tasks[self._current] | |
| split_type = SplitType(current_task.params.get("split", SplitType.XOR.value)) | |
| next_ids = current_task.next_tasks(ctx, split=split_type) | |
| if not next_ids: | |
| # Sem transiΓ§Γ£o β END implΓcito | |
| self._current = "__END__" | |
| return TaskDef(id="__END__", task_type="end", phase="END") | |
| # Para XOR e OR, executa o primeiro target (AND Γ© futuro) | |
| next_id = next_ids[0] | |
| if next_id not in self._tasks: | |
| raise KeyError(f"NΓ³ '{next_id}' referenciado mas nΓ£o definido no workflow") | |
| self._current = next_id | |
| ctx["current_phase"] = next_id | |
| return self._tasks[next_id] | |
| def peek_next(self, ctx: ExecutionContext) -> Optional[str]: | |
| """Retorna o id do prΓ³ximo nΓ³ sem avanΓ§ar o estado.""" | |
| task = self._tasks[self._current] | |
| ids = task.next_tasks(ctx) | |
| return ids[0] if ids else None | |
| def reset(self) -> None: | |
| self._current = self._start_id | |
| # ββ Factory βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def from_yaml(cls, path: str) -> "WorkflowEngine": | |
| """Carrega um workflow de um arquivo YAML.""" | |
| with open(path, encoding="utf-8") as f: | |
| spec = yaml.safe_load(f) | |
| return cls.from_dict(spec) | |
| def from_dict(cls, spec: Dict[str, Any]) -> "WorkflowEngine": | |
| """ConstrΓ³i o engine a partir de um dict (jΓ‘ parseado).""" | |
| wf = spec["workflow"] | |
| start_id = wf["start"] | |
| tasks: Dict[str, TaskDef] = {} | |
| # ββ Defaults do workflow (base para todos os tasks) ββββββββ | |
| wf_defaults = wf.get("defaults", {}) | |
| # Campos top-level da TaskDef β nΓ£o vΓ£o para params | |
| _top_level = {"id", "type", "phase", "provider", "model", "stop_tokens", | |
| "is_final", "loop", "transitions", "description", | |
| "prompt", "instructions", "output_keys", "params"} | |
| # ββ ConfiguraΓ§Γ£o de saΓda agnΓ³stica ββββββββββββββββββββββββ | |
| out_cfg = wf.get("output", {}) | |
| wrapper_key = out_cfg.get("wrapper_key", "output") | |
| id_key = out_cfg.get("id_key", "id") | |
| for raw in wf["tasks"]: | |
| task_id = raw["id"] | |
| # ββ Loop ββββββββββββββββββββββββββββββββββββββββββββββ | |
| loop = None | |
| if "loop" in raw: | |
| lraw = raw["loop"] | |
| loop = TaskLoop( | |
| while_condition = lraw.get("while"), | |
| until_condition = lraw.get("until"), | |
| max_iterations = lraw.get("max_iterations", 3), | |
| ) | |
| # ββ Transitions βββββββββββββββββββββββββββββββββββββββ | |
| transitions = [] | |
| for t in raw.get("transitions", []): | |
| transitions.append(Transition( | |
| target = t["target"], | |
| condition = t.get("condition"), | |
| label = t.get("label", ""), | |
| )) | |
| # ββ Params: defaults do YAML β sobrescritos por task ββ | |
| params = {k: v for k, v in wf_defaults.items() if k not in _top_level} | |
| params.update({k: v for k, v in raw.items() if k not in _top_level}) | |
| tasks[task_id] = TaskDef( | |
| id = task_id, | |
| task_type = raw.get("type", "task"), | |
| phase = raw.get("phase", task_id), | |
| provider = raw.get("provider", wf_defaults.get("provider", "groq")), | |
| model = raw.get("model", wf_defaults.get("model")), | |
| stop_tokens = raw.get("stop_tokens", False), | |
| is_final = raw.get("is_final", False), | |
| loop = loop, | |
| transitions = transitions, | |
| params = params, | |
| description = raw.get("description", ""), | |
| prompt = raw.get("prompt"), | |
| instructions = raw.get("instructions"), | |
| output_keys = raw.get("output_keys", []), | |
| ) | |
| engine = cls(tasks=tasks, start_id=start_id) | |
| engine._wrapper_key = wrapper_key | |
| engine._id_key = id_key | |
| return engine | |
| # ββ InspeΓ§Γ£o ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def task_ids(self) -> List[str]: | |
| return list(self._tasks.keys()) | |
| def describe(self) -> str: | |
| """Retorna representaΓ§Γ£o textual do grafo para debug.""" | |
| lines = [f"WorkflowEngine start={self._start_id} nodes={len(self._tasks)}"] | |
| for tid, t in self._tasks.items(): | |
| arrow = " β ".join( | |
| f"{tr.target}[{tr.condition or 'default'}]" | |
| for tr in t.transitions | |
| ) or "(terminal)" | |
| lines.append(f" {tid:30s} ({t.task_type:20s}) {arrow}") | |
| return "\n".join(lines) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SAFE EVAL β avalia condiΓ§Γ΅es em namespace restrito | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _SAFE_BUILTINS = { | |
| "len": len, | |
| "int": int, | |
| "float": float, | |
| "bool": bool, | |
| "str": str, | |
| "min": min, | |
| "max": max, | |
| "abs": abs, | |
| "round": round, | |
| "True": True, | |
| "False": False, | |
| "None": None, | |
| "all": all, | |
| "any": any, | |
| } | |
| def _safe_eval(expr: str, ctx: Dict[str, Any]) -> bool: | |
| """ | |
| Avalia `expr` (string Python) contra `ctx`. | |
| Namespace: builtins seguros + ctx. | |
| Qualquer exceΓ§Γ£o β False (condiΓ§Γ£o nΓ£o satisfeita). | |
| Exemplos vΓ‘lidos de expressΓ΅es: | |
| "reasoning_chars > 100" | |
| "audit_passed == True" | |
| "loop_count < 2 and not phase_failed" | |
| "atom_count >= 3" | |
| "schema_errors == 0" | |
| """ | |
| namespace = {**_SAFE_BUILTINS, **ctx} | |
| try: | |
| result = eval(expr, {"__builtins__": {}}, namespace) # noqa: S307 | |
| return bool(result) | |
| except Exception: | |
| return False | |