docs(my-actor.yaml): add an example actor (#3)
CI / security (push) Successful in 33s
CI / unit_tests (push) Successful in 42s
CI / coverage (push) Successful in 46s
CI / typecheck (push) Successful in 1m1s
CI / lint (push) Successful in 1m1s
CI / dead_code (push) Successful in 1m5s
CI / build (push) Successful in 27s
CI / security (push) Successful in 33s
CI / unit_tests (push) Successful in 42s
CI / coverage (push) Successful in 46s
CI / typecheck (push) Successful in 1m1s
CI / lint (push) Successful in 1m1s
CI / dead_code (push) Successful in 1m5s
CI / build (push) Successful in 27s
This code adds documentation of a sample actor. ISSUES CLOSED: #1 Reviewed-on: #3 Reviewed-by: Luis Mendes <luis.mendes@cleverthis.com>
This commit was merged in pull request #3.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Simple Graph Actor - Sequential Processing
|
||||
# Demonstrates a simple 3-node graph with linear execution
|
||||
|
||||
name: local/proofreader
|
||||
type: graph
|
||||
description: Simple proofreader
|
||||
version: "1.0.0"
|
||||
|
||||
# LLM model
|
||||
provider: openai
|
||||
model: gpt-3.5-turbo
|
||||
|
||||
# Graph topology
|
||||
route:
|
||||
nodes:
|
||||
# Node 1: Extract text from document
|
||||
- id: extractor
|
||||
type: tool
|
||||
name: extractor
|
||||
description: Extracts text from various document formats
|
||||
config:
|
||||
tool_name: documents/extract_text
|
||||
parameters:
|
||||
formats:
|
||||
- pdf
|
||||
- docx
|
||||
- txt
|
||||
|
||||
# Node 2: Analyze content
|
||||
- id: proofreader
|
||||
type: agent
|
||||
name: Proofreader
|
||||
description: Proofreads a document.
|
||||
config:
|
||||
model: gpt-3.5-turbo
|
||||
prompt: |
|
||||
You are an expert proofreader. Read the given text and correct:
|
||||
- misspellings
|
||||
- incorrect grammar
|
||||
- incorrect punctuation
|
||||
- incorrect capitalization
|
||||
- duplicate words
|
||||
- misplaced modifiers
|
||||
|
||||
Provide constructive feedback with specific suppestions for improvement.
|
||||
|
||||
# Linear edges
|
||||
edges:
|
||||
- from_node: extractor
|
||||
to_node: proofreader
|
||||
|
||||
# Entry and exit
|
||||
entry_node: extractor
|
||||
exit_nodes:
|
||||
- proofreader
|
||||
|
||||
# Context settings
|
||||
context_view: executor
|
||||
memory:
|
||||
enabled: true
|
||||
max_messages: 10
|
||||
|
||||
context:
|
||||
max_context_tokens: 4000
|
||||
@@ -13,5 +13,5 @@ def before_all(context: Any) -> None:
|
||||
"""Hook invoked once before any features run."""
|
||||
|
||||
|
||||
def before_scenario(context: Any, scenario: Any) -> None: # noqa: ARG001
|
||||
def before_scenario(context: Any, scenario: Any) -> None:
|
||||
"""Per-scenario reset of any context state."""
|
||||
|
||||
@@ -71,11 +71,9 @@ def when_compiled(context: Any) -> None:
|
||||
try:
|
||||
# NB: ``context.config`` is a reserved attribute on Behave's
|
||||
# Context; using ``actor_config`` keeps us out of its way.
|
||||
context.actor_config = ActorConfigSchema.model_validate(
|
||||
context.actor_raw
|
||||
)
|
||||
context.actor_config = ActorConfigSchema.model_validate(context.actor_raw)
|
||||
context.compiled = compile_actor(context.actor_config)
|
||||
except Exception as exc: # noqa: BLE001 (test capture)
|
||||
except Exception as exc:
|
||||
context.compile_exc = exc
|
||||
|
||||
|
||||
@@ -84,7 +82,7 @@ def when_validated(context: Any) -> None:
|
||||
context.validation_exc = None
|
||||
try:
|
||||
ActorConfigSchema.model_validate(context.actor_raw)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
context.validation_exc = exc
|
||||
|
||||
|
||||
|
||||
@@ -44,6 +44,18 @@ from cleveractors.lsp.models import LspBinding
|
||||
class ActorCompilationError(ValidationError):
|
||||
"""Raised when actor compilation fails."""
|
||||
|
||||
def __init__(
|
||||
self, message: str | None = None, details: dict[str, str] | None = None
|
||||
) -> None:
|
||||
if message is not None and details is not None:
|
||||
super().__init__(details={"details": message, **details})
|
||||
elif message is not None:
|
||||
super().__init__(details={"details": message})
|
||||
elif details is not None:
|
||||
super().__init__(details=details)
|
||||
else:
|
||||
super().__init__(details={})
|
||||
|
||||
|
||||
class SubgraphCycleError(ActorCompilationError):
|
||||
"""Raised when subgraph references form a cycle."""
|
||||
@@ -179,7 +191,6 @@ def _extract_lsp_bindings(node: NodeDefinition) -> list[LspBinding]:
|
||||
|
||||
|
||||
def _detect_subgraph_cycles(
|
||||
actor_name: str,
|
||||
route_nodes: list[NodeDefinition],
|
||||
resolver: ActorResolver | None,
|
||||
visited: frozenset[str],
|
||||
@@ -212,7 +223,6 @@ def _detect_subgraph_cycles(
|
||||
continue
|
||||
|
||||
deeper = _detect_subgraph_cycles(
|
||||
ref_name,
|
||||
referenced.route.nodes,
|
||||
resolver,
|
||||
visited | {ref_name},
|
||||
@@ -271,7 +281,7 @@ def compile_actor(
|
||||
|
||||
# Detect cross-actor subgraph cycles
|
||||
cross_cycles = _detect_subgraph_cycles(
|
||||
config.name, route.nodes, actor_resolver, frozenset({config.name})
|
||||
route.nodes, actor_resolver, frozenset({config.name})
|
||||
)
|
||||
if cross_cycles:
|
||||
raise SubgraphCycleError(
|
||||
|
||||
@@ -184,14 +184,19 @@ class ActorLoader:
|
||||
if duplicate_msgs:
|
||||
all_errors = errors + duplicate_msgs
|
||||
raise ValidationError(
|
||||
"Actor discovery failed:\n" + "\n".join(all_errors),
|
||||
details={"duplicates": duplicate_msgs, "errors": errors},
|
||||
details={
|
||||
"message": "Actor discovery failed:\n" + "\n".join(all_errors),
|
||||
"duplicates": ", ".join(duplicate_msgs),
|
||||
"errors": ", ".join(errors),
|
||||
}
|
||||
)
|
||||
|
||||
if errors:
|
||||
raise ValidationError(
|
||||
"Actor discovery failed:\n" + "\n".join(errors),
|
||||
details={"errors": errors},
|
||||
details={
|
||||
"message": "Actor discovery failed:\n" + "\n".join(errors),
|
||||
"errors": ", ".join(errors),
|
||||
},
|
||||
)
|
||||
|
||||
new_actors: dict[str, _CacheEntry] = {}
|
||||
|
||||
@@ -31,6 +31,9 @@ class ValidationError(Exception):
|
||||
- Invalid entry / exit points in the graph route.
|
||||
"""
|
||||
|
||||
def __init__(self, details: dict[str, str], *args: object) -> None:
|
||||
super().__init__(details, *args)
|
||||
|
||||
|
||||
class NotFoundError(Exception):
|
||||
"""Raised when a named entity is referenced but cannot be located.
|
||||
|
||||
@@ -1,32 +1,11 @@
|
||||
"""LangGraph primitives adapted for actor-first routing.
|
||||
|
||||
``graph`` sub-module is lazy-loaded because it pulls in
|
||||
``langchain_core``, ``langsmith``, and ``rx.operators`` which add
|
||||
multiple seconds of import overhead. Lightweight symbols from
|
||||
``nodes`` and ``state`` are available eagerly.
|
||||
"""
|
||||
"""LangGraph primitives adapted for actor-first routing."""
|
||||
|
||||
from .nodes import Edge, NodeConfig, NodeType
|
||||
from .state import GraphState
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
_GRAPH_ATTRS = {"GraphConfig", "LangGraph"}
|
||||
if name in _GRAPH_ATTRS:
|
||||
from .graph import GraphConfig as _GraphConfig
|
||||
from .graph import LangGraph as _LangGraph
|
||||
|
||||
globals()["GraphConfig"] = _GraphConfig
|
||||
globals()["LangGraph"] = _LangGraph
|
||||
return globals()[name]
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Edge",
|
||||
"GraphConfig",
|
||||
"GraphState",
|
||||
"LangGraph",
|
||||
"NodeConfig",
|
||||
"NodeType",
|
||||
]
|
||||
|
||||
@@ -26,6 +26,8 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
T = TypeVar("T", bound="GraphState")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StateUpdateMode(Enum):
|
||||
REPLACE = "replace"
|
||||
MERGE = "merge"
|
||||
@@ -91,5 +93,3 @@ class GraphState(BaseModel):
|
||||
@classmethod
|
||||
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
||||
return cls(**data)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user