ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.7.0a0__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 +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +33 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
- ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
- ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +122 -17
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -58
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +224 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +158 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +162 -27
- ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +37 -22
- ibm_watsonx_orchestrate/cli/config.py +2 -0
- ibm_watsonx_orchestrate/cli/main.py +6 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
- ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/credentials.py +4 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
- ibm_watsonx_orchestrate/client/service_instance.py +42 -1
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +37 -2
- ibm_watsonx_orchestrate/docker/compose-lite.yml +425 -81
- ibm_watsonx_orchestrate/docker/default.env +53 -15
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
- ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/METADATA +2 -1
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/RECORD +68 -61
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.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,68 @@ 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
|
-
|
55
|
+
|
56
|
+
SERVER_TAG=26-06-2025
|
57
57
|
SERVER_REGISTRY=
|
58
58
|
|
59
|
-
WORKER_TAG=
|
59
|
+
WORKER_TAG=26-06-2025
|
60
60
|
WORKER_REGISTRY=
|
61
61
|
|
62
|
-
AI_GATEWAY_TAG=
|
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=
|
69
|
-
AMDDBTAG=
|
70
|
-
ARM64DBTAG=
|
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=
|
76
|
+
UITAG=26-06-2025
|
74
77
|
|
75
78
|
CM_REGISTRY=
|
76
|
-
CM_TAG=
|
79
|
+
CM_TAG=18-06-2025
|
77
80
|
|
78
|
-
TRM_TAG=
|
81
|
+
TRM_TAG=25-06-2025
|
79
82
|
TRM_REGISTRY=
|
80
83
|
|
81
|
-
TR_TAG=
|
84
|
+
TR_TAG=25-06-2025
|
82
85
|
TR_REGISTRY=
|
83
86
|
|
84
|
-
BUILDER_TAG=
|
87
|
+
BUILDER_TAG=26-06-2025-a
|
85
88
|
BUILDER_REGISTRY=
|
86
89
|
|
87
|
-
FLOW_RUNTIME_TAG=
|
90
|
+
FLOW_RUNTIME_TAG=23-06-2025
|
88
91
|
FLOW_RUMTIME_REGISTRY=
|
89
92
|
|
93
|
+
|
94
|
+
AGENT_ANALYTICS_TAG=24-06-2025
|
95
|
+
AGENT_ANALYTICS_REGISTRY=
|
96
|
+
|
97
|
+
JAEGER_PROXY_TAG=24-06-2025
|
98
|
+
JAEGER_PROXY_REGISTRY=
|
99
|
+
|
100
|
+
SOCKET_HANDLER_TAG=29-05-2025
|
101
|
+
SOCKET_HANDLER_REGISTRY=
|
102
|
+
|
103
|
+
# IBM Document Processing
|
104
|
+
WDU_TAG=2.5.0-rc.2
|
105
|
+
WDU_REGISTRY=
|
106
|
+
|
107
|
+
DOCPROC_DPS_TAG=20250610-183301-248-865fbc1
|
108
|
+
DOCPROC_LLMSERVICE_TAG=20250604-192056-107-e1d4d66
|
109
|
+
DOCPROC_CACHE_TAG=20250610-214940-68-f3258f4
|
110
|
+
DOCPROC_DPI_TAG=20250624-155521-230-48b7f28b
|
111
|
+
DOCPROC_REGISTRY=
|
112
|
+
|
90
113
|
# END -- IMAGE REGISTRIES AND TAGS
|
91
114
|
|
92
115
|
TAVILY_API_KEY=dummy_tavily_api_key
|
93
|
-
PREFERRED_MODELS=meta-llama/llama-3-
|
116
|
+
PREFERRED_MODELS=meta-llama/llama-3-2-90b-vision-instruct,meta-llama/llama-3-405b-instruct
|
94
117
|
INCOMPATIBLE_MODELS=flan,embedding,cross-encoder,tinytimemixers
|
95
118
|
#WATSONX_APIKEY= #Must define in .env
|
96
119
|
WATSONX_URL=https://us-south.ml.cloud.ibm.com
|
@@ -119,7 +142,10 @@ TOOLS_RUNTIME_MANAGER_BASE_URL="http://tools-runtime-manager:8080"
|
|
119
142
|
CONNECTION_SERVICE_BASE_URL="http://wxo-server-connection-manager:3001"
|
120
143
|
AI_GATEWAY_BASE_URL="http://ai-gateway:8787/v1"
|
121
144
|
AI_GATEWAY_ENABLED=True
|
122
|
-
|
145
|
+
AGENT_GATEWAY_URI="http://wxo-agent-gateway:8989"
|
146
|
+
DEFAULT_TENANT_ID=10000000-0000-0000-0000-000000000000
|
147
|
+
ES_USERNAME=elastic
|
148
|
+
ES_PASSWORD=changeme
|
123
149
|
#To Prevent warnings
|
124
150
|
VECTOR_STORE_PROVIDER=
|
125
151
|
MILVUS_URI=
|
@@ -135,3 +161,15 @@ DB_CONN_LIFE=
|
|
135
161
|
DB_MAX_IDLE_CONN=
|
136
162
|
DB_MAX_CONN=
|
137
163
|
SERVER_HOST=
|
164
|
+
|
165
|
+
# Use your machine's local IP address for external async tool communication.
|
166
|
+
CALLBACK_HOST_URL=
|
167
|
+
|
168
|
+
AGENTOPS_API_KEY_AUTH_ENABLED=true
|
169
|
+
AGENTOPS_API_KEY=qwertyuiop
|
170
|
+
|
171
|
+
RUNTIME_MANAGER_API_KEY=example
|
172
|
+
|
173
|
+
|
174
|
+
# IBM Document Processing
|
175
|
+
SERVICE_URL=https://wxo-doc-processing-cache:8080
|
@@ -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
|
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'
|
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
|
-
|
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_" +
|
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
|
-
|
269
|
-
|
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=
|
276
|
-
|
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=
|
345
|
-
|
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=
|
395
|
-
|
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=
|
548
|
-
|
549
|
-
|
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=
|
584
|
-
|
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
|
-
|
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
|
-
|
609
|
-
|
610
|
-
|
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
|
-
|
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
|
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=
|
942
|
-
|
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=
|
1290
|
-
output_schema=
|
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.
|
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(
|
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
|
-
|
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
|
-
|
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:
|
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"
|