ibm-watsonx-orchestrate 1.4.2__py3-none-any.whl → 1.5.0b0__py3-none-any.whl
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.
- ibm_watsonx_orchestrate/__init__.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -0
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +7 -2
- ibm_watsonx_orchestrate/client/models/types.py +13 -1
- ibm_watsonx_orchestrate/client/toolkit/toolkit_client.py +13 -8
- ibm_watsonx_orchestrate/docker/default.env +9 -9
- ibm_watsonx_orchestrate/experimental/flow_builder/data_map.py +19 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/__init__.py +4 -3
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/constants.py +3 -1
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +3 -2
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/flow.py +245 -223
- ibm_watsonx_orchestrate/experimental/flow_builder/node.py +34 -15
- ibm_watsonx_orchestrate/experimental/flow_builder/resources/flow_status.openapi.yml +7 -39
- ibm_watsonx_orchestrate/experimental/flow_builder/types.py +285 -12
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +3 -1
- {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b0.dist-info}/METADATA +1 -1
- {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b0.dist-info}/RECORD +20 -20
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/data_map.py +0 -91
- {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -6,37 +6,36 @@ the Flow model.
|
|
6
6
|
import asyncio
|
7
7
|
from datetime import datetime
|
8
8
|
from enum import Enum
|
9
|
+
import inspect
|
9
10
|
from typing import (
|
10
11
|
Any, AsyncIterator, Callable, cast, List, Sequence, Union, Tuple
|
11
12
|
)
|
12
13
|
import json
|
13
14
|
import logging
|
14
|
-
import time
|
15
15
|
import copy
|
16
16
|
import uuid
|
17
17
|
import pytz
|
18
18
|
|
19
19
|
from typing_extensions import Self
|
20
|
-
from pydantic import BaseModel, Field,
|
20
|
+
from pydantic import BaseModel, Field, SerializeAsAny
|
21
21
|
import yaml
|
22
|
-
from munch import Munch
|
23
22
|
from ibm_watsonx_orchestrate.agent_builder.tools.python_tool import PythonTool
|
24
23
|
from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
|
25
24
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
26
25
|
from ..types import (
|
27
|
-
EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy,
|
28
|
-
StartNodeSpec, ToolSpec, JsonSchemaObject, ToolRequestBody, ToolResponseBody, WaitPolicy
|
26
|
+
EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy, PromptLLMParameters, PromptNodeSpec,
|
27
|
+
StartNodeSpec, ToolSpec, JsonSchemaObject, ToolRequestBody, ToolResponseBody, UserFieldKind, UserFieldOption, UserFlowSpec, UserNodeSpec, WaitPolicy
|
29
28
|
)
|
30
|
-
from .constants import START, END, ANY_USER
|
29
|
+
from .constants import CURRENT_USER, START, END, ANY_USER
|
31
30
|
from ..node import (
|
32
|
-
EndNode, Node, StartNode, UserNode, AgentNode, DataMap, ToolNode
|
31
|
+
EndNode, Node, PromptNode, StartNode, UserNode, AgentNode, DataMap, ToolNode
|
33
32
|
)
|
34
33
|
from ..types import (
|
35
34
|
AgentNodeSpec, extract_node_spec, FlowContext, FlowEventType, FlowEvent, FlowSpec,
|
36
35
|
NodeSpec, TaskEventType, ToolNodeSpec, SchemaRef, JsonSchemaObjectRef, _to_json_from_json_schema
|
37
36
|
)
|
38
37
|
|
39
|
-
from
|
38
|
+
from ..data_map import DataMap
|
40
39
|
from ..utils import _get_json_schema_obj, get_valid_name, import_flow_model
|
41
40
|
|
42
41
|
from .events import StreamConsumer
|
@@ -60,7 +59,6 @@ class FlowEdge(BaseModel):
|
|
60
59
|
start: str
|
61
60
|
end: str
|
62
61
|
|
63
|
-
|
64
62
|
class Flow(Node):
|
65
63
|
'''Flow represents a flow that will be run by wxO Flow engine.'''
|
66
64
|
output_map: DataMap | None = None
|
@@ -71,6 +69,7 @@ class Flow(Node):
|
|
71
69
|
validated: bool = False
|
72
70
|
metadata: dict[str, str] = {}
|
73
71
|
parent: Any = None
|
72
|
+
_sequence_id: int = 0 # internal-id
|
74
73
|
|
75
74
|
def __init__(self, **kwargs):
|
76
75
|
super().__init__(**kwargs)
|
@@ -83,6 +82,10 @@ class Flow(Node):
|
|
83
82
|
return self.parent._find_topmost_flow()
|
84
83
|
return self
|
85
84
|
|
85
|
+
def _next_sequence_id(self) -> int:
|
86
|
+
self._sequence_id += 1
|
87
|
+
return self._sequence_id
|
88
|
+
|
86
89
|
def _add_schema(self, schema: JsonSchemaObject, title: str = None) -> JsonSchemaObject:
|
87
90
|
'''
|
88
91
|
Adds a schema to the dictionary of schemas. If a schema with the same name already exists, it returns the existing schema. Otherwise, it creates a deep copy of the schema, adds it to the dictionary, and returns the new schema.
|
@@ -107,7 +110,7 @@ class Flow(Node):
|
|
107
110
|
if schema:
|
108
111
|
if isinstance(schema, dict):
|
109
112
|
# recast schema to support direct access
|
110
|
-
schema =
|
113
|
+
schema = JsonSchemaObject.model_validate(schema)
|
111
114
|
# we should only add schema when it is a complex object
|
112
115
|
if schema.type != "object" and schema.type != "array":
|
113
116
|
return schema
|
@@ -134,7 +137,7 @@ class Flow(Node):
|
|
134
137
|
schema_ref = self._add_schema_ref(value.items, value.items.title)
|
135
138
|
new_schema.properties[key].items = JsonSchemaObjectRef(title=value.title,
|
136
139
|
ref = f"{schema_ref.ref}")
|
137
|
-
elif value.model_extra and value.model_extra
|
140
|
+
elif value.model_extra and hasattr(value.model_extra, "$ref"):
|
138
141
|
# there is already a reference, remove $/defs/ from the initial ref
|
139
142
|
ref_value = value.model_extra["$ref"]
|
140
143
|
schema_ref = f"#/schemas/{ref_value[8:]}"
|
@@ -254,159 +257,18 @@ class Flow(Node):
|
|
254
257
|
|
255
258
|
input_schema: type[BaseModel] | None = None,
|
256
259
|
output_schema: type[BaseModel] | None = None,
|
257
|
-
input_map:
|
258
|
-
) ->
|
260
|
+
input_map: DataMap = None
|
261
|
+
) -> ToolNode:
|
259
262
|
'''create a tool node in the flow'''
|
260
263
|
if tool is None:
|
261
264
|
raise ValueError("tool must be provided")
|
262
265
|
|
263
|
-
if isinstance(tool, str):
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
display_name=display_name,
|
268
|
-
description=description,
|
269
|
-
input_schema=input_schema,
|
270
|
-
output_schema=output_schema,
|
271
|
-
input_map=input_map)
|
272
|
-
elif isinstance(tool, PythonTool):
|
273
|
-
return self._node(
|
274
|
-
node=tool,
|
275
|
-
name=name if name is not None and name != "" else tool.fn.__name__,
|
276
|
-
display_name=display_name,
|
277
|
-
description=description,
|
278
|
-
input_schema=input_schema,
|
279
|
-
output_schema=output_schema,
|
280
|
-
input_map=input_map)
|
281
|
-
else:
|
282
|
-
raise ValueError(f"tool is not a string or a callable: {tool}")
|
283
|
-
|
284
|
-
def agent(
|
285
|
-
self,
|
286
|
-
name: str | None = None,
|
287
|
-
display_name: str | None = None,
|
288
|
-
description: str | None = None,
|
289
|
-
agent: str | None = None,
|
290
|
-
message: str | None = None,
|
291
|
-
guidelines: str | None = None,
|
292
|
-
input_schema: type[BaseModel] | None = None,
|
293
|
-
output_schema: type[BaseModel] | None = None,
|
294
|
-
input_map: List[Assignment] = None
|
295
|
-
) -> Node:
|
296
|
-
'''create an agent node in the flow'''
|
297
|
-
return self._node(
|
298
|
-
name=name,
|
299
|
-
display_name=display_name,
|
300
|
-
description=description,
|
301
|
-
agent=agent,
|
302
|
-
message=message,
|
303
|
-
guidelines=guidelines,
|
304
|
-
input_schema=input_schema,
|
305
|
-
output_schema=output_schema,
|
306
|
-
input_map=input_map
|
307
|
-
)
|
308
|
-
|
309
|
-
def _node(
|
310
|
-
self,
|
311
|
-
node: Union[Node, Callable] = None,
|
312
|
-
name: str = None,
|
313
|
-
display_name: str | None = None,
|
314
|
-
description: str | None = None,
|
315
|
-
owners: Sequence[str] | None = None,
|
316
|
-
input_schema: type[BaseModel] | None = None,
|
317
|
-
output_schema: type[BaseModel] | None = None,
|
318
|
-
agent: str | None = None,
|
319
|
-
tool: str | None = None,
|
320
|
-
message: str | None = None,
|
321
|
-
guidelines: str | None = None,
|
322
|
-
input_map: Callable | List[Assignment] = None,
|
323
|
-
output_map: Callable | List[Assignment] = None,
|
324
|
-
) -> Node:
|
325
|
-
|
326
|
-
self._check_compiled()
|
327
|
-
|
328
|
-
if owners is None:
|
329
|
-
owners = []
|
330
|
-
|
331
|
-
if node is not None:
|
332
|
-
if not isinstance(node, Node):
|
333
|
-
if callable(node):
|
334
|
-
user_spec = getattr(node, "__user_spec__", None)
|
335
|
-
# script_spec = getattr(node, "__script_spec__", None)
|
336
|
-
tool_spec = getattr(node, "__tool_spec__", None)
|
337
|
-
if user_spec:
|
338
|
-
node = UserNode(spec = user_spec)
|
339
|
-
# elif script_spec:
|
340
|
-
# node = ScriptNode(spec = script_spec)
|
341
|
-
elif tool_spec:
|
342
|
-
node = self._create_node_from_tool_fn(node)
|
343
|
-
else:
|
344
|
-
raise ValueError(
|
345
|
-
"Only functions with @user, @tool or @script decorator can be added.")
|
346
|
-
elif isinstance(node, Node):
|
347
|
-
if node.spec.name in self.nodes:
|
348
|
-
raise ValueError(f"Node `{id}` already present.")
|
349
|
-
|
350
|
-
if node.spec.name == END or node.spec.name == START:
|
351
|
-
raise ValueError(f"Node `{id}` is reserved.")
|
352
|
-
else:
|
353
|
-
raise ValueError(
|
354
|
-
"A valid node or function must be specified for the node parameter.")
|
355
|
-
|
356
|
-
# setup input and output map
|
357
|
-
if input_map:
|
358
|
-
node.input_map = self._get_data_map(input_map)
|
359
|
-
if output_map:
|
360
|
-
node.output_map = self._get_data_map(output_map)
|
361
|
-
|
362
|
-
# add the node to the list of node
|
363
|
-
node = self._add_node(node)
|
364
|
-
return node
|
365
|
-
|
366
|
-
if name is not None:
|
367
|
-
if agent is not None:
|
368
|
-
node = self._create_agent_node(
|
369
|
-
name, agent, display_name, message, description, input_schema, output_schema, guidelines)
|
370
|
-
elif tool is not None:
|
371
|
-
node = self._create_tool_node(
|
372
|
-
name, tool, display_name, description, input_schema, output_schema)
|
373
|
-
else:
|
374
|
-
node = self._create_user_node(
|
375
|
-
name, display_name, description, owners, input_schema, output_schema)
|
376
|
-
|
377
|
-
# setup input and output map
|
378
|
-
if input_map:
|
379
|
-
node.input_map = self._get_data_map(input_map)
|
380
|
-
if output_map:
|
381
|
-
node.output_map = self._get_data_map(output_map)
|
382
|
-
|
383
|
-
# add the node to the list of node
|
384
|
-
node = self._add_node(node)
|
385
|
-
return node
|
386
|
-
|
387
|
-
raise ValueError("Either a node or a name must be specified.")
|
388
|
-
|
389
|
-
def _add_node(self, node: Node) -> Node:
|
390
|
-
# make a copy
|
391
|
-
new_node = copy.copy(node)
|
392
|
-
|
393
|
-
self._refactor_node_to_schemaref(new_node)
|
394
|
-
|
395
|
-
self.nodes[node.spec.name] = new_node
|
396
|
-
return new_node
|
397
|
-
|
398
|
-
|
399
|
-
def _create_tool_node(self, name: str, tool: str,
|
400
|
-
display_name: str|None=None,
|
401
|
-
description: str|None=None,
|
402
|
-
input_schema: type[BaseModel]|None=None,
|
403
|
-
output_schema: type[BaseModel]|None=None) -> Node:
|
404
|
-
|
405
|
-
# create input spec
|
406
|
-
input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = input_schema)
|
407
|
-
output_schema_obj = _get_json_schema_obj("output", output_schema)
|
266
|
+
if isinstance(tool, str):
|
267
|
+
name = name if name is not None and name != "" else tool
|
268
|
+
input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = input_schema)
|
269
|
+
output_schema_obj = _get_json_schema_obj("output", output_schema)
|
408
270
|
|
409
|
-
|
271
|
+
toolnode_spec = ToolNodeSpec(type = "tool",
|
410
272
|
name = name,
|
411
273
|
display_name = display_name,
|
412
274
|
description = description,
|
@@ -423,28 +285,62 @@ class Flow(Node):
|
|
423
285
|
output_schema_object = output_schema_obj,
|
424
286
|
tool = tool)
|
425
287
|
|
426
|
-
|
288
|
+
node = ToolNode(spec=toolnode_spec)
|
289
|
+
elif isinstance(tool, PythonTool):
|
290
|
+
if callable(tool):
|
291
|
+
tool_spec = getattr(tool, "__tool_spec__", None)
|
292
|
+
if tool_spec:
|
293
|
+
node = self._create_node_from_tool_fn(tool)
|
294
|
+
else:
|
295
|
+
raise ValueError("Only functions with @tool decorator can be added.")
|
296
|
+
else:
|
297
|
+
raise ValueError(f"tool is not a string or Callable: {tool}")
|
298
|
+
|
299
|
+
# setup input and output map
|
300
|
+
if input_map:
|
301
|
+
node.input_map = self._get_data_map(input_map)
|
427
302
|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
303
|
+
node = self._add_node(node)
|
304
|
+
return cast(ToolNode, node)
|
305
|
+
|
306
|
+
|
307
|
+
def _add_node(self, node: Node) -> Node:
|
308
|
+
self._check_compiled()
|
309
|
+
|
310
|
+
if node.spec.name in self.nodes:
|
311
|
+
raise ValueError(f"Node `{id}` already present.")
|
312
|
+
|
313
|
+
# make a copy
|
314
|
+
new_node = copy.copy(node)
|
315
|
+
|
316
|
+
self._refactor_node_to_schemaref(new_node)
|
317
|
+
|
318
|
+
self.nodes[node.spec.name] = new_node
|
319
|
+
return new_node
|
320
|
+
|
321
|
+
def agent(self,
|
322
|
+
name: str,
|
323
|
+
agent: str,
|
324
|
+
display_name: str|None=None,
|
325
|
+
message: str | None = "Follow the agent instructions.",
|
326
|
+
description: str | None = None,
|
327
|
+
input_schema: type[BaseModel]|None = None,
|
328
|
+
output_schema: type[BaseModel]|None=None,
|
329
|
+
guidelines: str|None=None,
|
330
|
+
input_map: DataMap = None) -> AgentNode:
|
331
|
+
|
332
|
+
# create input spec
|
435
333
|
input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = input_schema)
|
436
334
|
output_schema_obj = _get_json_schema_obj("output", output_schema)
|
437
335
|
|
438
|
-
# identify owner
|
439
|
-
if not owners:
|
440
|
-
owners = [ANY_USER]
|
441
|
-
|
442
336
|
# Create the tool spec
|
443
|
-
task_spec =
|
337
|
+
task_spec = AgentNodeSpec(
|
444
338
|
name=name,
|
445
339
|
display_name=display_name,
|
446
340
|
description=description,
|
447
|
-
|
341
|
+
agent=agent,
|
342
|
+
message=message,
|
343
|
+
guidelines=guidelines,
|
448
344
|
input_schema=ToolRequestBody(
|
449
345
|
type=input_schema_obj.type,
|
450
346
|
properties=input_schema_obj.properties,
|
@@ -455,31 +351,46 @@ class Flow(Node):
|
|
455
351
|
properties=output_schema_obj.properties,
|
456
352
|
required=output_schema_obj.required
|
457
353
|
),
|
458
|
-
tool=[],
|
459
354
|
output_schema_object = output_schema_obj
|
460
355
|
)
|
461
356
|
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
357
|
+
node = AgentNode(spec=task_spec)
|
358
|
+
# setup input map
|
359
|
+
if input_map:
|
360
|
+
node.input_map = self._get_data_map(input_map)
|
361
|
+
|
362
|
+
# add the node to the list of node
|
363
|
+
node = self._add_node(node)
|
364
|
+
return cast(AgentNode, node)
|
365
|
+
|
366
|
+
def prompt(self,
|
367
|
+
name: str,
|
368
|
+
display_name: str|None=None,
|
369
|
+
system_prompt: str | list[str] | None = None,
|
370
|
+
user_prompt: str | list[str] | None = None,
|
371
|
+
llm: str | None = None,
|
372
|
+
llm_parameters: PromptLLMParameters | None = None,
|
373
|
+
description: str | None = None,
|
374
|
+
input_schema: type[BaseModel]|None = None,
|
375
|
+
output_schema: type[BaseModel]|None=None,
|
376
|
+
input_map: DataMap = None) -> PromptNode:
|
377
|
+
|
378
|
+
if name is None:
|
379
|
+
raise ValueError("name must be provided.")
|
380
|
+
|
471
381
|
# create input spec
|
472
382
|
input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = input_schema)
|
473
383
|
output_schema_obj = _get_json_schema_obj("output", output_schema)
|
474
384
|
|
475
385
|
# Create the tool spec
|
476
|
-
task_spec =
|
386
|
+
task_spec = PromptNodeSpec(
|
477
387
|
name=name,
|
478
|
-
display_name=display_name,
|
388
|
+
display_name=display_name if display_name is not None else name,
|
479
389
|
description=description,
|
480
|
-
|
481
|
-
|
482
|
-
|
390
|
+
system_prompt=system_prompt,
|
391
|
+
user_prompt=user_prompt,
|
392
|
+
llm=llm,
|
393
|
+
llm_parameters=llm_parameters,
|
483
394
|
input_schema=ToolRequestBody(
|
484
395
|
type=input_schema_obj.type,
|
485
396
|
properties=input_schema_obj.properties,
|
@@ -493,7 +404,14 @@ class Flow(Node):
|
|
493
404
|
output_schema_object = output_schema_obj
|
494
405
|
)
|
495
406
|
|
496
|
-
|
407
|
+
node = PromptNode(spec=task_spec)
|
408
|
+
# setup input map
|
409
|
+
if input_map:
|
410
|
+
node.input_map = self._get_data_map(input_map)
|
411
|
+
|
412
|
+
# add the node to the list of node
|
413
|
+
node = self._add_node(node)
|
414
|
+
return cast(PromptNode, node)
|
497
415
|
|
498
416
|
def node_exists(self, node: Union[str, Node]):
|
499
417
|
|
@@ -587,9 +505,9 @@ class Flow(Node):
|
|
587
505
|
elif isinstance(evaluator, str):
|
588
506
|
e = Expression(expression=evaluator)
|
589
507
|
|
590
|
-
spec = BranchNodeSpec(name = "branch_" +
|
508
|
+
spec = BranchNodeSpec(name = "branch_" + str(self._next_sequence_id()), evaluator=e)
|
591
509
|
branch_node = Branch(spec = spec, containing_flow=self)
|
592
|
-
return cast(Branch, self.
|
510
|
+
return cast(Branch, self._add_node(branch_node))
|
593
511
|
|
594
512
|
def wait_for(self, *args) -> "Wait":
|
595
513
|
'''Wait for all incoming nodes to complete.'''
|
@@ -608,7 +526,7 @@ class Flow(Node):
|
|
608
526
|
|
609
527
|
def foreach(self, item_schema: type[BaseModel],
|
610
528
|
input_schema: type[BaseModel] |None=None,
|
611
|
-
output_schema: type[BaseModel] |None=None) -> "
|
529
|
+
output_schema: type[BaseModel] |None=None) -> "Foreach": # return an Foreach object
|
612
530
|
'''TODO: Docstrings'''
|
613
531
|
|
614
532
|
output_schema_obj = _get_json_schema_obj("output", output_schema)
|
@@ -625,7 +543,7 @@ class Flow(Node):
|
|
625
543
|
},
|
626
544
|
required = ["items"])
|
627
545
|
|
628
|
-
spec = ForeachSpec(name = "foreach_" +
|
546
|
+
spec = ForeachSpec(name = "foreach_" + str(self._next_sequence_id()),
|
629
547
|
input_schema=ToolRequestBody(
|
630
548
|
type=input_schema_obj.type,
|
631
549
|
properties=input_schema_obj.properties,
|
@@ -638,15 +556,14 @@ class Flow(Node):
|
|
638
556
|
) if output_schema_obj is not None else None,
|
639
557
|
item_schema = foreach_item_schema)
|
640
558
|
foreach_obj = Foreach(spec = spec, parent = self)
|
641
|
-
foreach_node = self.
|
559
|
+
foreach_node = self._add_node(foreach_obj)
|
642
560
|
self._add_schema(foreach_item_schema)
|
643
561
|
|
644
562
|
return cast(Flow, foreach_node)
|
645
563
|
|
646
564
|
def loop(self, evaluator: Union[Callable, Expression],
|
647
565
|
input_schema: type[BaseModel]|None=None,
|
648
|
-
output_schema: type[BaseModel]|None=None) -> "
|
649
|
-
'''TODO: Docstrings'''
|
566
|
+
output_schema: type[BaseModel]|None=None) -> "Loop": # return a WhileLoop object
|
650
567
|
e = evaluator
|
651
568
|
input_schema_obj = _get_json_schema_obj("input", input_schema)
|
652
569
|
output_schema_obj = _get_json_schema_obj("output", output_schema)
|
@@ -661,7 +578,7 @@ class Flow(Node):
|
|
661
578
|
elif isinstance(evaluator, str):
|
662
579
|
e = Expression(expression=evaluator)
|
663
580
|
|
664
|
-
loop_spec = LoopSpec(name = "loop_" +
|
581
|
+
loop_spec = LoopSpec(name = "loop_" + str(self._next_sequence_id()),
|
665
582
|
evaluator = e,
|
666
583
|
input_schema=ToolRequestBody(
|
667
584
|
type=input_schema_obj.type,
|
@@ -674,8 +591,35 @@ class Flow(Node):
|
|
674
591
|
required=output_schema_obj.required
|
675
592
|
) if output_schema_obj is not None else None)
|
676
593
|
while_loop = Loop(spec = loop_spec, parent = self)
|
677
|
-
while_node = self.
|
678
|
-
return while_node
|
594
|
+
while_node = self._add_node(while_loop)
|
595
|
+
return cast(Loop, while_node)
|
596
|
+
|
597
|
+
def userflow(self,
|
598
|
+
owners: Sequence[str] = [],
|
599
|
+
input_schema: type[BaseModel] |None=None,
|
600
|
+
output_schema: type[BaseModel] |None=None) -> "UserFlow": # return a UserFlow object
|
601
|
+
|
602
|
+
logger.warning("userflow is NOT working yet.")
|
603
|
+
|
604
|
+
output_schema_obj = _get_json_schema_obj("output", output_schema)
|
605
|
+
input_schema_obj = _get_json_schema_obj("input", input_schema)
|
606
|
+
|
607
|
+
spec = UserFlowSpec(name = "userflow_" + str(self._next_sequence_id()),
|
608
|
+
input_schema=ToolRequestBody(
|
609
|
+
type=input_schema_obj.type,
|
610
|
+
properties=input_schema_obj.properties,
|
611
|
+
required=input_schema_obj.required,
|
612
|
+
) if input_schema_obj is not None else None,
|
613
|
+
output_schema=ToolResponseBody(
|
614
|
+
type=output_schema_obj.type,
|
615
|
+
properties=output_schema_obj.properties,
|
616
|
+
required=output_schema_obj.required
|
617
|
+
) if output_schema_obj is not None else None,
|
618
|
+
owners = owners)
|
619
|
+
userflow_obj = UserFlow(spec = spec, parent = self)
|
620
|
+
userflow_node = self._add_node(userflow_obj)
|
621
|
+
|
622
|
+
return cast(UserFlow, userflow_node)
|
679
623
|
|
680
624
|
def validate_model(self) -> bool:
|
681
625
|
''' Validate the model. '''
|
@@ -787,25 +731,8 @@ class Flow(Node):
|
|
787
731
|
node_id = node
|
788
732
|
return node_id
|
789
733
|
|
790
|
-
def _get_data_map(self,
|
791
|
-
|
792
|
-
if isinstance(map_fn, Callable):
|
793
|
-
raise ValueError("Datamap with function is not supported yet.")
|
794
|
-
# map_spec = getattr(map_fn, "__map_spec__", None)
|
795
|
-
# if not map_spec:
|
796
|
-
# raise ValueError(
|
797
|
-
# "Only functions with @map decorator can be used to map between nodes.")
|
798
|
-
# map_spec_copy = copy.deepcopy(map_spec)
|
799
|
-
# self.refactor_datamap_spec_to_schemaref(map_spec_copy)
|
800
|
-
# data_map = FnDataMap(spec=map_spec_copy)
|
801
|
-
# return data_map
|
802
|
-
elif isinstance(map_fn, list):
|
803
|
-
data_map = AssignmentDataMap(spec=AssignmentDataMapSpec(
|
804
|
-
name="assignment",
|
805
|
-
maps=map_fn))
|
806
|
-
return data_map
|
807
|
-
return None
|
808
|
-
|
734
|
+
def _get_data_map(self, map: DataMap) -> DataMap:
|
735
|
+
return map
|
809
736
|
|
810
737
|
class FlowRunStatus(str, Enum):
|
811
738
|
NOT_STARTED = "not_started"
|
@@ -831,7 +758,6 @@ class FlowRun(BaseModel):
|
|
831
758
|
"arbitrary_types_allowed": True
|
832
759
|
}
|
833
760
|
|
834
|
-
|
835
761
|
async def _arun_events(self, input_data:dict=None, filters: Sequence[Union[FlowEventType, TaskEventType]]=None) -> AsyncIterator[FlowEvent]:
|
836
762
|
|
837
763
|
if self.status is not FlowRunStatus.NOT_STARTED:
|
@@ -1176,10 +1102,6 @@ class Loop(Flow):
|
|
1176
1102
|
def __init__(self, **kwargs):
|
1177
1103
|
super().__init__(**kwargs)
|
1178
1104
|
|
1179
|
-
# refactor item schema
|
1180
|
-
if isinstance(self.spec.evaluator, ScriptNodeSpec):
|
1181
|
-
self._refactor_spec_to_schemaref(self.spec.evaluator)
|
1182
|
-
|
1183
1105
|
def to_json(self) -> dict[str, Any]:
|
1184
1106
|
my_dict = super().to_json()
|
1185
1107
|
|
@@ -1286,3 +1208,103 @@ class FlowValidator(BaseModel):
|
|
1286
1208
|
bool: True if there are no errors, False otherwise.
|
1287
1209
|
'''
|
1288
1210
|
return not any(m.kind == FlowValidationKind.ERROR for m in messages)
|
1211
|
+
|
1212
|
+
class UserFlow(Flow):
|
1213
|
+
'''
|
1214
|
+
A flow that represents a series of user nodes.
|
1215
|
+
A user flow can include other nodes, but not another User Flows.
|
1216
|
+
'''
|
1217
|
+
|
1218
|
+
def __repr__(self):
|
1219
|
+
return f"UserFlow(name='{self.spec.name}', description='{self.spec.description}')"
|
1220
|
+
|
1221
|
+
def get_spec(self) -> NodeSpec:
|
1222
|
+
return cast(UserFlowSpec, self.spec)
|
1223
|
+
|
1224
|
+
def to_json(self) -> dict[str, Any]:
|
1225
|
+
my_dict = super().to_json()
|
1226
|
+
|
1227
|
+
return my_dict
|
1228
|
+
|
1229
|
+
def field(self,
|
1230
|
+
name: str,
|
1231
|
+
kind: UserFieldKind = UserFieldKind.Text,
|
1232
|
+
display_name: str | None = None,
|
1233
|
+
description: str | None = None,
|
1234
|
+
owners: list[str] = [],
|
1235
|
+
default: Any | None = None,
|
1236
|
+
text: str = None,
|
1237
|
+
option: UserFieldOption | None = None,
|
1238
|
+
input_map: DataMap = None,
|
1239
|
+
custom: dict[str, Any] = {}) -> UserNode:
|
1240
|
+
'''create a node in the flow'''
|
1241
|
+
# create a json schema object based on the single field
|
1242
|
+
if not name:
|
1243
|
+
raise AssertionError("name cannot be empty")
|
1244
|
+
|
1245
|
+
schema_obj = JsonSchemaObject(type="object",
|
1246
|
+
title=name,
|
1247
|
+
description=description)
|
1248
|
+
|
1249
|
+
schema_obj.properties = {}
|
1250
|
+
schema_obj.properties[name] = UserFieldKind.convert_kind_to_schema_property(kind, name, description, default, option, custom)
|
1251
|
+
|
1252
|
+
return self.user(name,
|
1253
|
+
display_name=display_name,
|
1254
|
+
description=description,
|
1255
|
+
owners=owners,
|
1256
|
+
text=text,
|
1257
|
+
output_schema=schema_obj,
|
1258
|
+
input_map=input_map)
|
1259
|
+
|
1260
|
+
def user(
|
1261
|
+
self,
|
1262
|
+
name: str | None = None,
|
1263
|
+
display_name: str | None = None,
|
1264
|
+
description: str | None = None,
|
1265
|
+
owners: list[str] = [],
|
1266
|
+
text: str | None = None,
|
1267
|
+
output_schema: type[BaseModel] | JsonSchemaObject| None = None,
|
1268
|
+
input_map: DataMap = None,
|
1269
|
+
) -> UserNode:
|
1270
|
+
'''create a user node in the flow'''
|
1271
|
+
|
1272
|
+
output_schema_obj = output_schema
|
1273
|
+
if inspect.isclass(output_schema):
|
1274
|
+
# create input spec
|
1275
|
+
output_schema_obj = _get_json_schema_obj(parameter_name = "output", type_def = output_schema)
|
1276
|
+
# input and output is always the same in an user node
|
1277
|
+
output_schema_obj = output_schema_obj
|
1278
|
+
|
1279
|
+
# identify owner
|
1280
|
+
if not owners:
|
1281
|
+
owners = [ANY_USER]
|
1282
|
+
|
1283
|
+
# Create the tool spec
|
1284
|
+
task_spec = UserNodeSpec(
|
1285
|
+
name=name,
|
1286
|
+
display_name=display_name,
|
1287
|
+
description=description,
|
1288
|
+
owners=owners,
|
1289
|
+
input_schema=None,
|
1290
|
+
output_schema=ToolResponseBody(
|
1291
|
+
type=output_schema_obj.type,
|
1292
|
+
properties=output_schema_obj.properties,
|
1293
|
+
required=output_schema_obj.required
|
1294
|
+
),
|
1295
|
+
text=text,
|
1296
|
+
output_schema_object = output_schema_obj
|
1297
|
+
)
|
1298
|
+
|
1299
|
+
task_spec.setup_fields()
|
1300
|
+
|
1301
|
+
node = UserNode(spec = task_spec)
|
1302
|
+
|
1303
|
+
# setup input map
|
1304
|
+
if input_map:
|
1305
|
+
node.input_map = self._get_data_map(input_map)
|
1306
|
+
|
1307
|
+
node = self._add_node(node)
|
1308
|
+
return cast(UserNode, node)
|
1309
|
+
|
1310
|
+
|