agstack 1.22.1__tar.gz → 1.24.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agstack-1.22.1 → agstack-1.24.0}/PKG-INFO +1 -1
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/flow.py +63 -7
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/__init__.py +2 -0
- agstack-1.24.0/agstack/llm/flow/nodes/iterator_node.py +160 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack.egg-info/PKG-INFO +1 -1
- {agstack-1.22.1 → agstack-1.24.0}/agstack.egg-info/SOURCES.txt +2 -0
- {agstack-1.22.1 → agstack-1.24.0}/pyproject.toml +1 -1
- agstack-1.24.0/tests/test_flow_iterator.py +769 -0
- {agstack-1.22.1 → agstack-1.24.0}/LICENSE +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/README.md +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/cache/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/cache/base.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/cache/memory.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/cache/redis.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/config/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/config/logger.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/config/manager.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/config/types.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/contexts.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/decorators.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/events.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/exceptions.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/fastapi/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/fastapi/exception.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/fastapi/middleware.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/fastapi/offline.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/fastapi/sse.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/infra/db/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/infra/es/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/infra/kg/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/infra/mq/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/client.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/agent.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/context.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/event.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/exceptions.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/factory.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/loader.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/agent_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/base.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/detect_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/echo_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/llm_chat_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/llm_embed_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/llm_rerank_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/python_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/subflow_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/switch_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/nodes/tool_node.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/records.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/registry.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/sandbox.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/state.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/tool.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/flow/trace.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/prompts.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/llm/token.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/messagebus/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/messagebus/base.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/messagebus/memory.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/messagebus/redis.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/schema.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/security/__init__.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/security/casbin.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/security/crypt.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack/status.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack.egg-info/dependency_links.txt +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack.egg-info/requires.txt +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/agstack.egg-info/top_level.txt +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/setup.cfg +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/tests/test_cache_memory.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/tests/test_cache_redis.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/tests/test_flow_io.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/tests/test_flow_switch_subflow.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/tests/test_messagebus_memory.py +0 -0
- {agstack-1.22.1 → agstack-1.24.0}/tests/test_messagebus_redis.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agstack
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.24.0
|
|
4
4
|
Summary: Production-ready toolkit for building FastAPI and LLM applications
|
|
5
5
|
Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
6
6
|
Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
@@ -358,8 +358,19 @@ class Flow:
|
|
|
358
358
|
|
|
359
359
|
elif node_type in self._node_handlers:
|
|
360
360
|
handler = self._node_handlers[node_type]
|
|
361
|
-
|
|
361
|
+
try:
|
|
362
|
+
result = await handler.execute(node, context)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
iter_target = self._find_iterator_fallback(current_node_id)
|
|
365
|
+
if iter_target:
|
|
366
|
+
context.set_variable(f"_iter_{iter_target}_error", str(e))
|
|
367
|
+
context.set_output(current_node_id, {"error": str(e)})
|
|
368
|
+
context.set_variable("_prev_node_id", current_node_id)
|
|
369
|
+
current_node_id = iter_target
|
|
370
|
+
continue
|
|
371
|
+
raise
|
|
362
372
|
context.set_output(current_node_id, result)
|
|
373
|
+
context.set_variable("_prev_node_id", current_node_id)
|
|
363
374
|
current_node_id = self._resolve_next_node(current_node_id, context)
|
|
364
375
|
|
|
365
376
|
else:
|
|
@@ -409,6 +420,18 @@ class Flow:
|
|
|
409
420
|
)
|
|
410
421
|
raise NodeExecutionError("UNKNOWN_NODE_TYPE", args={"node_type": node_type})
|
|
411
422
|
|
|
423
|
+
def _find_iterator_fallback(self, source_id: str) -> str | None:
|
|
424
|
+
"""查找从 source_id 出发指向 iterator 节点的 edge target"""
|
|
425
|
+
for edge in self.edges:
|
|
426
|
+
if edge.get("source") == source_id:
|
|
427
|
+
target_id: str | None = edge.get("target")
|
|
428
|
+
if not target_id:
|
|
429
|
+
continue
|
|
430
|
+
target_node = self.get_node_config(target_id)
|
|
431
|
+
if target_node and target_node.get("type") == "iterator":
|
|
432
|
+
return target_id
|
|
433
|
+
return None
|
|
434
|
+
|
|
412
435
|
async def _stream_edge_driven(self, context: "FlowContext") -> AsyncIterator[dict[str, Any]]:
|
|
413
436
|
"""边驱动流式执行"""
|
|
414
437
|
current_node_id: str | None = self.nodes[0]["id"] if self.nodes else None
|
|
@@ -478,9 +501,9 @@ class Flow:
|
|
|
478
501
|
step_evt["_echo"] = False
|
|
479
502
|
yield step_evt
|
|
480
503
|
|
|
481
|
-
|
|
504
|
+
_pqid: str = context.trace._qualify_id(current_node_id)
|
|
482
505
|
|
|
483
|
-
async def _exec_branch(branch_id: str,
|
|
506
|
+
async def _exec_branch(branch_id: str, parent_qid: str) -> None:
|
|
484
507
|
branch_node = self.get_node_config(branch_id)
|
|
485
508
|
if not branch_node:
|
|
486
509
|
return
|
|
@@ -494,7 +517,7 @@ class Flow:
|
|
|
494
517
|
branch_id,
|
|
495
518
|
branch_type,
|
|
496
519
|
inputs=branch_config.get("inputs", {}),
|
|
497
|
-
parent_id=
|
|
520
|
+
parent_id=parent_qid,
|
|
498
521
|
)
|
|
499
522
|
context.current_node = branch_id
|
|
500
523
|
try:
|
|
@@ -505,7 +528,7 @@ class Flow:
|
|
|
505
528
|
context.trace.record_node_end(branch_id, error=str(e))
|
|
506
529
|
raise
|
|
507
530
|
|
|
508
|
-
await asyncio.gather(*[_exec_branch(bid) for bid in branches])
|
|
531
|
+
await asyncio.gather(*[_exec_branch(bid, _pqid) for bid in branches])
|
|
509
532
|
merged: dict[str, Any] = {}
|
|
510
533
|
for bid in branches:
|
|
511
534
|
branch_result = context.outputs.get(bid, {})
|
|
@@ -600,8 +623,32 @@ class Flow:
|
|
|
600
623
|
label=config.get("label"),
|
|
601
624
|
)
|
|
602
625
|
|
|
603
|
-
|
|
604
|
-
|
|
626
|
+
# output_mode: "append" — 保存执行前的历史
|
|
627
|
+
append_mode = config.get("output_mode") == "append"
|
|
628
|
+
_history: list[Any] = []
|
|
629
|
+
if append_mode:
|
|
630
|
+
_prev_output = context.outputs.get(current_node_id)
|
|
631
|
+
_history = list(_prev_output) if isinstance(_prev_output, list) else []
|
|
632
|
+
|
|
633
|
+
try:
|
|
634
|
+
async for evt in self._execute_node_with_retry(node, context, current_node_id):
|
|
635
|
+
yield evt
|
|
636
|
+
except (NodeExecutionError, Exception) as e:
|
|
637
|
+
# 迭代体错误容忍:如果该节点有 edge 指向 iterator,路由回去而非中断 flow
|
|
638
|
+
iter_target = self._find_iterator_fallback(current_node_id)
|
|
639
|
+
if iter_target:
|
|
640
|
+
context.set_variable(f"_iter_{iter_target}_error", str(e))
|
|
641
|
+
context.set_output(current_node_id, {"error": str(e)})
|
|
642
|
+
context.trace.record_node_end(current_node_id, error=str(e))
|
|
643
|
+
context.set_variable("_prev_node_id", current_node_id)
|
|
644
|
+
current_node_id = iter_target
|
|
645
|
+
continue
|
|
646
|
+
raise
|
|
647
|
+
|
|
648
|
+
# output_mode: "append" — 合并新输出到历史数组
|
|
649
|
+
if append_mode:
|
|
650
|
+
_history.append(context.outputs.get(current_node_id))
|
|
651
|
+
context.outputs[current_node_id] = _history
|
|
605
652
|
|
|
606
653
|
# 收集 agent 节点存放的 tool_calls 或通用 execution_records
|
|
607
654
|
tool_calls = context.get_variable("_last_node_tool_calls")
|
|
@@ -622,6 +669,15 @@ class Flow:
|
|
|
622
669
|
tool_calls=tool_calls if tool_calls else None,
|
|
623
670
|
messages=messages,
|
|
624
671
|
)
|
|
672
|
+
|
|
673
|
+
# Iterator state cleanup: done 后清理 state 以支持 external re-entry
|
|
674
|
+
if node_type == "iterator":
|
|
675
|
+
_iter_out = context.outputs.get(current_node_id, {})
|
|
676
|
+
if isinstance(_iter_out, dict) and _iter_out.get("done"):
|
|
677
|
+
if not config.get("preserve_state", False):
|
|
678
|
+
context.set_variable(f"_iter_{current_node_id}", None)
|
|
679
|
+
|
|
680
|
+
context.set_variable("_prev_node_id", current_node_id)
|
|
625
681
|
current_node_id = self._resolve_next_node(current_node_id, context)
|
|
626
682
|
|
|
627
683
|
else:
|
|
@@ -6,6 +6,7 @@ from .agent_node import AgentNodeHandler
|
|
|
6
6
|
from .base import NodeHandler
|
|
7
7
|
from .detect_node import DetectNodeHandler
|
|
8
8
|
from .echo_node import EchoNodeHandler
|
|
9
|
+
from .iterator_node import IteratorNodeHandler
|
|
9
10
|
from .llm_chat_node import LLMChatNodeHandler
|
|
10
11
|
from .llm_embed_node import LLMEmbedNodeHandler
|
|
11
12
|
from .llm_rerank_node import LLMRerankNodeHandler
|
|
@@ -27,6 +28,7 @@ builtin_handlers: list[NodeHandler] = [
|
|
|
27
28
|
EchoNodeHandler(),
|
|
28
29
|
SwitchNodeHandler(),
|
|
29
30
|
SubflowNodeHandler(),
|
|
31
|
+
IteratorNodeHandler(),
|
|
30
32
|
]
|
|
31
33
|
|
|
32
34
|
__all__ = [
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Copyright (c) 2020-2026 XtraVisions, All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Iterator 节点处理器 — 通用迭代原语,管理遍历状态并通过 edge 组合实现路由"""
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
from .. import event
|
|
9
|
+
from .base import NodeHandler
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ..context import FlowContext
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IteratorNodeHandler(NodeHandler):
|
|
17
|
+
"""Iterator 节点:管理数组遍历状态,暴露当前元素供下游引用。
|
|
18
|
+
|
|
19
|
+
通过 edge-driven 执行被多次访问:
|
|
20
|
+
- 首次访问:初始化迭代,暴露第一个元素,走迭代体 edge
|
|
21
|
+
- Cycle-back:推进 index,继续或走 completion edge
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
node_type = "iterator"
|
|
25
|
+
|
|
26
|
+
def _resolve_items(self, config: dict, context: "FlowContext") -> list:
|
|
27
|
+
items_ref = config.get("items", "")
|
|
28
|
+
items = context.resolve_reference(items_ref) if isinstance(items_ref, str) else items_ref
|
|
29
|
+
if items is None:
|
|
30
|
+
return []
|
|
31
|
+
if not isinstance(items, list):
|
|
32
|
+
return [items]
|
|
33
|
+
return items
|
|
34
|
+
|
|
35
|
+
def _set_item_output(self, node_id: str, state: dict, config: dict, context: "FlowContext") -> None:
|
|
36
|
+
idx = state["index"]
|
|
37
|
+
context.set_output(
|
|
38
|
+
node_id,
|
|
39
|
+
{
|
|
40
|
+
"current_item": state["items"][idx],
|
|
41
|
+
"index": idx,
|
|
42
|
+
"count": len(state["items"]),
|
|
43
|
+
"done": False,
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def _set_done_output(self, node_id: str, state: dict, config: dict, context: "FlowContext") -> None:
|
|
48
|
+
context.set_output(
|
|
49
|
+
node_id,
|
|
50
|
+
{
|
|
51
|
+
"done": True,
|
|
52
|
+
"count": len(state["items"]),
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def _build_event(
|
|
57
|
+
self, config: dict, event_key: str, state: dict, *, error: str | None = None
|
|
58
|
+
) -> dict[str, Any] | None:
|
|
59
|
+
events_config = config.get("events")
|
|
60
|
+
if not events_config:
|
|
61
|
+
return None
|
|
62
|
+
evt_cfg = events_config.get(event_key)
|
|
63
|
+
if not evt_cfg:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
name = evt_cfg.get("name", event_key)
|
|
67
|
+
value_template = evt_cfg.get("value", {})
|
|
68
|
+
value = self._interpolate_event_value(value_template, state, error=error)
|
|
69
|
+
return event.custom(name=name, value=value)
|
|
70
|
+
|
|
71
|
+
def _interpolate_event_value(self, template: Any, state: dict, *, error: str | None = None) -> Any:
|
|
72
|
+
if isinstance(template, str):
|
|
73
|
+
if template == "$items":
|
|
74
|
+
return state["items"]
|
|
75
|
+
if template == "$index":
|
|
76
|
+
return state["index"]
|
|
77
|
+
if template == "$item":
|
|
78
|
+
return state["items"][state["index"]] if state["index"] < len(state["items"]) else None
|
|
79
|
+
if template == "$count":
|
|
80
|
+
return len(state["items"])
|
|
81
|
+
if template == "$error":
|
|
82
|
+
return error
|
|
83
|
+
return template
|
|
84
|
+
if isinstance(template, dict):
|
|
85
|
+
return {k: self._interpolate_event_value(v, state, error=error) for k, v in template.items()}
|
|
86
|
+
if isinstance(template, list):
|
|
87
|
+
return [self._interpolate_event_value(v, state, error=error) for v in template]
|
|
88
|
+
return template
|
|
89
|
+
|
|
90
|
+
async def execute(self, node: dict, context: "FlowContext") -> Any:
|
|
91
|
+
config = node.get("config", {})
|
|
92
|
+
items = self._resolve_items(config, context)
|
|
93
|
+
return {"done": True, "count": len(items), "current_item": None, "index": 0}
|
|
94
|
+
|
|
95
|
+
async def stream(self, node: dict, context: "FlowContext", node_id: str) -> AsyncIterator[dict[str, Any]]:
|
|
96
|
+
config = node.get("config", {})
|
|
97
|
+
state_key = f"_iter_{node_id}"
|
|
98
|
+
step_name = self.get_step_name(node, node_id)
|
|
99
|
+
|
|
100
|
+
state = context.get_variable(state_key)
|
|
101
|
+
|
|
102
|
+
if state is None:
|
|
103
|
+
# ═══ 首次访问(或 re-entry 后重新初始化)═══
|
|
104
|
+
items = self._resolve_items(config, context)
|
|
105
|
+
state = {"items": items, "index": 0}
|
|
106
|
+
context.set_variable(state_key, state)
|
|
107
|
+
|
|
108
|
+
sid = str(uuid4())
|
|
109
|
+
yield event.step_started(step_name=step_name, step_id=sid)
|
|
110
|
+
|
|
111
|
+
evt = self._build_event(config, "on_start", state)
|
|
112
|
+
if evt:
|
|
113
|
+
yield evt
|
|
114
|
+
|
|
115
|
+
if not items:
|
|
116
|
+
self._set_done_output(node_id, state, config, context)
|
|
117
|
+
yield event.step_finished(step_name=step_name, step_id=sid)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
self._set_item_output(node_id, state, config, context)
|
|
121
|
+
evt = self._build_event(config, "on_item_start", state)
|
|
122
|
+
if evt:
|
|
123
|
+
yield evt
|
|
124
|
+
|
|
125
|
+
yield event.step_finished(step_name=step_name, step_id=sid)
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
# ═══ Cycle-back:推进 index ═══
|
|
129
|
+
sid = str(uuid4())
|
|
130
|
+
yield event.step_started(step_name=step_name, step_id=sid)
|
|
131
|
+
|
|
132
|
+
error = context.get_variable(f"_iter_{node_id}_error")
|
|
133
|
+
|
|
134
|
+
if error:
|
|
135
|
+
evt = self._build_event(config, "on_item_error", state, error=error)
|
|
136
|
+
if evt:
|
|
137
|
+
yield evt
|
|
138
|
+
context.set_variable(f"_iter_{node_id}_error", None)
|
|
139
|
+
else:
|
|
140
|
+
evt = self._build_event(config, "on_item_end", state)
|
|
141
|
+
if evt:
|
|
142
|
+
yield evt
|
|
143
|
+
|
|
144
|
+
state["index"] += 1
|
|
145
|
+
max_iter = config.get("max_iterations")
|
|
146
|
+
|
|
147
|
+
if state["index"] >= len(state["items"]) or (max_iter and state["index"] >= max_iter):
|
|
148
|
+
self._set_done_output(node_id, state, config, context)
|
|
149
|
+
yield event.step_finished(step_name=step_name, step_id=sid)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
self._set_item_output(node_id, state, config, context)
|
|
153
|
+
evt = self._build_event(config, "on_item_start", state)
|
|
154
|
+
if evt:
|
|
155
|
+
yield evt
|
|
156
|
+
|
|
157
|
+
yield event.step_finished(step_name=step_name, step_id=sid)
|
|
158
|
+
|
|
159
|
+
def get_step_name(self, node: dict, node_id: str) -> str:
|
|
160
|
+
return f"iterator:{node_id}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agstack
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.24.0
|
|
4
4
|
Summary: Production-ready toolkit for building FastAPI and LLM applications
|
|
5
5
|
Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
6
6
|
Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
@@ -53,6 +53,7 @@ agstack/llm/flow/nodes/agent_node.py
|
|
|
53
53
|
agstack/llm/flow/nodes/base.py
|
|
54
54
|
agstack/llm/flow/nodes/detect_node.py
|
|
55
55
|
agstack/llm/flow/nodes/echo_node.py
|
|
56
|
+
agstack/llm/flow/nodes/iterator_node.py
|
|
56
57
|
agstack/llm/flow/nodes/llm_chat_node.py
|
|
57
58
|
agstack/llm/flow/nodes/llm_embed_node.py
|
|
58
59
|
agstack/llm/flow/nodes/llm_rerank_node.py
|
|
@@ -70,6 +71,7 @@ agstack/security/crypt.py
|
|
|
70
71
|
tests/test_cache_memory.py
|
|
71
72
|
tests/test_cache_redis.py
|
|
72
73
|
tests/test_flow_io.py
|
|
74
|
+
tests/test_flow_iterator.py
|
|
73
75
|
tests/test_flow_switch_subflow.py
|
|
74
76
|
tests/test_messagebus_memory.py
|
|
75
77
|
tests/test_messagebus_redis.py
|