licos-agent-runtime 0.2.6__tar.gz → 0.2.7__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.
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/PKG-INFO +1 -1
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/pyproject.toml +1 -1
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/graph_loader.py +18 -11
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/service.py +77 -16
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/workflow_canvas.py +2 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_graph_loader.py +60 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_workflow_protocol.py +106 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/.gitignore +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/__init__.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/__main__.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/agent_canvas.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/agent_config.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/app.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/attachments.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/context.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/errors.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/logging.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/openai.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/parser.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/payload.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/run_config.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/stream_protocol.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/stream_runner.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/telemetry.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/workflow_protocol.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_agent_config.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_context.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_parser.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_payload.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_service_stream_protocol.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_stream_protocol.py +0 -0
- {licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_stream_runner.py +0 -0
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/graph_loader.py
RENAMED
|
@@ -5,7 +5,7 @@ import inspect
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, get_type_hints
|
|
9
9
|
|
|
10
10
|
from .context import Context
|
|
11
11
|
|
|
@@ -217,7 +217,7 @@ def _node_data(node: Any) -> Any:
|
|
|
217
217
|
for attr in ("data", "func", "runnable", "node", "callable"):
|
|
218
218
|
try:
|
|
219
219
|
value = inspect.getattr_static(node, attr)
|
|
220
|
-
if isinstance(value, property):
|
|
220
|
+
if isinstance(value, property) or type(value).__name__ == "_tuplegetter":
|
|
221
221
|
value = getattr(node, attr, None)
|
|
222
222
|
except AttributeError:
|
|
223
223
|
value = getattr(node, attr, None)
|
|
@@ -229,14 +229,16 @@ def _node_data(node: Any) -> Any:
|
|
|
229
229
|
def _resolve_callable(value: Any) -> Any | None:
|
|
230
230
|
if value is None:
|
|
231
231
|
return None
|
|
232
|
-
if callable(value) or hasattr(value, "invoke") or hasattr(value, "ainvoke"):
|
|
233
|
-
return value
|
|
234
232
|
for attr in ("func", "afunc", "bound", "runnable", "node", "callable"):
|
|
235
233
|
nested = getattr(value, attr, None)
|
|
234
|
+
if inspect.ismethod(nested) and getattr(nested, "__self__", None) is value:
|
|
235
|
+
nested = nested.__func__
|
|
236
236
|
if nested is not None and nested is not value:
|
|
237
237
|
resolved = _resolve_callable(nested)
|
|
238
238
|
if resolved is not None:
|
|
239
239
|
return resolved
|
|
240
|
+
if callable(value) or hasattr(value, "invoke") or hasattr(value, "ainvoke"):
|
|
241
|
+
return value
|
|
240
242
|
return None
|
|
241
243
|
|
|
242
244
|
|
|
@@ -257,18 +259,23 @@ def _schema_from_signature(value: Any, kind: str) -> Any | None:
|
|
|
257
259
|
signature = inspect.signature(value)
|
|
258
260
|
except (TypeError, ValueError):
|
|
259
261
|
return None
|
|
262
|
+
try:
|
|
263
|
+
type_hints = get_type_hints(value)
|
|
264
|
+
except Exception:
|
|
265
|
+
type_hints = {}
|
|
260
266
|
|
|
261
267
|
if kind == "input":
|
|
262
268
|
for parameter in signature.parameters.values():
|
|
263
|
-
if parameter.name in {"self", "config", "context", "ctx"}:
|
|
269
|
+
if parameter.name in {"self", "config", "context", "ctx", "runtime"}:
|
|
264
270
|
continue
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
annotation = type_hints.get(parameter.name, parameter.annotation)
|
|
272
|
+
if annotation is not inspect.Signature.empty and not isinstance(annotation, str):
|
|
273
|
+
return annotation
|
|
267
274
|
return None
|
|
268
275
|
return None
|
|
269
276
|
|
|
270
|
-
annotation = signature.return_annotation
|
|
271
|
-
if annotation is inspect.Signature.empty:
|
|
277
|
+
annotation = type_hints.get("return", signature.return_annotation)
|
|
278
|
+
if annotation is inspect.Signature.empty or isinstance(annotation, str):
|
|
272
279
|
return None
|
|
273
280
|
return annotation
|
|
274
281
|
|
|
@@ -315,6 +322,6 @@ def get_graph_node_func_with_inout(graph: Any, node_id: str) -> tuple[Any | None
|
|
|
315
322
|
|
|
316
323
|
data = _node_data(node)
|
|
317
324
|
func = _resolve_callable(data)
|
|
318
|
-
input_cls = _schema_from(node, "input") or _schema_from(
|
|
319
|
-
output_cls = _schema_from(node, "output") or _schema_from(
|
|
325
|
+
input_cls = _schema_from(node, "input") or _schema_from(func, "input") or _schema_from(data, "input")
|
|
326
|
+
output_cls = _schema_from(node, "output") or _schema_from(func, "output") or _schema_from(data, "output")
|
|
320
327
|
return func, input_cls, output_cls
|
|
@@ -109,6 +109,80 @@ def _select_supported_kwargs(method: Any, kwargs_list: list[dict[str, Any]]) ->
|
|
|
109
109
|
return {}
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
def _runtime_for_context(ctx: Context) -> Any:
|
|
113
|
+
try:
|
|
114
|
+
from langgraph.runtime import Runtime
|
|
115
|
+
except Exception:
|
|
116
|
+
return None
|
|
117
|
+
return Runtime(context=ctx)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _coerce_node_payload(payload: dict[str, Any], input_cls: Any | None) -> Any:
|
|
121
|
+
if input_cls is None or input_cls is dict or input_cls is Any:
|
|
122
|
+
return payload
|
|
123
|
+
if not isinstance(payload, dict):
|
|
124
|
+
return payload
|
|
125
|
+
|
|
126
|
+
validate = getattr(input_cls, "model_validate", None)
|
|
127
|
+
if callable(validate):
|
|
128
|
+
return validate(payload)
|
|
129
|
+
|
|
130
|
+
if inspect.isclass(input_cls):
|
|
131
|
+
try:
|
|
132
|
+
return input_cls(**payload)
|
|
133
|
+
except Exception:
|
|
134
|
+
return payload
|
|
135
|
+
|
|
136
|
+
return payload
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _normalize_node_result(result: Any) -> Any:
|
|
140
|
+
dump = getattr(result, "model_dump", None)
|
|
141
|
+
if callable(dump):
|
|
142
|
+
return dump()
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def _call_node_callable(func: Any, payload: Any, run_config: dict[str, Any], ctx: Context) -> Any:
|
|
147
|
+
if hasattr(func, "ainvoke") and callable(func.ainvoke):
|
|
148
|
+
kwargs = _select_supported_kwargs(
|
|
149
|
+
func.ainvoke,
|
|
150
|
+
[
|
|
151
|
+
{"config": run_config, "context": ctx},
|
|
152
|
+
{"config": run_config},
|
|
153
|
+
],
|
|
154
|
+
)
|
|
155
|
+
return _normalize_node_result(await func.ainvoke(payload, **kwargs))
|
|
156
|
+
|
|
157
|
+
if hasattr(func, "invoke") and callable(func.invoke):
|
|
158
|
+
kwargs = _select_supported_kwargs(
|
|
159
|
+
func.invoke,
|
|
160
|
+
[
|
|
161
|
+
{"config": run_config, "context": ctx},
|
|
162
|
+
{"config": run_config},
|
|
163
|
+
],
|
|
164
|
+
)
|
|
165
|
+
return _normalize_node_result(func.invoke(payload, **kwargs))
|
|
166
|
+
|
|
167
|
+
if not callable(func):
|
|
168
|
+
raise TypeError("node is not callable")
|
|
169
|
+
|
|
170
|
+
runtime = _runtime_for_context(ctx)
|
|
171
|
+
kwargs = _select_supported_kwargs(
|
|
172
|
+
func,
|
|
173
|
+
[
|
|
174
|
+
{"config": run_config, "runtime": runtime, "context": ctx},
|
|
175
|
+
{"config": run_config, "runtime": runtime},
|
|
176
|
+
{"config": run_config, "context": ctx},
|
|
177
|
+
{"config": run_config},
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
result = func(payload, **kwargs)
|
|
181
|
+
if inspect.isawaitable(result):
|
|
182
|
+
result = await result
|
|
183
|
+
return _normalize_node_result(result)
|
|
184
|
+
|
|
185
|
+
|
|
112
186
|
class GraphService:
|
|
113
187
|
def __init__(
|
|
114
188
|
self,
|
|
@@ -501,22 +575,9 @@ class GraphService:
|
|
|
501
575
|
node_func, input_cls, output_cls = get_graph_node_func_with_inout(raw_graph, node_id)
|
|
502
576
|
if node_func is None:
|
|
503
577
|
raise KeyError(f"node_id '{node_id}' not found")
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
from langgraph.graph import END, StateGraph
|
|
508
|
-
|
|
509
|
-
parser = LangGraphParser(graph)
|
|
510
|
-
metadata = parser.get_node_metadata(node_id) or {}
|
|
511
|
-
state_graph_kwargs: dict[str, Any] = {"input_schema": input_cls}
|
|
512
|
-
if output_cls is not None:
|
|
513
|
-
state_graph_kwargs["output_schema"] = output_cls
|
|
514
|
-
single_node_graph = StateGraph(input_cls, **state_graph_kwargs)
|
|
515
|
-
single_node_graph.add_node("sn", node_func, metadata=metadata)
|
|
516
|
-
single_node_graph.set_entry_point("sn")
|
|
517
|
-
single_node_graph.add_edge("sn", END)
|
|
518
|
-
compiled = single_node_graph.compile()
|
|
519
|
-
return await _call_ainvoke(compiled, payload, init_run_config(compiled, ctx), ctx)
|
|
578
|
+
del output_cls
|
|
579
|
+
node_payload = _coerce_node_payload(payload, input_cls)
|
|
580
|
+
return await _call_node_callable(node_func, node_payload, init_run_config(graph, ctx), ctx)
|
|
520
581
|
|
|
521
582
|
def graph_inout_schema(self) -> dict[str, Any]:
|
|
522
583
|
graph = self._get_graph()
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/workflow_canvas.py
RENAMED
|
@@ -158,6 +158,8 @@ def build_query_text(before_nodes: list[dict[str, Any]], diff: dict[str, Any]) -
|
|
|
158
158
|
"2) 阅读 summary / instructions / stats,快速理解总体变更。\n"
|
|
159
159
|
"3) 新增/删除节点:查看 adds/removes 中的 tree(带层级缩进)和 after/before(完整嵌套结构)了解节点详情,node 了解节点基本信息,container/prev_id/next_id 了解位置。\n"
|
|
160
160
|
"4) 字段更新/约束/类型/顺序调整:查看 updates/reorders。\n\n"
|
|
161
|
+
"硬性要求:每个节点都必须保留 after.type / node.type 中的画布节点类型,并同步写入 graph.py 的 add_node metadata.type 与 AGENTS.md 节点清单;"
|
|
162
|
+
"例如 type=parallel 必须保持为 parallel,不能改写成 action。\n\n"
|
|
161
163
|
"注意:prev_id/next_id 表示节点在流程中的前后顺序位置,不代表图的边连接关系。subtree 为辅助信息,以 tree 和 after/before 的层级结构为准。\n\n"
|
|
162
164
|
"QueryDiff JSON:\n"
|
|
163
165
|
f"{diff_json}"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import tempfile
|
|
3
3
|
import unittest
|
|
4
|
+
from collections import namedtuple
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from unittest.mock import patch
|
|
6
7
|
|
|
@@ -140,6 +141,65 @@ class GraphLoaderTests(unittest.TestCase):
|
|
|
140
141
|
|
|
141
142
|
self.assertIs(func, real_node_name)
|
|
142
143
|
|
|
144
|
+
def test_get_graph_node_func_extracts_namedtuple_node_data(self):
|
|
145
|
+
GraphNode = namedtuple("GraphNode", ["id", "name", "data", "metadata"])
|
|
146
|
+
|
|
147
|
+
def node_func(payload):
|
|
148
|
+
return payload
|
|
149
|
+
|
|
150
|
+
class RawGraph:
|
|
151
|
+
nodes = {"node_a": GraphNode("node_a", "node_a", node_func, {})}
|
|
152
|
+
|
|
153
|
+
func, _input_cls, _output_cls = get_graph_node_func_with_inout(RawGraph(), "node_a")
|
|
154
|
+
|
|
155
|
+
self.assertIs(func, node_func)
|
|
156
|
+
|
|
157
|
+
def test_get_graph_node_func_prefers_wrapped_callable_func(self):
|
|
158
|
+
def node_func(payload):
|
|
159
|
+
return payload
|
|
160
|
+
|
|
161
|
+
class RunnableLike:
|
|
162
|
+
func = node_func
|
|
163
|
+
|
|
164
|
+
def __call__(self, payload):
|
|
165
|
+
return self.func(payload)
|
|
166
|
+
|
|
167
|
+
class Node:
|
|
168
|
+
data = RunnableLike()
|
|
169
|
+
|
|
170
|
+
class RawGraph:
|
|
171
|
+
nodes = {"node_a": Node()}
|
|
172
|
+
|
|
173
|
+
func, _input_cls, _output_cls = get_graph_node_func_with_inout(RawGraph(), "node_a")
|
|
174
|
+
|
|
175
|
+
self.assertIs(func, node_func)
|
|
176
|
+
|
|
177
|
+
def test_get_graph_node_func_resolves_future_annotations(self):
|
|
178
|
+
namespace = {}
|
|
179
|
+
exec(
|
|
180
|
+
"from __future__ import annotations\n"
|
|
181
|
+
"class Input: pass\n"
|
|
182
|
+
"class Output: pass\n"
|
|
183
|
+
"def node_func(state: Input) -> Output:\n"
|
|
184
|
+
" return Output()\n",
|
|
185
|
+
namespace,
|
|
186
|
+
)
|
|
187
|
+
node_func = namespace["node_func"]
|
|
188
|
+
input_cls = namespace["Input"]
|
|
189
|
+
output_cls = namespace["Output"]
|
|
190
|
+
|
|
191
|
+
class Node:
|
|
192
|
+
data = node_func
|
|
193
|
+
|
|
194
|
+
class RawGraph:
|
|
195
|
+
nodes = {"node_a": Node()}
|
|
196
|
+
|
|
197
|
+
func, resolved_input, resolved_output = get_graph_node_func_with_inout(RawGraph(), "node_a")
|
|
198
|
+
|
|
199
|
+
self.assertIs(func, node_func)
|
|
200
|
+
self.assertIs(resolved_input, input_cls)
|
|
201
|
+
self.assertIs(resolved_output, output_cls)
|
|
202
|
+
|
|
143
203
|
def test_call_factory_does_not_swallow_internal_type_error(self):
|
|
144
204
|
def factory(_ctx):
|
|
145
205
|
raise TypeError("internal")
|
|
@@ -94,6 +94,111 @@ class WorkflowProtocolTests(unittest.TestCase):
|
|
|
94
94
|
self.assertIn("#L", nodes[1]["script"]["content"])
|
|
95
95
|
self.assertEqual(nodes[2]["definition"]["inputs"]["properties"]["answer"]["type"], "string")
|
|
96
96
|
|
|
97
|
+
def test_run_node_executes_node_callable_directly(self):
|
|
98
|
+
def generate_answer(state):
|
|
99
|
+
return {"answer": state.get("topic", "")}
|
|
100
|
+
|
|
101
|
+
class Node:
|
|
102
|
+
metadata = {"title": "生成结果", "type": "action"}
|
|
103
|
+
data = generate_answer
|
|
104
|
+
|
|
105
|
+
class RawGraph:
|
|
106
|
+
nodes = {"generate_answer": Node()}
|
|
107
|
+
|
|
108
|
+
class Graph:
|
|
109
|
+
def get_graph(self):
|
|
110
|
+
return RawGraph()
|
|
111
|
+
|
|
112
|
+
class Service(GraphService):
|
|
113
|
+
def _get_graph(self, ctx=None):
|
|
114
|
+
del ctx
|
|
115
|
+
return Graph()
|
|
116
|
+
|
|
117
|
+
result = asyncio.run(
|
|
118
|
+
Service().run_node("generate_answer", {"topic": "单节点测试"}, new_context("test_node_run"))
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self.assertEqual(result, {"answer": "单节点测试"})
|
|
122
|
+
|
|
123
|
+
def test_run_node_passes_config_and_runtime_when_supported(self):
|
|
124
|
+
runtime_marker = object()
|
|
125
|
+
|
|
126
|
+
def generate_answer(state, config, runtime):
|
|
127
|
+
return {
|
|
128
|
+
"answer": state.get("topic", ""),
|
|
129
|
+
"has_config": "configurable" in config,
|
|
130
|
+
"has_runtime": runtime is runtime_marker,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class Node:
|
|
134
|
+
metadata = {"title": "生成结果", "type": "action"}
|
|
135
|
+
data = generate_answer
|
|
136
|
+
|
|
137
|
+
class RawGraph:
|
|
138
|
+
nodes = {"generate_answer": Node()}
|
|
139
|
+
|
|
140
|
+
class Graph:
|
|
141
|
+
def get_graph(self):
|
|
142
|
+
return RawGraph()
|
|
143
|
+
|
|
144
|
+
class Service(GraphService):
|
|
145
|
+
def _get_graph(self, ctx=None):
|
|
146
|
+
del ctx
|
|
147
|
+
return Graph()
|
|
148
|
+
|
|
149
|
+
with patch("licos_agent_runtime.service._runtime_for_context", return_value=runtime_marker):
|
|
150
|
+
result = asyncio.run(
|
|
151
|
+
Service().run_node("generate_answer", {"topic": "单节点测试"}, new_context("test_node_run"))
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
self.assertEqual(result["answer"], "单节点测试")
|
|
155
|
+
self.assertTrue(result["has_config"])
|
|
156
|
+
self.assertTrue(result["has_runtime"])
|
|
157
|
+
|
|
158
|
+
def test_run_node_coerces_payload_to_node_input_schema(self):
|
|
159
|
+
class Input:
|
|
160
|
+
def __init__(self, topic):
|
|
161
|
+
self.topic = topic
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def model_validate(cls, payload):
|
|
165
|
+
return cls(payload["topic"])
|
|
166
|
+
|
|
167
|
+
class Output:
|
|
168
|
+
def __init__(self, answer):
|
|
169
|
+
self.answer = answer
|
|
170
|
+
|
|
171
|
+
def model_dump(self):
|
|
172
|
+
return {"answer": self.answer}
|
|
173
|
+
|
|
174
|
+
def generate_answer(state):
|
|
175
|
+
return Output(state.topic)
|
|
176
|
+
|
|
177
|
+
generate_answer.input_schema = Input
|
|
178
|
+
generate_answer.output_schema = Output
|
|
179
|
+
|
|
180
|
+
class Node:
|
|
181
|
+
metadata = {"title": "生成结果", "type": "action"}
|
|
182
|
+
data = generate_answer
|
|
183
|
+
|
|
184
|
+
class RawGraph:
|
|
185
|
+
nodes = {"generate_answer": Node()}
|
|
186
|
+
|
|
187
|
+
class Graph:
|
|
188
|
+
def get_graph(self):
|
|
189
|
+
return RawGraph()
|
|
190
|
+
|
|
191
|
+
class Service(GraphService):
|
|
192
|
+
def _get_graph(self, ctx=None):
|
|
193
|
+
del ctx
|
|
194
|
+
return Graph()
|
|
195
|
+
|
|
196
|
+
result = asyncio.run(
|
|
197
|
+
Service().run_node("generate_answer", {"topic": "单节点测试"}, new_context("test_node_run"))
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
self.assertEqual(result, {"answer": "单节点测试"})
|
|
201
|
+
|
|
97
202
|
def test_canvas_submit_builds_query_diff_for_agent_execution(self):
|
|
98
203
|
before = {
|
|
99
204
|
"nodes": [
|
|
@@ -147,6 +252,7 @@ class WorkflowProtocolTests(unittest.TestCase):
|
|
|
147
252
|
self.assertEqual(result["msg"], "")
|
|
148
253
|
self.assertIn("当前流程结构", result["query"])
|
|
149
254
|
self.assertIn("QueryDiff JSON", result["query"])
|
|
255
|
+
self.assertIn("必须保留 after.type", result["query"])
|
|
150
256
|
self.assertEqual(result["display_text"], "2 个节点变更")
|
|
151
257
|
self.assertEqual(result["draft_text"], "2 个节点已编辑")
|
|
152
258
|
self.assertEqual(result["reference"]["type"], "workflow_canvas_edit")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/agent_canvas.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/agent_config.py
RENAMED
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/attachments.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/run_config.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/stream_protocol.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/stream_runner.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/telemetry.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/src/licos_agent_runtime/workflow_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.2.6 → licos_agent_runtime-0.2.7}/tests/test_service_stream_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|