ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
Workflow data structures for capturing and storing browser action sequences.
|
||||
|
||||
This module defines the structures used to represent learned workflows,
|
||||
including individual steps and complete action sequences.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
from enum import Enum
|
||||
import json
|
||||
|
||||
|
||||
class ActionType(Enum):
|
||||
"""Types of actions that can be recorded in a workflow"""
|
||||
NAVIGATE = "navigate"
|
||||
CLICK = "click"
|
||||
INPUT_TEXT = "input_text"
|
||||
SELECT_OPTION = "select_option"
|
||||
SCROLL = "scroll"
|
||||
WAIT = "wait"
|
||||
SWITCH_TAB = "switch_tab"
|
||||
CLOSE_TAB = "close_tab"
|
||||
UPLOAD_FILE = "upload_file"
|
||||
|
||||
|
||||
class PredicateType(Enum):
|
||||
"""Machine-checkable browser state predicates."""
|
||||
URL_CONTAINS = "url_contains"
|
||||
ELEMENT_VISIBLE = "element_visible"
|
||||
ELEMENT_TEXT_CONTAINS = "element_text_contains"
|
||||
ELEMENT_VALUE_EQUALS = "element_value_equals"
|
||||
PAGE_STATE_EQUALS = "page_state_equals"
|
||||
|
||||
|
||||
class WorkflowStatus(Enum):
|
||||
CANDIDATE = "candidate"
|
||||
VALIDATED = "validated"
|
||||
INVALID = "invalid"
|
||||
|
||||
|
||||
@dataclass
|
||||
class StatePredicate:
|
||||
"""A precondition, postcondition or final-state assertion."""
|
||||
|
||||
predicate_type: PredicateType
|
||||
expected: Any = True
|
||||
selector: Optional[str] = None
|
||||
state_key: Optional[str] = None
|
||||
description: str = ""
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"predicate_type": self.predicate_type.value,
|
||||
"expected": self.expected,
|
||||
"selector": self.selector,
|
||||
"state_key": self.state_key,
|
||||
"description": self.description,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'StatePredicate':
|
||||
values = dict(data)
|
||||
values["predicate_type"] = PredicateType(values["predicate_type"])
|
||||
return cls(**values)
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowStep:
|
||||
"""Represents a single step in a workflow"""
|
||||
|
||||
action_type: ActionType
|
||||
|
||||
# Stable selectors for element identification
|
||||
xpath: Optional[str] = None
|
||||
css_selector: Optional[str] = None
|
||||
|
||||
# Action parameters
|
||||
parameters: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
# Additional context
|
||||
element_attributes: Dict[str, str] = field(default_factory=dict)
|
||||
description: str = ""
|
||||
|
||||
# Timing information
|
||||
wait_before: float = 0.0 # Seconds to wait before executing this step
|
||||
timeout: float = 15.0 # Maximum time to wait for element to be ready
|
||||
|
||||
# Validation
|
||||
expected_outcome: Optional[str] = None
|
||||
preconditions: List[StatePredicate] = field(default_factory=list)
|
||||
postconditions: List[StatePredicate] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert step to dictionary for serialization"""
|
||||
return {
|
||||
"action_type": self.action_type.value,
|
||||
"xpath": self.xpath,
|
||||
"css_selector": self.css_selector,
|
||||
"parameters": self.parameters,
|
||||
"element_attributes": self.element_attributes,
|
||||
"description": self.description,
|
||||
"wait_before": self.wait_before,
|
||||
"timeout": self.timeout,
|
||||
"expected_outcome": self.expected_outcome,
|
||||
"preconditions": [item.to_dict() for item in self.preconditions],
|
||||
"postconditions": [item.to_dict() for item in self.postconditions],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'WorkflowStep':
|
||||
"""Create step from dictionary"""
|
||||
data = data.copy()
|
||||
data['action_type'] = ActionType(data['action_type'])
|
||||
data['preconditions'] = [StatePredicate.from_dict(item) for item in data.get('preconditions', [])]
|
||||
data['postconditions'] = [StatePredicate.from_dict(item) for item in data.get('postconditions', [])]
|
||||
return cls(**data)
|
||||
|
||||
@classmethod
|
||||
def from_browser_action(cls, action_type: str, element: Optional[Any] = None, **params) -> 'WorkflowStep':
|
||||
"""Create a workflow step from browser-use action and element info"""
|
||||
step = cls(
|
||||
action_type=ActionType(action_type.lower()),
|
||||
parameters=params
|
||||
)
|
||||
|
||||
if element:
|
||||
# Extract stable selectors from DOMInteractedElement
|
||||
if hasattr(element, 'x_path'):
|
||||
step.xpath = element.x_path
|
||||
|
||||
# Store relevant attributes for fallback identification
|
||||
if hasattr(element, 'attributes') and element.attributes:
|
||||
step.element_attributes = {
|
||||
k: v for k, v in element.attributes.items()
|
||||
if k in ['id', 'name', 'class', 'type', 'role', 'aria-label', 'data-testid']
|
||||
}
|
||||
|
||||
return step
|
||||
|
||||
|
||||
@dataclass
|
||||
class Workflow:
|
||||
"""Represents a complete workflow that can be learned and replayed"""
|
||||
|
||||
# Identification
|
||||
workflow_id: str
|
||||
intent: str # The task intent this workflow accomplishes
|
||||
|
||||
# Steps
|
||||
steps: List[WorkflowStep] = field(default_factory=list)
|
||||
|
||||
# Metadata
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
last_used_at: Optional[datetime] = None
|
||||
success_count: int = 0
|
||||
failure_count: int = 0
|
||||
|
||||
# Learning context
|
||||
initial_url: Optional[str] = None
|
||||
example_parameters: Dict[str, Any] = field(default_factory=dict)
|
||||
description: str = ""
|
||||
|
||||
# Performance metrics
|
||||
average_execution_time: float = 0.0
|
||||
model_calls_saved: int = 0
|
||||
|
||||
# Validation lifecycle. New workflows are candidates until a complete
|
||||
# replay succeeds in a reset environment.
|
||||
validation_status: WorkflowStatus = WorkflowStatus.CANDIDATE
|
||||
final_predicates: List[StatePredicate] = field(default_factory=list)
|
||||
validated_at: Optional[datetime] = None
|
||||
invalid_reason: Optional[str] = None
|
||||
|
||||
def add_step(self, step: WorkflowStep) -> None:
|
||||
"""Add a step to the workflow"""
|
||||
self.steps.append(step)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert workflow to dictionary for serialization"""
|
||||
return {
|
||||
"workflow_id": self.workflow_id,
|
||||
"intent": self.intent,
|
||||
"steps": [step.to_dict() for step in self.steps],
|
||||
"created_at": self.created_at.isoformat(),
|
||||
"last_used_at": self.last_used_at.isoformat() if self.last_used_at else None,
|
||||
"success_count": self.success_count,
|
||||
"failure_count": self.failure_count,
|
||||
"initial_url": self.initial_url,
|
||||
"example_parameters": self.example_parameters,
|
||||
"description": self.description,
|
||||
"average_execution_time": self.average_execution_time,
|
||||
"model_calls_saved": self.model_calls_saved,
|
||||
"validation_status": self.validation_status.value,
|
||||
"final_predicates": [item.to_dict() for item in self.final_predicates],
|
||||
"validated_at": self.validated_at.isoformat() if self.validated_at else None,
|
||||
"invalid_reason": self.invalid_reason,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'Workflow':
|
||||
"""Create workflow from dictionary"""
|
||||
data = data.copy()
|
||||
data['steps'] = [WorkflowStep.from_dict(s) for s in data.get('steps', [])]
|
||||
data['created_at'] = datetime.fromisoformat(data['created_at'])
|
||||
if data.get('last_used_at'):
|
||||
data['last_used_at'] = datetime.fromisoformat(data['last_used_at'])
|
||||
# Old files had no lifecycle field. Treat them as candidates so they
|
||||
# cannot silently bypass the new validation protocol.
|
||||
data['validation_status'] = WorkflowStatus(data.get('validation_status', 'candidate'))
|
||||
data['final_predicates'] = [StatePredicate.from_dict(item) for item in data.get('final_predicates', [])]
|
||||
if data.get('validated_at'):
|
||||
data['validated_at'] = datetime.fromisoformat(data['validated_at'])
|
||||
return cls(**data)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Serialize workflow to JSON string"""
|
||||
return json.dumps(self.to_dict(), indent=2, default=str)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> 'Workflow':
|
||||
"""Deserialize workflow from JSON string"""
|
||||
data = json.loads(json_str)
|
||||
return cls.from_dict(data)
|
||||
|
||||
def parameterize(self, parameters: Dict[str, Any]) -> 'Workflow':
|
||||
"""
|
||||
Create a parameterized copy of this workflow with specific values.
|
||||
|
||||
Args:
|
||||
parameters: Dictionary mapping parameter names to values
|
||||
|
||||
Returns:
|
||||
A new Workflow instance with parameters applied
|
||||
"""
|
||||
import copy
|
||||
parameterized = copy.deepcopy(self)
|
||||
|
||||
# Apply parameters to each step
|
||||
for step in parameterized.steps:
|
||||
for param_key, param_value in parameters.items():
|
||||
# Replace placeholders in step parameters
|
||||
for key, value in step.parameters.items():
|
||||
if isinstance(value, str) and f"{{{param_key}}}" in value:
|
||||
step.parameters[key] = value.replace(f"{{{param_key}}}", str(param_value))
|
||||
for predicate in (*step.preconditions, *step.postconditions):
|
||||
if isinstance(predicate.expected, str) and f"{{{param_key}}}" in predicate.expected:
|
||||
predicate.expected = predicate.expected.replace(f"{{{param_key}}}", str(param_value))
|
||||
for predicate in parameterized.final_predicates:
|
||||
for param_key, param_value in parameters.items():
|
||||
if isinstance(predicate.expected, str) and f"{{{param_key}}}" in predicate.expected:
|
||||
predicate.expected = predicate.expected.replace(f"{{{param_key}}}", str(param_value))
|
||||
|
||||
return parameterized
|
||||
|
||||
def mark_validated(self) -> None:
|
||||
self.validation_status = WorkflowStatus.VALIDATED
|
||||
self.validated_at = datetime.now()
|
||||
self.invalid_reason = None
|
||||
|
||||
def mark_invalid(self, reason: str) -> None:
|
||||
self.validation_status = WorkflowStatus.INVALID
|
||||
self.invalid_reason = reason
|
||||
Reference in New Issue
Block a user