first recipe: extract textcontent from output
This commit is contained in:
@@ -75,6 +75,14 @@ from .nodes import (
|
||||
outcome,
|
||||
)
|
||||
from .reducers import AuthoredReducer, ReducerCatalog, reducer
|
||||
from .recipes import (
|
||||
ExtractTextContentInput,
|
||||
ExtractTextContentOutput,
|
||||
ExtractTextContentState,
|
||||
build_extract_text_content_spec,
|
||||
build_extract_text_content_workflow,
|
||||
extract_text_content,
|
||||
)
|
||||
from .schemas import StateFieldMetadata, state_field
|
||||
from .subgraph import async_subgraph_node, subgraph_node, subgraph_ref
|
||||
|
||||
@@ -89,6 +97,9 @@ __all__ = [
|
||||
"ConstantInput",
|
||||
"CountOutput",
|
||||
"ExtractFieldInput",
|
||||
"ExtractTextContentInput",
|
||||
"ExtractTextContentOutput",
|
||||
"ExtractTextContentState",
|
||||
"FilterItemsInput",
|
||||
"FilterItemsPresentInput",
|
||||
"GraphPath",
|
||||
@@ -117,12 +128,15 @@ __all__ = [
|
||||
"bind_fields",
|
||||
"build_async_registry",
|
||||
"build_registry",
|
||||
"build_extract_text_content_spec",
|
||||
"build_extract_text_content_workflow",
|
||||
"bind_state",
|
||||
"coalesce",
|
||||
"concat",
|
||||
"constant",
|
||||
"default_if_none",
|
||||
"extract_field",
|
||||
"extract_text_content",
|
||||
"filter_items",
|
||||
"filter_items_present",
|
||||
"merge_maps",
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from wf_authoring.builder import WorkflowBuilder
|
||||
from wf_authoring.dsl import input_from, input_path, input_value, output_to, state_path
|
||||
from wf_authoring.nodes import NodeSpec, build_registry
|
||||
from wf_authoring.ops import concat, extract_field, filter_items
|
||||
from wf_authoring.subgraph import subgraph_node
|
||||
from wf_core import END
|
||||
|
||||
|
||||
class ExtractTextContentInput(BaseModel):
|
||||
"""Input for extracting text from generic content-block dictionaries."""
|
||||
|
||||
content: list[dict[str, Any]]
|
||||
separator: str = ""
|
||||
|
||||
|
||||
class ExtractTextContentState(BaseModel):
|
||||
"""Internal recipe state for staged content extraction."""
|
||||
|
||||
text_items: list[dict[str, Any]] = []
|
||||
texts: list[str] = []
|
||||
text: str = ""
|
||||
|
||||
|
||||
class ExtractTextContentOutput(BaseModel):
|
||||
"""Output text joined from all text content blocks."""
|
||||
|
||||
text: str
|
||||
|
||||
|
||||
def build_extract_text_content_workflow():
|
||||
"""Build the first-party text-content extraction recipe workflow.
|
||||
|
||||
Current limitation: this recipe is exposed as a wrapper-node capability via
|
||||
`subgraph_node`, not as a native `SubgraphNode`. Callers can use it like any
|
||||
other NodeSpec, but parent traces see one node call and child frames,
|
||||
interrupts, and step-level diagnostics are not promoted to the parent run.
|
||||
"""
|
||||
builder = WorkflowBuilder(
|
||||
name="extract_text_content",
|
||||
input_schema=ExtractTextContentInput,
|
||||
state_schema=ExtractTextContentState,
|
||||
output_schema=ExtractTextContentOutput,
|
||||
)
|
||||
filter_text = builder.use(
|
||||
filter_items,
|
||||
id="filter_text",
|
||||
input=[
|
||||
input_from(input_path("content"), "items"),
|
||||
input_value("key", "type"),
|
||||
input_value("value", "text"),
|
||||
],
|
||||
output=[output_to("items", state_path("text_items"))],
|
||||
)
|
||||
extract_text = builder.use(
|
||||
extract_field,
|
||||
id="extract_text",
|
||||
input=[
|
||||
input_from(state_path("text_items"), "items"),
|
||||
input_value("field", "text"),
|
||||
],
|
||||
output=[output_to("values", state_path("texts"))],
|
||||
)
|
||||
join_text = builder.use(
|
||||
concat,
|
||||
id="join_text",
|
||||
input=[
|
||||
input_from(state_path("texts"), "items"),
|
||||
input_from(input_path("separator"), "separator"),
|
||||
],
|
||||
output=[output_to("text", state_path("text"))],
|
||||
)
|
||||
builder.set_entry_point(filter_text)
|
||||
builder.connect(filter_text, "ok", extract_text)
|
||||
builder.connect(extract_text, "ok", join_text)
|
||||
builder.connect(join_text, "ok", END)
|
||||
return builder.compile()
|
||||
|
||||
|
||||
def build_extract_text_content_spec() -> NodeSpec[
|
||||
ExtractTextContentInput, ExtractTextContentOutput
|
||||
]:
|
||||
"""Return the recipe as a normal NodeSpec for first-party capability sources."""
|
||||
workflow = build_extract_text_content_workflow()
|
||||
return subgraph_node(
|
||||
name="authoring.extract_text_content",
|
||||
workflow=workflow,
|
||||
registry=build_registry(filter_items, extract_field, concat),
|
||||
input_model=ExtractTextContentInput,
|
||||
output_model=ExtractTextContentOutput,
|
||||
description=(
|
||||
"Extract text fields from content blocks with type='text' and join "
|
||||
"them using separator."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
extract_text_content = build_extract_text_content_spec()
|
||||
"""First-party recipe composed from generic sequence/value ops."""
|
||||
Reference in New Issue
Block a user