ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.6.0__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 (71) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
  3. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
  4. ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
  5. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
  7. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
  8. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
  9. ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
  10. ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
  11. ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
  13. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
  14. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
  15. ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
  17. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +153 -21
  18. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
  19. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  20. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
  21. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
  22. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
  23. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
  24. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +312 -0
  26. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +171 -0
  27. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
  28. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
  29. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
  30. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
  31. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
  32. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -21
  33. ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +111 -32
  36. ibm_watsonx_orchestrate/cli/config.py +2 -0
  37. ibm_watsonx_orchestrate/cli/main.py +6 -0
  38. ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
  39. ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
  40. ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
  41. ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
  42. ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
  43. ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
  44. ibm_watsonx_orchestrate/client/credentials.py +4 -0
  45. ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
  46. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
  47. ibm_watsonx_orchestrate/client/service_instance.py +42 -1
  48. ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
  49. ibm_watsonx_orchestrate/client/utils.py +37 -2
  50. ibm_watsonx_orchestrate/docker/compose-lite.yml +252 -81
  51. ibm_watsonx_orchestrate/docker/default.env +40 -15
  52. ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
  53. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
  54. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
  55. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
  56. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
  57. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
  58. ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
  59. ibm_watsonx_orchestrate/run/connections.py +4 -4
  60. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/METADATA +2 -1
  61. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/RECORD +69 -62
  62. ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
  63. ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
  64. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
  65. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
  66. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
  67. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
  68. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
  69. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/WHEEL +0 -0
  70. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/entry_points.txt +0 -0
  71. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -22,7 +22,6 @@ ASSISTANT_EMBEDDINGS_API_BASE=https://us-south.ml.cloud.ibm.com
22
22
  #ASSISTANT_EMBEDDINGS_SPACE_ID=#Will default to WATSONX_SPACE_ID if specified
23
23
  ROUTING_LLM_API_BASE=https://us-south.ml.cloud.ibm.com
24
24
  ROUTING_LLM_MODEL_ID=watsonx/ibm/granite-8b-unified-api-model-v2
25
- AUTHORIZATION_URL=https://iam.test.cloud.ibm.com/identity/token
26
25
  #ROUTING_LLM_API_KEY=#Will default to WATSONX_APIKEY if specified
27
26
  #ROUTING_LLM_SPACE_ID=#Will default to WATSONX_SPACE_ID if specified
28
27
  ASSISTANT_INDEX_CHUNK_SIZE=1000
@@ -53,44 +52,59 @@ EVENT_BROKER_TTL="-1"
53
52
  # See get_default_registry_env_vars_by_dev_edition_source() in src/ibm_watsonx_orchestrate/cli/commands/server/server_command.py for more details.
54
53
  REGISTRY_URL=
55
54
 
56
- SERVER_TAG=05-06-2025-v1
55
+
56
+ SERVER_TAG=27-06-2025
57
57
  SERVER_REGISTRY=
58
58
 
59
- WORKER_TAG=03-06-2025
59
+ WORKER_TAG=27-06-2025
60
60
  WORKER_REGISTRY=
61
61
 
62
- AI_GATEWAY_TAG=03-06-2025
62
+ AI_GATEWAY_TAG=23-06-2025
63
63
  AI_GATEWAY_REGISTRY=
64
64
 
65
+ AGENT_GATEWAY_TAG=23-06-2025
66
+ AGENT_GATEWAY_REGISTRY=
67
+
65
68
  DB_REGISTRY=
66
69
  # If you build multiarch set all three of these to the same, we have a pr against main
67
70
  # to not have this separation, but we can merge it later
68
- DBTAG=07-06-2025
69
- AMDDBTAG=07-06-2025
70
- ARM64DBTAG=07-06-2025
71
+ DBTAG=24-06-2025-v1
72
+ AMDDBTAG=24-06-2025-v1
73
+ ARM64DBTAG=24-06-2025-v1
71
74
 
72
75
  UI_REGISTRY=
73
- UITAG=03-06-2025
76
+ UITAG=27-06-2025
74
77
 
75
78
  CM_REGISTRY=
76
- CM_TAG=13-05-2025
79
+ CM_TAG=27-06-2025
77
80
 
78
- TRM_TAG=11-06-2025-1-5-0
81
+ TRM_TAG=26-06-2025
79
82
  TRM_REGISTRY=
80
83
 
81
- TR_TAG=11-06-2025-1-5-0
84
+ TR_TAG=26-06-2025
82
85
  TR_REGISTRY=
83
86
 
84
- BUILDER_TAG=03-06-2025
87
+ BUILDER_TAG=26-06-2025-a
85
88
  BUILDER_REGISTRY=
86
89
 
87
- FLOW_RUNTIME_TAG=13-05-2025
90
+ FLOW_RUNTIME_TAG=23-06-2025
88
91
  FLOW_RUMTIME_REGISTRY=
89
92
 
93
+
94
+ AGENT_ANALYTICS_TAG=27-06-2025-v2
95
+ AGENT_ANALYTICS_REGISTRY=
96
+
97
+ JAEGER_PROXY_TAG=27-06-2025
98
+ JAEGER_PROXY_REGISTRY=
99
+
100
+ SOCKET_HANDLER_TAG=29-05-2025
101
+ SOCKET_HANDLER_REGISTRY=
102
+
103
+
90
104
  # END -- IMAGE REGISTRIES AND TAGS
91
105
 
92
106
  TAVILY_API_KEY=dummy_tavily_api_key
93
- PREFERRED_MODELS=meta-llama/llama-3-1-8b-instruct,meta-llama/llama-3-1-70b-instruct,meta-llama/llama-3-405b-instruct,meta-llama/llama-3-2-1b-instruct,meta-llama/llama-3-2-3b-instruct,meta-llama/llama-3-2-11b-vision-instruct,meta-llama/llama-3-2-90b-vision-instruct,mistralai/mistral-large,mistralai/mixtral-8x7b-instruct-v01,ibm/granite-3-2b-instruct,ibm/granite-3-8b-instruct
107
+ PREFERRED_MODELS=meta-llama/llama-3-2-90b-vision-instruct,meta-llama/llama-3-405b-instruct
94
108
  INCOMPATIBLE_MODELS=flan,embedding,cross-encoder,tinytimemixers
95
109
  #WATSONX_APIKEY= #Must define in .env
96
110
  WATSONX_URL=https://us-south.ml.cloud.ibm.com
@@ -119,7 +133,10 @@ TOOLS_RUNTIME_MANAGER_BASE_URL="http://tools-runtime-manager:8080"
119
133
  CONNECTION_SERVICE_BASE_URL="http://wxo-server-connection-manager:3001"
120
134
  AI_GATEWAY_BASE_URL="http://ai-gateway:8787/v1"
121
135
  AI_GATEWAY_ENABLED=True
122
-
136
+ AGENT_GATEWAY_URI="http://wxo-agent-gateway:8989"
137
+ DEFAULT_TENANT_ID=10000000-0000-0000-0000-000000000000
138
+ ES_USERNAME=elastic
139
+ ES_PASSWORD=changeme
123
140
  #To Prevent warnings
124
141
  VECTOR_STORE_PROVIDER=
125
142
  MILVUS_URI=
@@ -135,3 +152,11 @@ DB_CONN_LIFE=
135
152
  DB_MAX_IDLE_CONN=
136
153
  DB_MAX_CONN=
137
154
  SERVER_HOST=
155
+
156
+ # Use your machine's local IP address for external async tool communication.
157
+ CALLBACK_HOST_URL=
158
+
159
+ AGENTOPS_API_KEY_AUTH_ENABLED=true
160
+ AGENTOPS_API_KEY=qwertyuiop
161
+
162
+ RUNTIME_MANAGER_API_KEY=example
@@ -0,0 +1,12 @@
1
+ # Default tenant to use when tenant_id is not found
2
+ default_tenant: "default"
3
+
4
+ # Tenant configurations
5
+ tenants:
6
+ # Default tenant on ES instance 1
7
+ default:
8
+ store_type: elasticsearch
9
+ hostname: "http://elasticsearch:9200"
10
+ username: "elastic"
11
+ password: "changeme"
12
+ index_prefix: "default"
@@ -1,8 +1,8 @@
1
1
  from .constants import START, END, RESERVED
2
2
  from ..types import FlowContext, TaskData, TaskEventType
3
- from ..node import UserNode, AgentNode, StartNode, EndNode, PromptNode
3
+ from ..node import UserNode, AgentNode, StartNode, EndNode, PromptNode, ToolNode
4
4
  from .flow import Flow, CompiledFlow, FlowRun, FlowEvent, FlowEventType, FlowFactory, MatchPolicy, WaitPolicy, ForeachPolicy, Branch, Foreach, Loop
5
- from .decorators import user, flow_spec, flow
5
+ from .decorators import flow
6
6
  from ..data_map import Assignment, DataMap
7
7
 
8
8
 
@@ -20,6 +20,7 @@ __all__ = [
20
20
  "StartNode",
21
21
  "EndNode",
22
22
  "PromptNode",
23
+ "ToolNode",
23
24
  "Assignment",
24
25
  "DataMap",
25
26
 
@@ -0,0 +1,77 @@
1
+ """
2
+ A set of decorators to help define different Flow constructs.
3
+ """
4
+
5
+ import asyncio
6
+ from functools import wraps
7
+ import logging
8
+ import inspect
9
+ from typing import Callable, Optional, Sequence
10
+ from pydantic import BaseModel
11
+ from ..types import extract_node_spec, UserNodeSpec, FlowSpec
12
+
13
+ from .flow import FlowFactory, Flow
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class FlowWrapper:
19
+ def __init__(self, func, a_model):
20
+ self.func = func
21
+ self.a_model = a_model
22
+ wraps(func)(self) # Preserve metadata
23
+
24
+ def __call__(self, *args, **kwargs):
25
+ result = self.func(self.a_model)
26
+ if not isinstance(result, Flow):
27
+ raise ValueError("Return value must be of type Flow")
28
+ return result
29
+
30
+ def flow(*args,
31
+ name: Optional[str]=None,
32
+ display_name: Optional[str]=None,
33
+ description: str|None=None,
34
+ input_schema: type[BaseModel] | None = None,
35
+ output_schema: type[BaseModel] | None = None,
36
+ initiators: Sequence[str] = ()):
37
+ """Decorator to mark a function as a flow model builder."""
38
+
39
+ def decorator(func: Callable):
40
+ """
41
+ Decorator that takes a function as an argument and returns a wrapper function.
42
+ The wrapper function takes a single argument of type Flow and calls the original function with the created flow as an argument.
43
+ """
44
+
45
+ sig = inspect.signature(func)
46
+ if len(sig.parameters) != 1:
47
+ raise ValueError("Only one argument is allowed")
48
+ param = list(sig.parameters.values())[0]
49
+ if param.annotation != Flow:
50
+ raise ValueError("Argument must be of type Flow")
51
+ if sig.return_annotation != Flow:
52
+ raise ValueError("Return value must be of type Flow")
53
+
54
+ node_spec = extract_node_spec(func, name, description)
55
+ a_model = FlowFactory.create_flow(
56
+ name = node_spec.name,
57
+ display_name = display_name,
58
+ description = node_spec.description,
59
+ input_schema = input_schema,
60
+ output_schema = output_schema,
61
+ initiators = initiators)
62
+
63
+ # logger.info("Creating flow model: %s", a_model.spec.name)
64
+
65
+ # @wraps(func)
66
+ # def wrapper(*args, **kwargs):
67
+ # result = func(a_model)
68
+ # if not isinstance(result, Flow):
69
+ # raise ValueError("Return value must be of type Flow")
70
+ # return result
71
+
72
+ return FlowWrapper(func, a_model)
73
+
74
+ if len(args) == 1 and callable(args[0]):
75
+ return decorator(args[0])
76
+ else:
77
+ return decorator
@@ -47,8 +47,13 @@ class StreamConsumer:
47
47
  def deserialize_flow_event(byte_data: bytes) -> FlowEvent:
48
48
  """Deserialize byte data into a FlowEvent object."""
49
49
  # Decode the byte data
50
- decoded_data = byte_data[b'message'].decode('utf-8')
50
+ decoded_data = byte_data[b'data'].decode('utf-8') if b'data' in byte_data else None
51
+ if not decoded_data:
52
+ decoded_data = byte_data[b'message'].decode('utf-8') if b'message' in byte_data else None
51
53
 
54
+ if not decoded_data:
55
+ raise ValueError("No data in received event.")
56
+
52
57
  # Parse the JSON string into a dictionary
53
58
  parsed_data = json.loads(decoded_data)
54
59
 
@@ -20,6 +20,7 @@ from typing_extensions import Self
20
20
  from pydantic import BaseModel, Field, SerializeAsAny
21
21
  import yaml
22
22
  from ibm_watsonx_orchestrate.agent_builder.tools.python_tool import PythonTool
23
+ from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
23
24
  from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
24
25
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
25
26
  from ..types import (
@@ -36,7 +37,7 @@ from ..types import (
36
37
  )
37
38
 
38
39
  from ..data_map import DataMap
39
- from ..utils import _get_json_schema_obj, get_valid_name, import_flow_model
40
+ from ..utils import _get_json_schema_obj, get_valid_name, import_flow_model, _get_tool_request_body, _get_tool_response_body
40
41
 
41
42
  from .events import StreamConsumer
42
43
 
@@ -70,6 +71,7 @@ class Flow(Node):
70
71
  metadata: dict[str, str] = {}
71
72
  parent: Any = None
72
73
  _sequence_id: int = 0 # internal-id
74
+ _tool_client: ToolClient = None
73
75
 
74
76
  def __init__(self, **kwargs):
75
77
  super().__init__(**kwargs)
@@ -77,6 +79,9 @@ class Flow(Node):
77
79
  # extract data schemas
78
80
  self._refactor_node_to_schemaref(self)
79
81
 
82
+ # get Tool Client
83
+ self._tool_client = instantiate_client(ToolClient)
84
+
80
85
  def _find_topmost_flow(self) -> Self:
81
86
  if self.parent:
82
87
  return self.parent._find_topmost_flow()
@@ -104,7 +109,27 @@ class Flow(Node):
104
109
  # if there is already a schema with the same name, return it
105
110
  if title:
106
111
  if title in top_flow.schemas:
107
- return top_flow.schemas[title]
112
+ existing_schema = top_flow.schemas[title]
113
+ # we need a deep compare if the incoming schema and existing_schema is the same
114
+ # pydantic suppport nested comparison by default
115
+
116
+ schema.title = title
117
+
118
+ if schema == existing_schema:
119
+ return existing_schema
120
+ # we need to do a deep compare
121
+ incoming_model = schema.model_dump(exclude_none=True, exclude_unset=True)
122
+ existing_model = existing_schema.model_dump(exclude_none=True, exclude_unset=True)
123
+
124
+ # log the model
125
+ # logger.info(f"incoming_model: {incoming_model}")
126
+ # logger.info(f"existing_model: {existing_model}")
127
+
128
+ if incoming_model == existing_model:
129
+ return existing_schema
130
+
131
+ # else we need a new name, and create a new schema
132
+ title = title + "_" + str(self._next_sequence_id())
108
133
 
109
134
  # otherwise, create a deep copy of the schema, add it to the dictionary and return it
110
135
  if schema:
@@ -122,7 +147,7 @@ class Flow(Node):
122
147
  elif schema.aliasName:
123
148
  title = get_valid_name(schema.aliasName)
124
149
  else:
125
- title = "bo_" + uuid.uuid4().hex
150
+ title = "bo_" + str(self._next_sequence_id())
126
151
 
127
152
  if new_schema.type == "object":
128
153
  # iterate the properties and add schema recursively
@@ -193,7 +218,7 @@ class Flow(Node):
193
218
  required= spec.output_schema.required)
194
219
  spec.output_schema = self._add_schema_ref(json_obj, f"{spec.name}_output")
195
220
  elif spec.output_schema.type == "array":
196
- if spec.output_schema.items.type == "object":
221
+ if hasattr(spec.output_schema, "items") and hasattr(spec.output_schema.items, "type") and spec.output_schema.items.type == "object":
197
222
  schema_ref = self._add_schema_ref(spec.output_schema.items)
198
223
  spec.output_schema.items = JsonSchemaObjectRef(ref=f"{schema_ref.ref}")
199
224
 
@@ -265,23 +290,30 @@ class Flow(Node):
265
290
 
266
291
  if isinstance(tool, str):
267
292
  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)
293
+
294
+ if input_schema is None and output_schema is None:
295
+ # try to retrieve the schema from server
296
+ tool_specs: List[dict] = self._tool_client.get_draft_by_name(name)
297
+ if (tool_specs is None) or (len(tool_specs) == 0):
298
+ raise ValueError(f"tool '{name}' not found")
299
+
300
+ # use the first spec
301
+ tool_spec: ToolSpec = ToolSpec.model_validate(tool_specs[0])
302
+ # just pick the first one that is found
303
+ if hasattr(tool_spec, "input_schema"):
304
+ input_schema_obj = _get_json_schema_obj("input", tool_spec.input_schema, True)
305
+ if hasattr(tool_spec, "output_schema"):
306
+ output_schema_obj = _get_json_schema_obj("output", tool_spec.output_schema)
307
+ else:
308
+ input_schema_obj = _get_json_schema_obj("input", input_schema)
309
+ output_schema_obj = _get_json_schema_obj("output", output_schema)
270
310
 
271
311
  toolnode_spec = ToolNodeSpec(type = "tool",
272
312
  name = name,
273
313
  display_name = display_name,
274
314
  description = description,
275
- input_schema=ToolRequestBody(
276
- type=input_schema_obj.type,
277
- properties=input_schema_obj.properties,
278
- required=input_schema_obj.required,
279
- ) if input_schema is not None else None,
280
- output_schema=ToolResponseBody(
281
- type=output_schema_obj.type,
282
- properties=output_schema_obj.properties,
283
- required=output_schema_obj.required
284
- ) if output_schema is not None else None,
315
+ input_schema= _get_tool_request_body(input_schema_obj),
316
+ output_schema= _get_tool_response_body(output_schema_obj),
285
317
  output_schema_object = output_schema_obj,
286
318
  tool = tool)
287
319
 
@@ -341,16 +373,8 @@ class Flow(Node):
341
373
  agent=agent,
342
374
  message=message,
343
375
  guidelines=guidelines,
344
- input_schema=ToolRequestBody(
345
- type=input_schema_obj.type,
346
- properties=input_schema_obj.properties,
347
- required=input_schema_obj.required,
348
- ),
349
- output_schema=ToolResponseBody(
350
- type=output_schema_obj.type,
351
- properties=output_schema_obj.properties,
352
- required=output_schema_obj.required
353
- ),
376
+ input_schema=_get_tool_request_body(input_schema_obj),
377
+ output_schema=_get_tool_response_body(output_schema_obj),
354
378
  output_schema_object = output_schema_obj
355
379
  )
356
380
 
@@ -391,16 +415,8 @@ class Flow(Node):
391
415
  user_prompt=user_prompt,
392
416
  llm=llm,
393
417
  llm_parameters=llm_parameters,
394
- input_schema=ToolRequestBody(
395
- type=input_schema_obj.type,
396
- properties=input_schema_obj.properties,
397
- required=input_schema_obj.required,
398
- ),
399
- output_schema=ToolResponseBody(
400
- type=output_schema_obj.type,
401
- properties=output_schema_obj.properties,
402
- required=output_schema_obj.required
403
- ),
418
+ input_schema=_get_tool_request_body(input_schema_obj),
419
+ output_schema=_get_tool_response_body(output_schema_obj),
404
420
  output_schema_object = output_schema_obj
405
421
  )
406
422
 
@@ -543,21 +559,13 @@ class Flow(Node):
543
559
  },
544
560
  required = ["items"])
545
561
 
562
+ new_foreach_item_schema = self._add_schema(foreach_item_schema)
546
563
  spec = ForeachSpec(name = "foreach_" + str(self._next_sequence_id()),
547
- input_schema=ToolRequestBody(
548
- type=input_schema_obj.type,
549
- properties=input_schema_obj.properties,
550
- required=input_schema_obj.required,
551
- ),
552
- output_schema=ToolResponseBody(
553
- type=output_schema_obj.type,
554
- properties=output_schema_obj.properties,
555
- required=output_schema_obj.required
556
- ) if output_schema_obj is not None else None,
557
- item_schema = foreach_item_schema)
564
+ input_schema=_get_tool_request_body(input_schema_obj),
565
+ output_schema=_get_tool_response_body(output_schema_obj),
566
+ item_schema = new_foreach_item_schema)
558
567
  foreach_obj = Foreach(spec = spec, parent = self)
559
568
  foreach_node = self._add_node(foreach_obj)
560
- self._add_schema(foreach_item_schema)
561
569
 
562
570
  return cast(Flow, foreach_node)
563
571
 
@@ -580,16 +588,8 @@ class Flow(Node):
580
588
 
581
589
  loop_spec = LoopSpec(name = "loop_" + str(self._next_sequence_id()),
582
590
  evaluator = e,
583
- input_schema=ToolRequestBody(
584
- type=input_schema_obj.type,
585
- properties=input_schema_obj.properties,
586
- required=input_schema_obj.required,
587
- ) if input_schema_obj is not None else None,
588
- output_schema=ToolResponseBody(
589
- type=output_schema_obj.type,
590
- properties=output_schema_obj.properties,
591
- required=output_schema_obj.required
592
- ) if output_schema_obj is not None else None)
591
+ input_schema=_get_tool_request_body(input_schema_obj),
592
+ output_schema=_get_tool_response_body(output_schema_obj))
593
593
  while_loop = Loop(spec = loop_spec, parent = self)
594
594
  while_node = self._add_node(while_loop)
595
595
  return cast(Loop, while_node)
@@ -599,23 +599,15 @@ class Flow(Node):
599
599
  input_schema: type[BaseModel] |None=None,
600
600
  output_schema: type[BaseModel] |None=None) -> "UserFlow": # return a UserFlow object
601
601
 
602
- logger.warning("userflow is NOT working yet.")
602
+ raise ValueError("userflow is NOT supported yet and it's interface will change.")
603
603
 
604
604
  output_schema_obj = _get_json_schema_obj("output", output_schema)
605
605
  input_schema_obj = _get_json_schema_obj("input", input_schema)
606
606
 
607
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)
608
+ input_schema=_get_tool_request_body(input_schema_obj),
609
+ output_schema=_get_tool_response_body(output_schema_obj),
610
+ owners = owners)
619
611
  userflow_obj = UserFlow(spec = spec, parent = self)
620
612
  userflow_node = self._add_node(userflow_obj)
621
613
 
@@ -679,8 +671,9 @@ class Flow(Node):
679
671
 
680
672
  # Deploy flow to the engine
681
673
  model = self.to_json()
682
- await import_flow_model(model)
674
+ tool_id = await import_flow_model(model)
683
675
 
676
+ compiled_flow.flow_id = tool_id
684
677
  compiled_flow.deployed = True
685
678
 
686
679
  return compiled_flow
@@ -746,6 +739,7 @@ class FlowRun(BaseModel):
746
739
  name: str | None = None
747
740
  id: str = None
748
741
  flow: Flow
742
+ deployed_flow_id: str
749
743
  status: FlowRunStatus = FlowRunStatus.NOT_STARTED
750
744
  output: Any = None
751
745
  error: Any = None
@@ -765,10 +759,12 @@ class FlowRun(BaseModel):
765
759
 
766
760
  # Start the flow
767
761
  client:TempusClient = instantiate_client(client=TempusClient)
768
- ack = client.arun_flow(self.flow.spec.name,input_data)
762
+ logger.info(f"Launching flow instance...")
763
+ ack = client.arun_flow(self.deployed_flow_id,input_data)
769
764
  self.id=ack["instance_id"]
770
765
  self.name = f"{self.flow.spec.name}:{self.id}"
771
766
  self.status = FlowRunStatus.IN_PROGRESS
767
+ logger.info(f"Flow instance `{self.name}` started.")
772
768
 
773
769
  # Listen for events
774
770
  consumer = StreamConsumer(self.id)
@@ -781,6 +777,11 @@ class FlowRun(BaseModel):
781
777
 
782
778
  self._update_status(event)
783
779
 
780
+ if event.kind == FlowEventType.ON_FLOW_END:
781
+ logger.info(f"Flow instance `{self.name}` completed.")
782
+ elif event.kind == FlowEventType.ON_FLOW_ERROR:
783
+ logger.error(f"Flow instance `{self.name}` failed with error: {event.error}")
784
+
784
785
  yield event
785
786
 
786
787
  def _update_status(self, event:FlowEvent):
@@ -794,7 +795,7 @@ class FlowRun(BaseModel):
794
795
 
795
796
 
796
797
  if self.debug:
797
- logger.debug(f"Flow instance `{self.name}` status change: `{self.status}`")
798
+ logger.debug(f"Flow instance `{self.name}` status change: `{self.status}`. \nEvent: {event}")
798
799
 
799
800
  async def _arun(self, input_data: dict=None, **kwargs):
800
801
 
@@ -822,7 +823,7 @@ class FlowRun(BaseModel):
822
823
  def _on_flow_end(self, event:FlowEvent):
823
824
 
824
825
  self.status = FlowRunStatus.COMPLETED
825
- self.output = event.context.data["output"] if "output" in event.context.data else None
826
+ self.output = event.context.data.output
826
827
 
827
828
  if self.debug:
828
829
  logger.debug(f"Flow run `{self.name}`: on_complete handler called. Output: {self.output}")
@@ -846,6 +847,7 @@ class FlowRun(BaseModel):
846
847
  class CompiledFlow(BaseModel):
847
848
  '''A compiled version of the flow'''
848
849
  flow: Flow
850
+ flow_id: str | None = None
849
851
  deployed: bool = False
850
852
 
851
853
  async def invoke(self, input_data:dict=None, on_flow_end_handler: Callable=None, on_flow_error_handler: Callable=None, debug:bool=False, **kwargs) -> FlowRun:
@@ -868,7 +870,7 @@ class CompiledFlow(BaseModel):
868
870
  if self.deployed is False:
869
871
  raise ValueError("Flow has not been deployed yet. Please deploy the flow before invoking it by using the Flow.compile_deploy() function.")
870
872
 
871
- flow_run = FlowRun(flow=self.flow, on_flow_end_handler=on_flow_end_handler, on_flow_error_handler=on_flow_error_handler, debug=debug, **kwargs)
873
+ flow_run = FlowRun(flow=self.flow, deployed_flow_id=self.flow_id, on_flow_end_handler=on_flow_end_handler, on_flow_error_handler=on_flow_error_handler, debug=debug, **kwargs)
872
874
  asyncio.create_task(flow_run._arun(input_data=input_data, **kwargs))
873
875
  return flow_run
874
876
 
@@ -890,7 +892,7 @@ class CompiledFlow(BaseModel):
890
892
  if self.deployed is False:
891
893
  raise ValueError("Flow has not been deployed yet. Please deploy the flow before invoking it by using the Flow.compile_deploy() function.")
892
894
 
893
- flow_run = FlowRun(flow=self.flow, debug=debug)
895
+ flow_run = FlowRun(flow=self.flow, deployed_flow_id=self.flow_id, debug=debug)
894
896
  async for event in flow_run._arun_events(input_data=input_data, filters=filters):
895
897
  yield (event, flow_run)
896
898
 
@@ -938,16 +940,8 @@ class FlowFactory(BaseModel):
938
940
  display_name=display_name,
939
941
  description=description,
940
942
  initiators=initiators,
941
- input_schema=ToolRequestBody(
942
- type=input_schema_obj.type,
943
- properties=input_schema_obj.properties,
944
- required=input_schema_obj.required,
945
- ) if input_schema_obj else None,
946
- output_schema=ToolResponseBody(
947
- type=output_schema_obj.type,
948
- properties=output_schema_obj.properties,
949
- required=output_schema_obj.required
950
- ) if output_schema_obj else None,
943
+ input_schema=_get_tool_request_body(input_schema_obj),
944
+ output_schema=_get_tool_response_body(output_schema_obj),
951
945
  output_schema_object = output_schema_obj
952
946
  )
953
947
 
@@ -974,6 +968,9 @@ class Branch(FlowControl):
974
968
  Returns:
975
969
  Self: The current node.
976
970
  '''
971
+ if kind == MatchPolicy.ANY_MATCH:
972
+ raise ValueError("Branch with policy ANY_MATCH is not supported yet.")
973
+
977
974
  self.spec.match_policy = kind
978
975
  return self
979
976
 
@@ -1286,12 +1283,8 @@ class UserFlow(Flow):
1286
1283
  display_name=display_name,
1287
1284
  description=description,
1288
1285
  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
- ),
1286
+ input_schema=_get_tool_request_body(output_schema_obj),
1287
+ output_schema=_get_tool_response_body(output_schema_obj),
1295
1288
  text=text,
1296
1289
  output_schema_object = output_schema_obj
1297
1290
  )
@@ -14,7 +14,7 @@ from langchain_core.tools.base import create_schema_from_function
14
14
  from langchain_core.utils.json_schema import dereference_refs
15
15
 
16
16
  from ibm_watsonx_orchestrate.agent_builder.tools import PythonTool
17
- from ibm_watsonx_orchestrate.experimental.flow_builder.flows.constants import ANY_USER
17
+ from ibm_watsonx_orchestrate.flow_builder.flows.constants import ANY_USER
18
18
  from ibm_watsonx_orchestrate.agent_builder.tools.types import (
19
19
  ToolSpec, ToolRequestBody, ToolResponseBody, JsonSchemaObject
20
20
  )
@@ -29,9 +29,9 @@ class SchemaRef(BaseModel):
29
29
 
30
30
  ref: str = Field(description="The id of the schema to be used.", serialization_alias="$ref")
31
31
 
32
- def _assign_attribute(obj, attr_name, schema):
32
+ def _assign_attribute(model_spec, attr_name, schema):
33
33
  if hasattr(schema, attr_name) and (getattr(schema, attr_name) is not None):
34
- obj[attr_name] = getattr(schema, attr_name)
34
+ model_spec[attr_name] = getattr(schema, attr_name)
35
35
 
36
36
  def _to_json_from_json_schema(schema: JsonSchemaObject) -> dict[str, Any]:
37
37
  model_spec = {}
@@ -62,12 +62,14 @@ def _to_json_from_json_schema(schema: JsonSchemaObject) -> dict[str, Any]:
62
62
  model_spec["anyOf"] = [_to_json_from_json_schema(schema) for schema in schema.anyOf]
63
63
 
64
64
  _assign_attribute(model_spec, "in_field", schema)
65
+ _assign_attribute(model_spec, "in", schema)
65
66
  _assign_attribute(model_spec, "aliasName", schema)
66
67
 
67
68
  if hasattr(schema, 'model_extra') and schema.model_extra:
68
69
  # for each extra fiels, add it to the model spec
69
70
  for key, value in schema.model_extra.items():
70
- model_spec[key] = value
71
+ if value is not None:
72
+ model_spec[key] = value
71
73
 
72
74
  if isinstance(schema, JsonSchemaObjectRef):
73
75
  model_spec["$ref"] = schema.ref
@@ -635,6 +637,11 @@ class TaskEventType(Enum):
635
637
  ON_TASK_STREAM = "on_task_stream"
636
638
  ON_TASK_ERROR = "on_task_error"
637
639
 
640
+ class FlowData(BaseModel):
641
+ '''This class represents the data that is passed between tasks in a flow.'''
642
+ input: dict[str, Any] = Field(default_factory=dict)
643
+ output: dict[str, Any] = Field(default_factory=dict)
644
+
638
645
  class FlowContext(BaseModel):
639
646
 
640
647
  name: str | None = None # name of the process or task
@@ -642,10 +649,12 @@ class FlowContext(BaseModel):
642
649
  flow_id: str | None = None # id of the flow, this is at the flow definition level
643
650
  instance_id: str | None = None
644
651
  thread_id: str | None = None
652
+ correlation_id: str | None = None
653
+ tenant_id: str | None = None
645
654
  parent_context: Any | None = None
646
655
  child_context: List["FlowContext"] | None = None
647
656
  metadata: dict = Field(default_factory=dict[str, Any])
648
- data: dict = Field(default_factory=dict[str, Any])
657
+ data: Optional[FlowData] = None
649
658
 
650
659
  def get(self, key: str) -> Any:
651
660
 
@@ -655,7 +664,7 @@ class FlowContext(BaseModel):
655
664
  if self.parent_context:
656
665
  pc = cast(FlowContext, self.parent_conetxt)
657
666
  return pc.get(key)
658
-
667
+
659
668
  class FlowEventType(Enum):
660
669
 
661
670
  ON_FLOW_START = "on_flow_start"