agstack 1.5.0__tar.gz → 1.7.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.
Files changed (61) hide show
  1. {agstack-1.5.0 → agstack-1.7.0}/PKG-INFO +1 -1
  2. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/__init__.py +4 -0
  3. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/agent.py +16 -11
  4. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/context.py +23 -25
  5. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/flow.py +123 -225
  6. agstack-1.7.0/agstack/llm/flow/nodes/__init__.py +39 -0
  7. agstack-1.7.0/agstack/llm/flow/nodes/agent_node.py +48 -0
  8. agstack-1.7.0/agstack/llm/flow/nodes/base.py +51 -0
  9. agstack-1.7.0/agstack/llm/flow/nodes/detect_node.py +93 -0
  10. agstack-1.7.0/agstack/llm/flow/nodes/llm_chat_node.py +152 -0
  11. agstack-1.7.0/agstack/llm/flow/nodes/llm_embed_node.py +38 -0
  12. agstack-1.7.0/agstack/llm/flow/nodes/llm_rerank_node.py +48 -0
  13. agstack-1.5.0/agstack/llm/flow/sandbox.py → agstack-1.7.0/agstack/llm/flow/nodes/python_node.py +42 -6
  14. agstack-1.7.0/agstack/llm/flow/nodes/tool_node.py +34 -0
  15. agstack-1.7.0/agstack/llm/flow/sandbox.py +8 -0
  16. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/tool.py +9 -10
  17. {agstack-1.5.0 → agstack-1.7.0}/agstack.egg-info/PKG-INFO +1 -1
  18. {agstack-1.5.0 → agstack-1.7.0}/agstack.egg-info/SOURCES.txt +11 -1
  19. {agstack-1.5.0 → agstack-1.7.0}/pyproject.toml +2 -1
  20. agstack-1.7.0/tests/test_flow_io.py +435 -0
  21. {agstack-1.5.0 → agstack-1.7.0}/LICENSE +0 -0
  22. {agstack-1.5.0 → agstack-1.7.0}/README.md +0 -0
  23. {agstack-1.5.0 → agstack-1.7.0}/agstack/__init__.py +0 -0
  24. {agstack-1.5.0 → agstack-1.7.0}/agstack/config/__init__.py +0 -0
  25. {agstack-1.5.0 → agstack-1.7.0}/agstack/config/logger.py +0 -0
  26. {agstack-1.5.0 → agstack-1.7.0}/agstack/config/manager.py +0 -0
  27. {agstack-1.5.0 → agstack-1.7.0}/agstack/config/types.py +0 -0
  28. {agstack-1.5.0 → agstack-1.7.0}/agstack/contexts.py +0 -0
  29. {agstack-1.5.0 → agstack-1.7.0}/agstack/decorators.py +0 -0
  30. {agstack-1.5.0 → agstack-1.7.0}/agstack/events.py +0 -0
  31. {agstack-1.5.0 → agstack-1.7.0}/agstack/exceptions.py +0 -0
  32. {agstack-1.5.0 → agstack-1.7.0}/agstack/fastapi/__init__.py +0 -0
  33. {agstack-1.5.0 → agstack-1.7.0}/agstack/fastapi/exception.py +0 -0
  34. {agstack-1.5.0 → agstack-1.7.0}/agstack/fastapi/middleware.py +0 -0
  35. {agstack-1.5.0 → agstack-1.7.0}/agstack/fastapi/offline.py +0 -0
  36. {agstack-1.5.0 → agstack-1.7.0}/agstack/fastapi/sse.py +0 -0
  37. {agstack-1.5.0 → agstack-1.7.0}/agstack/infra/db/__init__.py +0 -0
  38. {agstack-1.5.0 → agstack-1.7.0}/agstack/infra/es/__init__.py +0 -0
  39. {agstack-1.5.0 → agstack-1.7.0}/agstack/infra/kg/__init__.py +0 -0
  40. {agstack-1.5.0 → agstack-1.7.0}/agstack/infra/mq/__init__.py +0 -0
  41. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/__init__.py +0 -0
  42. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/client.py +0 -0
  43. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/event.py +0 -0
  44. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/exceptions.py +0 -0
  45. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/factory.py +0 -0
  46. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/loader.py +0 -0
  47. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/records.py +0 -0
  48. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/registry.py +0 -0
  49. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/flow/state.py +0 -0
  50. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/prompts.py +0 -0
  51. {agstack-1.5.0 → agstack-1.7.0}/agstack/llm/token.py +0 -0
  52. {agstack-1.5.0 → agstack-1.7.0}/agstack/registry.py +0 -0
  53. {agstack-1.5.0 → agstack-1.7.0}/agstack/schema.py +0 -0
  54. {agstack-1.5.0 → agstack-1.7.0}/agstack/security/__init__.py +0 -0
  55. {agstack-1.5.0 → agstack-1.7.0}/agstack/security/casbin.py +0 -0
  56. {agstack-1.5.0 → agstack-1.7.0}/agstack/security/crypt.py +0 -0
  57. {agstack-1.5.0 → agstack-1.7.0}/agstack/status.py +0 -0
  58. {agstack-1.5.0 → agstack-1.7.0}/agstack.egg-info/dependency_links.txt +0 -0
  59. {agstack-1.5.0 → agstack-1.7.0}/agstack.egg-info/requires.txt +0 -0
  60. {agstack-1.5.0 → agstack-1.7.0}/agstack.egg-info/top_level.txt +0 -0
  61. {agstack-1.5.0 → agstack-1.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agstack
3
- Version: 1.5.0
3
+ Version: 1.7.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>
@@ -18,6 +18,7 @@ from .exceptions import (
18
18
  from .factory import create_agent, create_tool
19
19
  from .flow import Flow
20
20
  from .loader import FlowLoader
21
+ from .nodes import NodeHandler, register_node_handler
21
22
  from .records import Record, Status
22
23
  from .registry import registry
23
24
  from .state import FlowState
@@ -32,6 +33,9 @@ __all__ = [
32
33
  "Flow",
33
34
  "FlowContext",
34
35
  "Usage",
36
+ # 节点处理器
37
+ "NodeHandler",
38
+ "register_node_handler",
35
39
  # AG-UI 协议
36
40
  "EventType",
37
41
  "event",
@@ -64,23 +64,29 @@ class Agent:
64
64
  return tool
65
65
  return None
66
66
 
67
- async def run(self, context: "FlowContext") -> str:
67
+ async def run(self, context: "FlowContext", inputs: dict[str, Any] | None = None) -> dict[str, Any]:
68
68
  """执行 Agent 逻辑"""
69
69
  content_parts = []
70
- async for evt in self.stream(context):
70
+ async for evt in self.stream(context, inputs):
71
71
  # AG-UI 事件格式
72
72
  if isinstance(evt, dict):
73
73
  if evt.get("type") == EventType.TEXT_MESSAGE_CONTENT:
74
74
  content_parts.append(evt.get("delta", ""))
75
75
  elif evt.get("type") == EventType.RUN_ERROR:
76
76
  raise FlowError("AGENT_EXECUTION_FAILED", 500, {"error": evt.get("message")})
77
- return "".join(content_parts)
77
+ return {"result": "".join(content_parts)}
78
78
 
79
- async def stream(self, context: "FlowContext") -> AsyncIterator[dict[str, Any]]:
79
+ async def stream(
80
+ self, context: "FlowContext", inputs: dict[str, Any] | None = None
81
+ ) -> AsyncIterator[dict[str, Any]]:
80
82
  """流式执行 Agent,输出 AG-UI 标准事件"""
81
83
 
82
- # 输入来源:优先 input(A2A 传入),回退到 query
83
- user_input = context.get_variable("input") or context.get_variable("query", "")
84
+ # 输入来源:优先 inputs 参数,回退到 context.variables
85
+ user_input = ""
86
+ if inputs:
87
+ user_input = inputs.get("input", "")
88
+ if not user_input:
89
+ user_input = context.get_variable("input") or context.get_variable("query", "")
84
90
  msg_id = context.message_id or str(uuid4())
85
91
 
86
92
  # 添加用户消息(scoped by agent name)
@@ -214,7 +220,7 @@ class Agent:
214
220
  # 如果没有工具调用,结束循环
215
221
  if not tool_calls:
216
222
  # 存储结果供 Flow/A2A 使用
217
- context.set_node_result(self.name, assistant_content)
223
+ context.set_output(self.name, {"result": assistant_content})
218
224
  # AG-UI: TEXT_MESSAGE_END
219
225
  yield event.text_message_end(message_id=msg_id)
220
226
  return
@@ -237,15 +243,14 @@ class Agent:
237
243
  )
238
244
  continue
239
245
 
240
- # 解析 LLM 返回的工具参数并注入 context
246
+ # 解析 LLM 返回的工具参数
241
247
  try:
242
248
  tool_args = json.loads(tool_call["arguments"]) if tool_call["arguments"] else {}
243
249
  except json.JSONDecodeError:
244
250
  tool_args = {}
245
- context.update_variables(tool_args)
246
251
 
247
- # 执行工具
248
- result = await tool.execute_async(context)
252
+ # 执行工具(传入 LLM 解析的参数作为 inputs)
253
+ result = await tool.execute_async(context, tool_args)
249
254
 
250
255
  # 保存工具结果
251
256
  result_content = json.dumps(result.result) if result.success else json.dumps({"error": result.error})
@@ -50,7 +50,7 @@ class FlowContext:
50
50
  message_id: str | None = None
51
51
 
52
52
  # 图执行状态
53
- node_results: dict[str, Any] = field(default_factory=dict)
53
+ outputs: dict[str, Any] = field(default_factory=dict)
54
54
  current_node: str | None = None
55
55
 
56
56
  # 执行记录(可选)
@@ -115,33 +115,31 @@ class FlowContext:
115
115
  self.messages.clear()
116
116
  self.turn_count = 0
117
117
 
118
- def resolve_reference(self, ref: str) -> Any:
119
- """解析变量引用 {node@variable.field} 或 {node_id}"""
120
- if not isinstance(ref, str) or not ref.startswith("{"):
121
- return ref
118
+ def resolve_reference(self, ref: Any) -> Any:
119
+ """解析变量引用
122
120
 
123
- ref_content = ref[1:-1] # 移除 {}
124
- if "@" not in ref_content:
125
- # 先从 variables 查找,回退到 node_results
126
- result = self.variables.get(ref_content)
127
- if result is None:
128
- result = self.node_results.get(ref_content)
121
+ $o.node_id.field.subfield → context.outputs["node_id"]["field"]["subfield"]
122
+ $v.key → context.variables["key"]
123
+ 其他字符串 → 原样返回(字面值)
124
+ """
125
+ if not isinstance(ref, str):
126
+ return ref
127
+ if ref.startswith("$o."):
128
+ parts = ref[3:].split(".")
129
+ result = self.outputs.get(parts[0])
130
+ for part in parts[1:]:
131
+ if isinstance(result, dict):
132
+ result = result.get(part)
133
+ else:
134
+ result = getattr(result, part, None)
129
135
  return result
136
+ if ref.startswith("$v."):
137
+ return self.variables.get(ref[3:])
138
+ return ref
130
139
 
131
- node_id, var_path = ref_content.split("@", 1)
132
- result = self.node_results.get(node_id)
133
-
134
- # 支持嵌套字段访问 variable.field.subfield
135
- for field_name in var_path.split("."):
136
- if isinstance(result, dict):
137
- result = result.get(field_name)
138
- else:
139
- result = getattr(result, field_name, None)
140
- return result
141
-
142
- def set_node_result(self, node_id: str, result: Any):
143
- """设置节点执行结果"""
144
- self.node_results[node_id] = result
140
+ def set_output(self, node_id: str, result: Any):
141
+ """设置节点输出"""
142
+ self.outputs[node_id] = result
145
143
 
146
144
  def add_execution_record(self, task_id: str, status: str, **kwargs) -> None:
147
145
  """添加执行记录"""