ibm-watsonx-orchestrate 1.6.3__py3-none-any.whl → 1.6.4__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.
Files changed (58) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +3 -3
  3. ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +3 -2
  4. ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +3 -2
  5. ibm_watsonx_orchestrate/agent_builder/agents/types.py +38 -9
  6. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +4 -3
  7. ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -2
  8. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
  9. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
  10. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
  11. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +75 -24
  12. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
  13. ibm_watsonx_orchestrate/agent_builder/tools/types.py +17 -11
  14. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +7 -6
  16. ibm_watsonx_orchestrate/cli/commands/channels/types.py +3 -2
  17. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +1 -2
  18. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
  19. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -8
  20. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  21. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +368 -0
  22. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +170 -0
  23. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +5 -5
  24. ibm_watsonx_orchestrate/cli/commands/environment/types.py +2 -0
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +102 -37
  26. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +20 -2
  27. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
  28. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +36 -20
  29. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
  30. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +94 -36
  31. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +11 -4
  33. ibm_watsonx_orchestrate/cli/config.py +3 -3
  34. ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
  35. ibm_watsonx_orchestrate/cli/main.py +5 -0
  36. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  37. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +67 -0
  38. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
  39. ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
  40. ibm_watsonx_orchestrate/client/service_instance.py +33 -7
  41. ibm_watsonx_orchestrate/client/utils.py +15 -13
  42. ibm_watsonx_orchestrate/docker/compose-lite.yml +198 -6
  43. ibm_watsonx_orchestrate/docker/default.env +36 -12
  44. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +9 -4
  45. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
  46. ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
  47. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +131 -20
  48. ibm_watsonx_orchestrate/flow_builder/node.py +18 -1
  49. ibm_watsonx_orchestrate/flow_builder/types.py +271 -16
  50. ibm_watsonx_orchestrate/flow_builder/utils.py +120 -6
  51. ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
  52. {ibm_watsonx_orchestrate-1.6.3.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/METADATA +3 -7
  53. {ibm_watsonx_orchestrate-1.6.3.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/RECORD +56 -53
  54. ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +0 -149
  55. ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
  56. {ibm_watsonx_orchestrate-1.6.3.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/WHEEL +0 -0
  57. {ibm_watsonx_orchestrate-1.6.3.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/entry_points.txt +0 -0
  58. {ibm_watsonx_orchestrate-1.6.3.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/licenses/LICENSE +0 -0
@@ -15,6 +15,7 @@ import logging
15
15
  import copy
16
16
  import uuid
17
17
  import pytz
18
+ import os
18
19
 
19
20
  from typing_extensions import Self
20
21
  from pydantic import BaseModel, Field, SerializeAsAny
@@ -25,11 +26,12 @@ from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
25
26
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
26
27
  from ..types import (
27
28
  EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy, PromptLLMParameters, PromptNodeSpec,
28
- StartNodeSpec, ToolSpec, JsonSchemaObject, ToolRequestBody, ToolResponseBody, UserFieldKind, UserFieldOption, UserFlowSpec, UserNodeSpec, WaitPolicy
29
+ StartNodeSpec, ToolSpec, JsonSchemaObject, ToolRequestBody, ToolResponseBody, UserFieldKind, UserFieldOption, UserFlowSpec, UserNodeSpec, WaitPolicy,
30
+ DocProcSpec, TextExtractionResponse, File, DecisionsNodeSpec, DecisionsRule
29
31
  )
30
32
  from .constants import CURRENT_USER, START, END, ANY_USER
31
33
  from ..node import (
32
- EndNode, Node, PromptNode, StartNode, UserNode, AgentNode, DataMap, ToolNode
34
+ EndNode, Node, PromptNode, StartNode, UserNode, AgentNode, DataMap, ToolNode, DocProcNode, DecisionsNode
33
35
  )
34
36
  from ..types import (
35
37
  AgentNodeSpec, extract_node_spec, FlowContext, FlowEventType, FlowEvent, FlowSpec,
@@ -114,7 +116,7 @@ class Flow(Node):
114
116
  # pydantic suppport nested comparison by default
115
117
 
116
118
  schema.title = title
117
-
119
+
118
120
  if schema == existing_schema:
119
121
  return existing_schema
120
122
  # we need to do a deep compare
@@ -354,6 +356,7 @@ class Flow(Node):
354
356
  name: str,
355
357
  agent: str,
356
358
  display_name: str|None=None,
359
+ title: str | None = None,
357
360
  message: str | None = "Follow the agent instructions.",
358
361
  description: str | None = None,
359
362
  input_schema: type[BaseModel]|None = None,
@@ -371,6 +374,7 @@ class Flow(Node):
371
374
  display_name=display_name,
372
375
  description=description,
373
376
  agent=agent,
377
+ title=title,
374
378
  message=message,
375
379
  guidelines=guidelines,
376
380
  input_schema=_get_tool_request_body(input_schema_obj),
@@ -428,6 +432,91 @@ class Flow(Node):
428
432
  # add the node to the list of node
429
433
  node = self._add_node(node)
430
434
  return cast(PromptNode, node)
435
+
436
+ def decisions(self,
437
+ name: str,
438
+ display_name: str|None=None,
439
+ rules: list[DecisionsRule] | None = None,
440
+ default_actions: dict[str, Any] = None,
441
+ locale: str | None = None,
442
+ description: str | None = None,
443
+ input_schema: type[BaseModel]|None = None,
444
+ output_schema: type[BaseModel]|None=None,
445
+ input_map: DataMap = None) -> PromptNode:
446
+
447
+ if name is None:
448
+ raise ValueError("name must be provided.")
449
+
450
+ if rules is None:
451
+ raise ValueError("rules must be specified.")
452
+
453
+ # create input spec
454
+ input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = input_schema)
455
+ output_schema_obj = _get_json_schema_obj("output", output_schema)
456
+
457
+ # Create the tool spec
458
+ task_spec = DecisionsNodeSpec(
459
+ name=name,
460
+ display_name=display_name if display_name is not None else name,
461
+ description=description,
462
+ rules=rules,
463
+ default_actions=default_actions,
464
+ locale=locale,
465
+ input_schema=_get_tool_request_body(input_schema_obj),
466
+ output_schema=_get_tool_response_body(output_schema_obj),
467
+ output_schema_object = output_schema_obj
468
+ )
469
+
470
+ node = DecisionsNode(spec=task_spec)
471
+ # setup input map
472
+ if input_map:
473
+ node.input_map = self._get_data_map(input_map)
474
+
475
+ # add the node to the list of node
476
+ node = self._add_node(node)
477
+ return cast(DecisionsNode, node)
478
+
479
+ def docproc(self,
480
+ name: str,
481
+ task: str,
482
+ display_name: str|None=None,
483
+ description: str | None = None,
484
+ input_map: DataMap = None) -> DocProcNode:
485
+
486
+ if name is None :
487
+ raise ValueError("name must be provided.")
488
+
489
+ if task is None:
490
+ raise ValueError("task must be provided.")
491
+
492
+ output_schema_dict = {
493
+ "text_extraction" : TextExtractionResponse
494
+ }
495
+ # create input spec
496
+ input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = File)
497
+ output_schema_obj = _get_json_schema_obj("output", output_schema_dict[task])
498
+ if "$defs" in output_schema_obj.model_extra:
499
+ output_schema_obj.model_extra.pop("$defs")
500
+ # Create the docproc spec
501
+ task_spec = DocProcSpec(
502
+ name=name,
503
+ display_name=display_name if display_name is not None else name,
504
+ description=description,
505
+ input_schema=_get_tool_request_body(input_schema_obj),
506
+ output_schema=_get_tool_response_body(output_schema_obj),
507
+ output_schema_object = output_schema_obj,
508
+ task=task
509
+ )
510
+
511
+ node = DocProcNode(spec=task_spec)
512
+ # setup input map
513
+ if input_map:
514
+ node.input_map = self._get_data_map(input_map)
515
+
516
+ # add the node to the list of node
517
+ node = self._add_node(node)
518
+ return cast(DocProcNode, node)
519
+
431
520
 
432
521
  def node_exists(self, node: Union[str, Node]):
433
522
 
@@ -921,7 +1010,8 @@ class FlowFactory(BaseModel):
921
1010
  description: str|None=None,
922
1011
  initiators: Sequence[str]|None=None,
923
1012
  input_schema: type[BaseModel]|None=None,
924
- output_schema: type[BaseModel]|None=None) -> Flow:
1013
+ output_schema: type[BaseModel]|None=None,
1014
+ schedulable: bool=False) -> Flow:
925
1015
  if isinstance(name, Callable):
926
1016
  flow_spec = getattr(name, "__flow_spec__", None)
927
1017
  if not flow_spec:
@@ -942,7 +1032,8 @@ class FlowFactory(BaseModel):
942
1032
  initiators=initiators,
943
1033
  input_schema=_get_tool_request_body(input_schema_obj),
944
1034
  output_schema=_get_tool_response_body(output_schema_obj),
945
- output_schema_object = output_schema_obj
1035
+ output_schema_object = output_schema_obj,
1036
+ schedulable=schedulable,
946
1037
  )
947
1038
 
948
1039
  return Flow(spec = flow_spec)
@@ -1228,10 +1319,12 @@ class UserFlow(Flow):
1228
1319
  kind: UserFieldKind = UserFieldKind.Text,
1229
1320
  display_name: str | None = None,
1230
1321
  description: str | None = None,
1231
- owners: list[str] = [],
1232
1322
  default: Any | None = None,
1233
- text: str = None,
1323
+ text: str = None, # The text used to ask question to the user, e.g. 'what is your name?'
1234
1324
  option: UserFieldOption | None = None,
1325
+ is_list: bool = False,
1326
+ min: Any | None = None,
1327
+ max: Any | None = None,
1235
1328
  input_map: DataMap = None,
1236
1329
  custom: dict[str, Any] = {}) -> UserNode:
1237
1330
  '''create a node in the flow'''
@@ -1246,20 +1339,42 @@ class UserFlow(Flow):
1246
1339
  schema_obj.properties = {}
1247
1340
  schema_obj.properties[name] = UserFieldKind.convert_kind_to_schema_property(kind, name, description, default, option, custom)
1248
1341
 
1249
- return self.user(name,
1250
- display_name=display_name,
1251
- description=description,
1252
- owners=owners,
1253
- text=text,
1254
- output_schema=schema_obj,
1255
- input_map=input_map)
1342
+ task_spec = UserNodeSpec(
1343
+ name=name,
1344
+ display_name=display_name,
1345
+ description=description,
1346
+ owners=[CURRENT_USER],
1347
+ input_schema=_get_tool_request_body(schema_obj),
1348
+ output_schema=_get_tool_response_body(schema_obj),
1349
+ text=text,
1350
+ output_schema_object = schema_obj
1351
+ )
1352
+
1353
+ node = UserNode(spec = task_spec)
1354
+ node.field(name = name,
1355
+ kind = kind,
1356
+ display_name = display_name,
1357
+ description = description,
1358
+ default = default,
1359
+ text = text,
1360
+ option = option,
1361
+ is_list = is_list,
1362
+ min = min,
1363
+ max = max,
1364
+ custom = custom)
1365
+
1366
+ # setup input map
1367
+ if input_map:
1368
+ node.input_map = self._get_data_map(input_map)
1369
+
1370
+ node = self._add_node(node)
1371
+ return cast(UserNode, node)
1256
1372
 
1257
1373
  def user(
1258
1374
  self,
1259
1375
  name: str | None = None,
1260
1376
  display_name: str | None = None,
1261
1377
  description: str | None = None,
1262
- owners: list[str] = [],
1263
1378
  text: str | None = None,
1264
1379
  output_schema: type[BaseModel] | JsonSchemaObject| None = None,
1265
1380
  input_map: DataMap = None,
@@ -1273,16 +1388,12 @@ class UserFlow(Flow):
1273
1388
  # input and output is always the same in an user node
1274
1389
  output_schema_obj = output_schema_obj
1275
1390
 
1276
- # identify owner
1277
- if not owners:
1278
- owners = [ANY_USER]
1279
-
1280
1391
  # Create the tool spec
1281
1392
  task_spec = UserNodeSpec(
1282
1393
  name=name,
1283
1394
  display_name=display_name,
1284
1395
  description=description,
1285
- owners=owners,
1396
+ owners=[CURRENT_USER],
1286
1397
  input_schema=_get_tool_request_body(output_schema_obj),
1287
1398
  output_schema=_get_tool_response_body(output_schema_obj),
1288
1399
  text=text,
@@ -5,7 +5,7 @@ import uuid
5
5
  import yaml
6
6
  from pydantic import BaseModel, Field, SerializeAsAny
7
7
 
8
- from .types import EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec
8
+ from .types import EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec, DocProcSpec, DecisionsNodeSpec
9
9
  from .data_map import DataMap
10
10
 
11
11
  class Node(BaseModel):
@@ -78,6 +78,8 @@ class UserNode(Node):
78
78
  description: str | None = None,
79
79
  default: Any | None = None,
80
80
  option: UserFieldOption | None = None,
81
+ min: Any | None = None,
82
+ max: Any | None = None,
81
83
  is_list: bool = False,
82
84
  custom: dict[str, Any] | None = None,
83
85
  widget: str | None = None):
@@ -88,6 +90,8 @@ class UserNode(Node):
88
90
  description=description,
89
91
  default=default,
90
92
  option=option,
93
+ min=min,
94
+ max=max,
91
95
  is_list=is_list,
92
96
  custom=custom,
93
97
  widget=widget)
@@ -105,7 +109,20 @@ class PromptNode(Node):
105
109
 
106
110
  def get_spec(self) -> PromptNodeSpec:
107
111
  return cast(PromptNodeSpec, self.spec)
112
+
113
+ class DocProcNode(Node):
114
+ def __repr__(self):
115
+ return f"DocProcNode(name='{self.spec.name}', description='{self.spec.description}')"
108
116
 
117
+ def get_spec(self) -> DocProcSpec:
118
+ return cast(DocProcSpec, self.spec)
119
+ class DecisionsNode(Node):
120
+ def __repr__(self):
121
+ return f"DecisionsNode(name='{self.spec.name}', description='{self.spec.description}')"
122
+
123
+ def get_spec(self) -> DecisionsNodeSpec:
124
+ return cast(DecisionsNodeSpec, self.spec)
125
+
109
126
  class NodeInstance(BaseModel):
110
127
  node: Node
111
128
  id: str # unique id of this task instance
@@ -1,5 +1,7 @@
1
1
  from dataclasses import dataclass
2
- from enum import Enum
2
+ from enum import Enum, StrEnum, auto
3
+ from datetime import date
4
+ import numbers
3
5
  import inspect
4
6
  import logging
5
7
  from typing import (
@@ -7,7 +9,6 @@ from typing import (
7
9
  )
8
10
 
9
11
  import docstring_parser
10
- from munch import Munch
11
12
  from pydantic import BaseModel, Field
12
13
 
13
14
  from langchain_core.tools.base import create_schema_from_function
@@ -116,7 +117,7 @@ def _to_json_from_output_schema(schema: Union[ToolResponseBody, SchemaRef]) -> d
116
117
  return model_spec
117
118
 
118
119
  class NodeSpec(BaseModel):
119
- kind: Literal["node", "tool", "user", "agent", "flow", "start", "decisions", "prompt", "branch", "wait", "foreach", "loop", "userflow", "end"] = "node"
120
+ kind: Literal["node", "tool", "user", "agent", "flow", "start", "decisions", "prompt", "branch", "wait", "foreach", "loop", "userflow", "end", "docproc" ] = "node"
120
121
  name: str
121
122
  display_name: str | None = None
122
123
  description: str | None = None
@@ -162,6 +163,24 @@ class NodeSpec(BaseModel):
162
163
 
163
164
  return model_spec
164
165
 
166
+ class DocProcTask(StrEnum):
167
+ '''
168
+ Possible names for the Document processing task parameter
169
+ '''
170
+ text_extraction = auto()
171
+
172
+ class DocProcSpec(NodeSpec):
173
+ task: DocProcTask = Field(description='The document processing operation name', default=DocProcTask.text_extraction)
174
+
175
+ def __init__(self, **data):
176
+ super().__init__(**data)
177
+ self.kind = "docproc"
178
+
179
+ def to_json(self) -> dict[str, Any]:
180
+ model_spec = super().to_json()
181
+ model_spec["task"] = self.task
182
+ return model_spec
183
+
165
184
  class StartNodeSpec(NodeSpec):
166
185
  def __init__(self, **data):
167
186
  super().__init__(**data)
@@ -296,6 +315,8 @@ class UserField(BaseModel):
296
315
  description: str | None = None
297
316
  default: Any | None = None
298
317
  option: UserFieldOption | None = None
318
+ min: Any | None = None,
319
+ max: Any | None = None,
299
320
  is_list: bool = False
300
321
  custom: dict[str, Any] | None = None
301
322
  widget: str | None = None
@@ -314,6 +335,10 @@ class UserField(BaseModel):
314
335
  model_spec["description"] = self.description
315
336
  if self.default:
316
337
  model_spec["default"] = self.default
338
+ if self.min:
339
+ model_spec["min"] = self.min
340
+ if self.max:
341
+ model_spec["min"] = self.max
317
342
  if self.is_list:
318
343
  model_spec["is_list"] = self.is_list
319
344
  if self.option:
@@ -356,7 +381,10 @@ class UserNodeSpec(NodeSpec):
356
381
  display_name: str | None = None,
357
382
  description: str | None = None,
358
383
  default: Any | None = None,
359
- option: list[str] | None = None, is_list: bool = False,
384
+ option: list[str] | None = None,
385
+ min: Any | None = None,
386
+ max: Any | None = None,
387
+ is_list: bool = False,
360
388
  custom: dict[str, Any] | None = None,
361
389
  widget: str | None = None):
362
390
  userfield = UserField(name=name,
@@ -366,6 +394,8 @@ class UserNodeSpec(NodeSpec):
366
394
  description=description,
367
395
  default=default,
368
396
  option=option,
397
+ min=min,
398
+ max=max,
369
399
  is_list=is_list,
370
400
  custom=custom,
371
401
  widget=widget)
@@ -402,6 +432,8 @@ class UserNodeSpec(NodeSpec):
402
432
  default=prop_schema.default,
403
433
  option=self.setup_field_options(prop_schema.title, prop_schema.enum),
404
434
  is_list=prop_schema.type == "array",
435
+ min=prop_schema.minimum,
436
+ max=prop_schema.maximum,
405
437
  custom=prop_schema.model_extra))
406
438
 
407
439
  def setup_field_options(self, name: str, enums: List[str]) -> UserFieldOption:
@@ -415,6 +447,7 @@ class UserNodeSpec(NodeSpec):
415
447
 
416
448
  class AgentNodeSpec(ToolNodeSpec):
417
449
  message: str | None = Field(default=None, description="The instructions for the task.")
450
+ title: str | None = Field(default=None, description="The title of the message.")
418
451
  guidelines: str | None = Field(default=None, description="The guidelines for the task.")
419
452
  agent: str
420
453
 
@@ -430,6 +463,8 @@ class AgentNodeSpec(ToolNodeSpec):
430
463
  model_spec["guidelines"] = self.guidelines
431
464
  if self.agent:
432
465
  model_spec["agent"] = self.agent
466
+ if self.title:
467
+ model_spec["title"] = self.title
433
468
  return model_spec
434
469
 
435
470
  class PromptLLMParameters(BaseModel):
@@ -479,6 +514,7 @@ class PromptNodeSpec(NodeSpec):
479
514
  model_spec["llm_parameters"] = self.llm_parameters.to_json()
480
515
 
481
516
  return model_spec
517
+
482
518
 
483
519
  class Expression(BaseModel):
484
520
  '''An expression could return a boolean or a value'''
@@ -553,10 +589,9 @@ class WaitNodeSpec(FlowControlNodeSpec):
553
589
  return my_dict
554
590
 
555
591
  class FlowSpec(NodeSpec):
556
-
557
-
558
592
  # who can initiate the flow
559
593
  initiators: Sequence[str] = [ANY_USER]
594
+ schedulable: bool = False
560
595
 
561
596
  def __init__(self, **kwargs):
562
597
  super().__init__(**kwargs)
@@ -566,6 +601,8 @@ class FlowSpec(NodeSpec):
566
601
  model_spec = super().to_json()
567
602
  if self.initiators:
568
603
  model_spec["initiators"] = self.initiators
604
+
605
+ model_spec["schedulable"] = self.schedulable
569
606
 
570
607
  return model_spec
571
608
 
@@ -631,11 +668,11 @@ class TaskData(NamedTuple):
631
668
 
632
669
  class TaskEventType(Enum):
633
670
 
634
- ON_TASK_WAIT = "on_task_wait" # the task is waiting for inputs before proceeding
635
- ON_TASK_START = "on_task_start"
636
- ON_TASK_END = "on_task_end"
637
- ON_TASK_STREAM = "on_task_stream"
638
- ON_TASK_ERROR = "on_task_error"
671
+ ON_TASK_WAIT = "task:on_task_wait" # the task is waiting for inputs before proceeding
672
+ ON_TASK_START = "task:on_task_start"
673
+ ON_TASK_END = "task:on_task_end"
674
+ ON_TASK_STREAM = "task:on_task_stream"
675
+ ON_TASK_ERROR = "task:on_task_error"
639
676
 
640
677
  class FlowData(BaseModel):
641
678
  '''This class represents the data that is passed between tasks in a flow.'''
@@ -667,9 +704,9 @@ class FlowContext(BaseModel):
667
704
 
668
705
  class FlowEventType(Enum):
669
706
 
670
- ON_FLOW_START = "on_flow_start"
671
- ON_FLOW_END = "on_flow_end"
672
- ON_FLOW_ERROR = "on_flow_error"
707
+ ON_FLOW_START = "flow:on_flow_start"
708
+ ON_FLOW_END = "flow:on_flow_end"
709
+ ON_FLOW_ERROR = "flow:on_flow_error"
673
710
 
674
711
 
675
712
  @dataclass
@@ -691,9 +728,227 @@ class Assignment(BaseModel):
691
728
  e.g. "node.input.name" or "=f'{node.output.name}_{node.output.id}'"
692
729
 
693
730
  '''
694
- target: str
695
- source: str
731
+ target_variable: str
732
+ value_expression: str | None = None
733
+ has_no_value: bool = False
734
+ default_value: Any | None = None
735
+ metadata: dict = Field(default_factory=dict[str, Any])
736
+
737
+ class LanguageCode(StrEnum):
738
+ '''
739
+ The ISO-639 language codes understood by Document Processing functions.
740
+ A special 'en_hw' code is used to enable an English handwritten model.
741
+ '''
742
+ en = auto()
743
+ fr = auto()
744
+ en_hw = auto()
745
+
746
+ class File(BaseModel):
747
+ '''
748
+ This class represents the input of a Document processing task.
749
+
750
+ Attributes:
751
+ document_ref (bytes|str): This is either a URL to the location of the document bytes or an ID that we use to resolve the location of the document
752
+ language (LanguageCode): Optional language code used when processing the input document
753
+ '''
754
+ # This is declared as bytes but the runtime will understand if a URL is send in as input.
755
+ # We need to use bytes here for Chat-with-doc to recognize the input as a File.
756
+ document_ref: bytes | str = Field(
757
+ description="Either an ID or a URL identifying the document to be used.",
758
+ title='Document reference',
759
+ default=None,
760
+ json_schema_extra={"format": "binary"})
761
+ language: Optional[LanguageCode] = Field(
762
+ description='Optional language code of the document, defaults to "en"',
763
+ title='Document language code',
764
+ default=LanguageCode.en)
765
+
766
+ class TextExtraction(BaseModel):
767
+ '''
768
+ This class represents the output generated by a "text_extraction" document processing (docproc) operation.
769
+ Attributes:
770
+ text (str): the text extracted from the input document.
771
+ '''
772
+ text: str = Field(description='The text extracted from the input document', title='Text extraction')
773
+
774
+ class TextExtractionResponse(BaseModel):
775
+ '''
776
+ The text extraction operation response.
777
+ Attributes:
778
+ output (TextExtraction): a wrapper for the text extraction response
779
+ '''
780
+ output: TextExtraction = Field(description='The text extraction response')
781
+
782
+
783
+ class DecisionsCondition(BaseModel):
784
+ _condition: str | None = None
785
+
786
+ def greater_than(self, value: Union[numbers.Number, date, str]) -> Self:
787
+ self._check_type_is_number_or_date_or_str(value)
788
+ self._condition = f"> {self._format_value(value)}"
789
+ return self
790
+
791
+ def greater_than_or_equal(self, value: Union[numbers.Number, date, str]) -> Self:
792
+ self._check_type_is_number_or_date_or_str(value)
793
+ self._condition = f">= {self._format_value(value)}"
794
+ return self
795
+
796
+ def less_than(self, value: Union[numbers.Number, date, str]) -> Self:
797
+ self._check_type_is_number_or_date_or_str(value)
798
+ self._condition = f"< {self._format_value(value)}"
799
+ return self
800
+
801
+ def less_than_or_equal(self, value: Union[numbers.Number, date, str]) -> Self:
802
+ self._check_type_is_number_or_date_or_str(value)
803
+ self._condition = f"<= {self._format_value(value)}"
804
+ return self
805
+
806
+ def equal(self, value: Union[numbers.Number, date, str]) -> Self:
807
+ self._check_type_is_number_or_date_or_str(value)
808
+ self._condition = f"== {self._format_value(value)}"
809
+ return self
810
+
811
+ def not_equal(self, value: Union[numbers.Number, date, str]) -> Self:
812
+ self._check_type_is_number_or_date_or_str(value)
813
+ self._condition = f"== {self._format_value(value)}"
814
+ return self
815
+
816
+ def contains(self, value: str) -> Self:
817
+ self._check_type_is_str(value)
818
+ self._condition = f"contains {self._format_value(value)}"
819
+ return self
820
+
821
+ def not_contains(self, value: str) -> Self:
822
+ self._check_type_is_str(value)
823
+ self._condition = f"doesNotContain {self._format_value(value)}"
824
+ return self
825
+
826
+ def is_in(self, value: str) -> Self:
827
+ self._check_type_is_str(value)
828
+ self._condition = f"in {self._format_value(value)}"
829
+ return self
830
+
831
+ def is_not_in(self, value: str) -> Self:
832
+ self._check_type_is_str(value)
833
+ self._condition = f"notIn {self._format_value(value)}"
834
+ return self
835
+
836
+ def startswith(self, value: str) -> Self:
837
+ self._check_type_is_str(value)
838
+ self._condition = f"startsWith {self._format_value(value)}"
839
+ return self
840
+
841
+ def endswith(self, value: str) -> Self:
842
+ self._check_type_is_str(value)
843
+ self._condition = f"endsWith {self._format_value(value)}"
844
+ return self
845
+
846
+
847
+ def in_range(self, startValue: Union[numbers.Number, date], endValue: Union[numbers.Number, date],
848
+ startsInclusive: bool = False, endsInclusive: bool = False) -> Self:
849
+ self._check_type_is_number_or_date_or_str(startValue)
850
+ self._check_type_is_number_or_date_or_str(endValue)
851
+ if type(startValue) is not type(endValue):
852
+ raise TypeError("startValue and endValue must be of the same type")
853
+ start_op = "[" if startsInclusive else "(" # [ is inclusive, ( is exclusive
854
+ end_op = "]" if endsInclusive else ")"
855
+ self._condition = f"{start_op}{self._format_value(startValue)}:{self._format_value(endValue)}{end_op}"
856
+ return self
857
+
858
+ def _check_type_is_number_or_date(self, value: Union[numbers.Number, date]):
859
+ if not isinstance(value, (numbers.Number, date)):
860
+ raise TypeError("Value must be a number or a date")
861
+
862
+ def _check_type_is_number_or_date_or_str(self, value: Union[numbers.Number, date, str]):
863
+ if not isinstance(value, (numbers.Number, date, str)):
864
+ raise TypeError("Value must be a number or a date or a string")
865
+
866
+ def _check_type_is_str(self, value: str):
867
+ if not isinstance(value, str):
868
+ raise TypeError("Value must be a string")
869
+
870
+ @staticmethod
871
+ def _format_value(value: Union[numbers.Number, date, str]):
872
+ if isinstance(value, numbers.Number):
873
+ return f"{value}"
874
+ if isinstance(value, date):
875
+ return f"\"{value.strftime('%B %d, %Y')}\""
876
+ return f"\"{value}\""
877
+
878
+ def condition(self):
879
+ return self._condition
880
+
881
+
882
+
883
+ class DecisionsRule(BaseModel):
884
+ '''
885
+ A set of decisions rules.
886
+ '''
887
+ _conditions: dict[str, str]
888
+ _actions: dict[str, Union[numbers.Number, str]]
889
+
890
+ def __init__(self, **data):
891
+ super().__init__(**data)
892
+ self._conditions = {}
893
+ self._actions = {}
894
+
895
+ def condition(self, key: str, cond: DecisionsCondition) -> Self:
896
+ self._conditions[key] = cond.condition()
897
+ return self
696
898
 
899
+ def action(self, key: str, value: Union[numbers.Number, date, str]) -> Self:
900
+ if isinstance(value, date):
901
+ self._actions[key] = value.strftime("%B %d, %Y")
902
+ return self
903
+ self._actions[key] = value
904
+ return self
905
+
906
+ def to_json(self) -> dict[str, Any]:
907
+ '''
908
+ Serialize the rules into JSON object
909
+ '''
910
+ model_spec = {}
911
+ if self._conditions:
912
+ model_spec["conditions"] = self._conditions
913
+ if self._actions:
914
+ model_spec["actions"] = self._actions
915
+ return model_spec
916
+
917
+
918
+ class DecisionsNodeSpec(NodeSpec):
919
+ '''
920
+ Node specification for Decision Table
921
+ '''
922
+ locale: str | None = None
923
+ rules: list[DecisionsRule]
924
+ default_actions: dict[str, Union[int, float, complex, str]] | None
925
+
926
+ def __init__(self, **data):
927
+ super().__init__(**data)
928
+ self.kind = "decisions"
929
+
930
+ def default_action(self, key: str, value: Union[int, float, complex, date, str]) -> Self:
931
+ '''
932
+ create a new default action
933
+ '''
934
+ if isinstance(value, date):
935
+ self.default_actions[key] = value.strftime("%B %d, %Y")
936
+ return self
937
+ self.default_actions[key] = value
938
+ return self
939
+
940
+ def to_json(self) -> dict[str, Any]:
941
+ model_spec = super().to_json()
942
+ if self.locale:
943
+ model_spec["locale"] = self.locale
944
+ if self.rules:
945
+ model_spec["rules"] = [rule.to_json() for rule in self.rules]
946
+ if self.default_actions:
947
+ model_spec["default_actions"] = self.default_actions
948
+
949
+ return model_spec
950
+
951
+
697
952
  def extract_node_spec(
698
953
  fn: Callable | PythonTool,
699
954
  name: Optional[str] = None,