ibm-watsonx-orchestrate 1.2.0__py3-none-any.whl → 1.4.2__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/types.py +6 -1
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py +16 -3
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +4 -20
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +13 -15
- ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +32 -0
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +42 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +4 -2
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +29 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +273 -12
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +79 -39
- ibm_watsonx_orchestrate/cli/commands/models/env_file_model_provider_mapper.py +180 -0
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +194 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +117 -48
- ibm_watsonx_orchestrate/cli/commands/server/types.py +105 -0
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +55 -7
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +123 -42
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +22 -1
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +209 -25
- ibm_watsonx_orchestrate/cli/init_helper.py +43 -0
- ibm_watsonx_orchestrate/cli/main.py +3 -1
- ibm_watsonx_orchestrate/client/agents/agent_client.py +4 -1
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +5 -1
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +5 -1
- ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +2 -6
- ibm_watsonx_orchestrate/client/base_api_client.py +5 -2
- ibm_watsonx_orchestrate/client/connections/connections_client.py +15 -21
- ibm_watsonx_orchestrate/client/model_policies/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +47 -0
- ibm_watsonx_orchestrate/client/model_policies/types.py +36 -0
- ibm_watsonx_orchestrate/client/models/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/models/models_client.py +46 -0
- ibm_watsonx_orchestrate/client/models/types.py +177 -0
- ibm_watsonx_orchestrate/client/toolkit/toolkit_client.py +15 -6
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +40 -0
- ibm_watsonx_orchestrate/client/tools/tool_client.py +8 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +68 -13
- ibm_watsonx_orchestrate/docker/default.env +22 -12
- ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -1
- ibm_watsonx_orchestrate/experimental/flow_builder/__init__.py +0 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/__init__.py +41 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/constants.py +17 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/data_map.py +91 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +143 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/events.py +72 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/flow.py +1288 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/node.py +97 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/resources/flow_status.openapi.yml +98 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/types.py +492 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +113 -0
- ibm_watsonx_orchestrate/utils/utils.py +5 -2
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/METADATA +6 -2
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/RECORD +59 -36
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -21,6 +21,10 @@ class ToolClient(BaseAPIClient):
|
|
21
21
|
def upload_tools_artifact(self, tool_id: str, file_path: str) -> dict:
|
22
22
|
return self._post(f"/tools/{tool_id}/upload", files={"file": (f"{tool_id}.zip", open(file_path, "rb"), "application/zip", {"Expires": "0"})})
|
23
23
|
|
24
|
+
def download_tools_artifact(self, tool_id: str) -> bytes:
|
25
|
+
response = self._get(f"/tools/{tool_id}/download", return_raw=True)
|
26
|
+
return response.content
|
27
|
+
|
24
28
|
def get_draft_by_name(self, tool_name: str) -> List[dict]:
|
25
29
|
return self.get_drafts_by_names([tool_name])
|
26
30
|
|
@@ -39,3 +43,7 @@ class ToolClient(BaseAPIClient):
|
|
39
43
|
if e.response.status_code == 404 and "not found with the given name" in e.response.text:
|
40
44
|
return ""
|
41
45
|
raise(e)
|
46
|
+
|
47
|
+
def get_drafts_by_ids(self, tool_ids: List[str]) -> List[dict]:
|
48
|
+
formatted_tool_ids = [f"ids={x}" for x in tool_ids]
|
49
|
+
return self._get(f"/tools?{'&'.join(formatted_tool_ids)}")
|
@@ -49,6 +49,17 @@ services:
|
|
49
49
|
ports:
|
50
50
|
- 3001:3001
|
51
51
|
|
52
|
+
ai-gateway:
|
53
|
+
image: ${AI_GATEWAY_REGISTRY:-us.icr.io/watson-orchestrate-private}/ai-gateway:${AI_GATEWAY_TAG:-latest}
|
54
|
+
platform: linux/amd64
|
55
|
+
restart: unless-stopped
|
56
|
+
ports:
|
57
|
+
- "8787:8787"
|
58
|
+
environment:
|
59
|
+
WATSONX_API_KEY: ${WATSONX_APIKEY}
|
60
|
+
WATSONX_URL: ${WATSONX_URL}
|
61
|
+
WATSONX_SPACE_ID: ${WATSONX_SPACE_ID}
|
62
|
+
|
52
63
|
ui:
|
53
64
|
image: ${UI_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-chat:${UITAG:-latest}
|
54
65
|
platform: linux/amd64
|
@@ -95,6 +106,7 @@ services:
|
|
95
106
|
environment:
|
96
107
|
WXO_DEPLOYMENT_TYPE: 'laptop'
|
97
108
|
AGENT_RUNTIME_ENDPOINT: http://wxo-server:4321
|
109
|
+
TEMPUS_RUNTIME_ENDPOINT: http://wxo-tempus-runtime:9044
|
98
110
|
command: 'npm start'
|
99
111
|
ports:
|
100
112
|
- '4025:4025'
|
@@ -210,14 +222,21 @@ services:
|
|
210
222
|
volumes:
|
211
223
|
- ./sdk:/packages
|
212
224
|
- tools:/tools
|
225
|
+
|
213
226
|
command: >
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
227
|
+
bash -c '
|
228
|
+
export PYTHONPATH="/tmp/monkey:$PYTHONPATH"
|
229
|
+
mkdir -p /tmp/monkey &&
|
230
|
+
|
231
|
+
echo -e "from uvicorn.supervisors.multiprocess import Process\n_original_is_alive = Process.is_alive\ndef patched_is_alive(self, timeout: float = 30):\n return _original_is_alive(self, timeout=30)\nProcess.is_alive = patched_is_alive" > /tmp/monkey/sitecustomize.py &&
|
232
|
+
|
233
|
+
uvicorn --host 0.0.0.0 --port 4321 --log-config /app/config/logs/log_conf.yaml --log-level debug --workers 5 wo_archer.api.main:app &
|
234
|
+
|
235
|
+
for i in {1..40}; do curl --silent --fail http://127.0.0.1:4321/health/alive && echo "[INFO] HTTP Service ready" && break || echo "[INFO] Waiting for HTTP service..." && sleep 10; done;
|
236
|
+
curl --silent --fail http://127.0.0.1:4321/health/alive || (echo "[ERROR] HTTP service failed to start" && exit 1);
|
237
|
+
|
238
|
+
wait
|
239
|
+
'
|
221
240
|
depends_on:
|
222
241
|
wxo-server-redis:
|
223
242
|
condition: service_started
|
@@ -294,6 +313,15 @@ services:
|
|
294
313
|
MILVUS_DB_NAME: default
|
295
314
|
MILVUS_USERNAME: root
|
296
315
|
MILVUS_PASSWORD: Milvus
|
316
|
+
WO_API_KEY: ${WO_API_KEY}
|
317
|
+
WO_USERNAME: ${WO_USERNAME}
|
318
|
+
WO_PASSWORD: ${WO_PASSWORD}
|
319
|
+
WO_INSTANCE: ${WO_INSTANCE}
|
320
|
+
USE_SAAS_ML_TOOLS_RUNTIME: ${USE_SAAS_ML_TOOLS_RUNTIME}
|
321
|
+
AUTHORIZATION_URL: ${AUTHORIZATION_URL}
|
322
|
+
WO_AUTH_TYPE: ${WO_AUTH_TYPE}
|
323
|
+
AI_GATEWAY_BASE_URL: ${AI_GATEWAY_BASE_URL}
|
324
|
+
AI_GATEWAY_ENABLED : ${AI_GATEWAY_ENABLED}
|
297
325
|
|
298
326
|
wxo-server-worker:
|
299
327
|
image: ${WORKER_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-server-conversation_controller:${WORKER_TAG:-latest}
|
@@ -311,6 +339,8 @@ services:
|
|
311
339
|
- ./sdk:/packages
|
312
340
|
- tools:/tools
|
313
341
|
environment:
|
342
|
+
AI_GATEWAY_ENABLED : ${AI_GATEWAY_ENABLED}
|
343
|
+
AI_GATEWAY_BASE_URL: ${AI_GATEWAY_BASE_URL}
|
314
344
|
IS_WXO_LITE: 'TRUE'
|
315
345
|
TRM_BASE_URL: http://tools-runtime-manager:8080
|
316
346
|
AGENT_STEP_DETAILS: redis://wxo-server-redis:6379/0
|
@@ -372,6 +402,13 @@ services:
|
|
372
402
|
MILVUS_DB_NAME: default
|
373
403
|
MILVUS_USERNAME: root
|
374
404
|
MILVUS_PASSWORD: Milvus
|
405
|
+
WO_API_KEY: ${WO_API_KEY}
|
406
|
+
WO_USERNAME: ${WO_USERNAME}
|
407
|
+
WO_PASSWORD: ${WO_PASSWORD}
|
408
|
+
WO_INSTANCE: ${WO_INSTANCE}
|
409
|
+
USE_SAAS_ML_TOOLS_RUNTIME: ${USE_SAAS_ML_TOOLS_RUNTIME}
|
410
|
+
AUTHORIZATION_URL: ${AUTHORIZATION_URL}
|
411
|
+
WO_AUTH_TYPE: ${WO_AUTH_TYPE}
|
375
412
|
|
376
413
|
tools-runtime-manager:
|
377
414
|
image: ${TRM_REGISTRY:-us.icr.io/watson-orchestrate-private}/tools-runtime-manager:${TRM_TAG:-latest}
|
@@ -407,6 +444,12 @@ services:
|
|
407
444
|
VOLUME_MOUNT_PATH: "/shared-data"
|
408
445
|
TOOLS_RUNTIME_MANAGER_BASE_URL: ${TOOLS_RUNTIME_MANAGER_BASE_URL}
|
409
446
|
CONNECTION_SERVICE_BASE_URL: http://wxo-server-connection-manager:3001
|
447
|
+
STORAGE_S3_BUCKET: ${STORAGE_S3_BUCKET:-wxo-server-storage-bucket}
|
448
|
+
STORAGE_S3_ENDPOINT: http://wxo-server-minio:9000
|
449
|
+
STORAGE_S3_FORCE_PATH_STYLE: 'true'
|
450
|
+
STORAGE_S3_REGION: us-east-1
|
451
|
+
AWS_ACCESS_KEY_ID: ${MINIO_ROOT_USER:-minioadmin}
|
452
|
+
AWS_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:-watsonxorchestrate}
|
410
453
|
extra_hosts:
|
411
454
|
- "host.docker.internal:host-gateway"
|
412
455
|
|
@@ -432,6 +475,12 @@ services:
|
|
432
475
|
DB_PASSWORD: ${DB_PASSWORD:-postgres}
|
433
476
|
DB_NAME: ${DB_NAME:-postgres}
|
434
477
|
DB_SSLMODE: ${DB_SSLMODE:-disable} # Disable SSL if not configured
|
478
|
+
STORAGE_S3_BUCKET: ${STORAGE_S3_BUCKET:-wxo-server-storage-bucket}
|
479
|
+
STORAGE_S3_ENDPOINT: http://wxo-server-minio:9000
|
480
|
+
STORAGE_S3_FORCE_PATH_STYLE: 'true'
|
481
|
+
STORAGE_S3_REGION: us-east-1
|
482
|
+
AWS_ACCESS_KEY_ID: ${MINIO_ROOT_USER:-minioadmin}
|
483
|
+
AWS_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:-watsonxorchestrate}
|
435
484
|
|
436
485
|
########################
|
437
486
|
# LANGFUSE dependencies
|
@@ -531,11 +580,8 @@ services:
|
|
531
580
|
wxo-tempus-runtime:
|
532
581
|
image: ${FLOW_RUMTIME_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-tempus-runtime:${FLOW_RUNTIME_TAG:-latest}
|
533
582
|
restart: unless-stopped
|
534
|
-
platform: linux/amd64
|
535
|
-
profiles: [with-tempus-runtime]
|
536
583
|
environment:
|
537
584
|
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
538
|
-
COMMON_CONFIG_FILE: "/shared/common-config.yaml"
|
539
585
|
SERVER_MODE: production
|
540
586
|
SERVER_ENVIRONMENT: SDK
|
541
587
|
SERVER_HTTP_PORT: "9044"
|
@@ -557,6 +603,17 @@ services:
|
|
557
603
|
PG_DATABASE: ${POSTGRES_USER:-postgres}
|
558
604
|
USER_MGMT_BASE_URL: "" # TODO
|
559
605
|
WXO_SERVER_BASE_URL: http://wxo-server:4321
|
606
|
+
TRM_SERVER_BASE_URL: http://tools-runtime-manager:8080
|
607
|
+
CONNECTION_MANAGER_SERVER_BASE_URL: http://wxo-server-connection-manager:3001
|
608
|
+
REDIS_ENDPOINT: wxo-server-redis:6379
|
609
|
+
REDIS_USER:
|
610
|
+
REDIS_PASSWORD:
|
611
|
+
REDIS_TLS: false
|
612
|
+
LANGFUSE_ENABLED: ${LANGFUSE_ENABLED:-false}
|
613
|
+
LANGFUSE_HOST: ${LANGFUSE_HOST:-http://langfuse-web:3000}
|
614
|
+
LANGFUSE_PUBLIC_KEY: ${LANGFUSE_PUBLIC_KEY:-pk-lf-7417757e-d6df-421b-957e-683b76acb5df}
|
615
|
+
LANGFUSE_SECRET_KEY: ${LANGFUSE_PRIVATE_KEY:-sk-lf-7bc4da63-7b2b-40c0-b5eb-1e0cf64f9af2}
|
616
|
+
LOG_LEVEL: info
|
560
617
|
healthcheck:
|
561
618
|
test: curl -k http://localhost:9044/readiness --fail
|
562
619
|
interval: 5s
|
@@ -564,8 +621,6 @@ services:
|
|
564
621
|
retries: 5
|
565
622
|
ports:
|
566
623
|
- 9044:9044
|
567
|
-
volumes:
|
568
|
-
- ./tempus/common-config.yaml:/shared/common-config.yaml
|
569
624
|
depends_on:
|
570
625
|
- wxo-server-db
|
571
626
|
|
@@ -595,4 +650,4 @@ volumes:
|
|
595
650
|
networks:
|
596
651
|
default:
|
597
652
|
name: wxo-server
|
598
|
-
|
653
|
+
|
@@ -46,38 +46,46 @@ CELERY_RESULTS_TTL="3600"
|
|
46
46
|
EVENT_BROKER_TTL="-1"
|
47
47
|
|
48
48
|
# START -- IMAGE REGISTRIES AND TAGS
|
49
|
-
#
|
49
|
+
# The registry URL to pull the private images from, including the name of the repository in the registry.
|
50
|
+
# e.g. cp.icr.io/cp/wxo-lite
|
51
|
+
# If the registry URL is not set here or by the user, then it will be set automatically based on the value of WO_DEVELOPER_EDITION_SOURCE
|
52
|
+
# The *_REGISTRY variables are used to set the registry URL for each component. If not set, the URL here will be used.
|
53
|
+
# See get_default_registry_env_vars_by_dev_edition_source() in src/ibm_watsonx_orchestrate/cli/commands/server/server_command.py for more details.
|
50
54
|
REGISTRY_URL=
|
51
55
|
|
52
|
-
SERVER_TAG=
|
56
|
+
SERVER_TAG=22-05-2025
|
53
57
|
SERVER_REGISTRY=
|
54
58
|
|
55
|
-
WORKER_TAG=
|
59
|
+
WORKER_TAG=22-05-2025
|
56
60
|
WORKER_REGISTRY=
|
57
61
|
|
62
|
+
AI_GATEWAY_TAG=20-05-2025
|
63
|
+
AI_GATEWAY_REGISTRY=
|
64
|
+
|
58
65
|
DB_REGISTRY=
|
59
66
|
# If you build multiarch set all three of these to the same, we have a pr against main
|
60
67
|
# to not have this separation, but we can merge it later
|
61
|
-
DBTAG=
|
62
|
-
AMDDBTAG=
|
63
|
-
ARM64DBTAG=
|
68
|
+
DBTAG=21-05-2025
|
69
|
+
AMDDBTAG=21-05-2025
|
70
|
+
ARM64DBTAG=21-05-2025
|
64
71
|
|
65
72
|
UI_REGISTRY=
|
66
|
-
UITAG=
|
73
|
+
UITAG=23-05-2025
|
67
74
|
|
68
75
|
CM_REGISTRY=
|
69
|
-
CM_TAG=
|
76
|
+
CM_TAG=13-05-2025
|
70
77
|
|
71
|
-
TRM_TAG=
|
78
|
+
TRM_TAG=23-05-2025
|
72
79
|
TRM_REGISTRY=
|
73
80
|
|
74
|
-
TR_TAG=
|
81
|
+
TR_TAG=23-05-2025
|
75
82
|
TR_REGISTRY=
|
76
83
|
|
84
|
+
BUILDER_TAG=21-05-2025
|
77
85
|
BUILDER_REGISTRY=
|
78
|
-
BUILDER_TAG=02-05-2025
|
79
86
|
|
80
|
-
|
87
|
+
|
88
|
+
FLOW_RUNTIME_TAG=13-05-2025
|
81
89
|
FLOW_RUMTIME_REGISTRY=
|
82
90
|
|
83
91
|
# END -- IMAGE REGISTRIES AND TAGS
|
@@ -110,6 +118,8 @@ WXO_BASE_URL=http://wxo-server:4321
|
|
110
118
|
RUNTIME_MANAGER_API_KEY="testapikey"
|
111
119
|
TOOLS_RUNTIME_MANAGER_BASE_URL="http://tools-runtime-manager:8080"
|
112
120
|
CONNECTION_SERVICE_BASE_URL="http://wxo-server-connection-manager:3001"
|
121
|
+
AI_GATEWAY_BASE_URL="http://ai-gateway:8787/v1"
|
122
|
+
AI_GATEWAY_ENABLED=True
|
113
123
|
|
114
124
|
#To Prevent warnings
|
115
125
|
VECTOR_STORE_PROVIDER=
|
@@ -1 +1 @@
|
|
1
|
-
NODE_LOG_LEVEL:
|
1
|
+
NODE_LOG_LEVEL: audit
|
File without changes
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from .constants import START, END, RESERVED
|
2
|
+
from ..types import FlowContext, TaskData, TaskEventType
|
3
|
+
from ..node import UserNode, AgentNode, StartNode, EndNode
|
4
|
+
from .flow import Flow, CompiledFlow, FlowRun, FlowEvent, FlowEventType, FlowFactory, MatchPolicy, WaitPolicy, ForeachPolicy, Branch, Foreach, Loop
|
5
|
+
from .decorators import user, flow_spec, flow
|
6
|
+
from .data_map import Assignment, DataMapSpec
|
7
|
+
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"START",
|
11
|
+
"END",
|
12
|
+
"RESERVED",
|
13
|
+
|
14
|
+
"FlowContext",
|
15
|
+
"TaskData",
|
16
|
+
"TaskEventType",
|
17
|
+
|
18
|
+
"UserNode",
|
19
|
+
"AgentNode",
|
20
|
+
"StartNode",
|
21
|
+
"EndNode",
|
22
|
+
"Assignment",
|
23
|
+
"DataMapSpec",
|
24
|
+
|
25
|
+
"Flow",
|
26
|
+
"CompiledFlow",
|
27
|
+
"FlowRun",
|
28
|
+
"FlowEvent",
|
29
|
+
"FlowEventType",
|
30
|
+
"FlowFactory",
|
31
|
+
"MatchPolicy",
|
32
|
+
"WaitPolicy",
|
33
|
+
"ForeachPolicy",
|
34
|
+
"Branch",
|
35
|
+
"Foreach",
|
36
|
+
"Loop",
|
37
|
+
|
38
|
+
"user",
|
39
|
+
"flow_spec",
|
40
|
+
"flow"
|
41
|
+
]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"""
|
2
|
+
Predefined constants for Flow.
|
3
|
+
"""
|
4
|
+
import sys
|
5
|
+
|
6
|
+
START = sys.intern("__start__")
|
7
|
+
END = sys.intern("__end__")
|
8
|
+
|
9
|
+
ANY_USER = sys.intern("__any_user__")
|
10
|
+
FLOW_CONTEXT = sys.intern("FlowContext")
|
11
|
+
|
12
|
+
RESERVED = {
|
13
|
+
START,
|
14
|
+
END,
|
15
|
+
FLOW_CONTEXT,
|
16
|
+
ANY_USER
|
17
|
+
}
|
@@ -0,0 +1,91 @@
|
|
1
|
+
from typing import Any, List, cast
|
2
|
+
import uuid
|
3
|
+
import json
|
4
|
+
import yaml
|
5
|
+
from pydantic import BaseModel, Field, SerializeAsAny
|
6
|
+
|
7
|
+
from ibm_watsonx_orchestrate.agent_builder.tools.types import (
|
8
|
+
ToolRequestBody, ToolResponseBody, JsonSchemaObject
|
9
|
+
)
|
10
|
+
from ..types import (
|
11
|
+
_to_json_from_input_schema, _to_json_from_output_schema, SchemaRef, Assignment
|
12
|
+
)
|
13
|
+
|
14
|
+
class DataMapSpec(BaseModel):
|
15
|
+
|
16
|
+
name: str
|
17
|
+
|
18
|
+
def __init__(self, **data: Any) -> None:
|
19
|
+
if 'id' not in data:
|
20
|
+
data['id'] = str(uuid.uuid4())
|
21
|
+
super().__init__(**data)
|
22
|
+
|
23
|
+
def to_json(self) -> dict[Any, dict]:
|
24
|
+
'''Create a JSON object representing the data'''
|
25
|
+
model_spec = {}
|
26
|
+
model_spec["name"] = self.name
|
27
|
+
return model_spec
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
class AssignmentDataMapSpec(DataMapSpec):
|
32
|
+
|
33
|
+
maps: List[Assignment]
|
34
|
+
|
35
|
+
def to_json(self) -> dict[str, Any]:
|
36
|
+
'''Create a JSON object representing the data'''
|
37
|
+
model_spec = super().to_json()
|
38
|
+
|
39
|
+
if self.maps:
|
40
|
+
model_spec["maps"] = [assignment.model_dump() for assignment in self.maps]
|
41
|
+
|
42
|
+
return model_spec
|
43
|
+
|
44
|
+
|
45
|
+
class DataMap(BaseModel):
|
46
|
+
|
47
|
+
spec: SerializeAsAny[DataMapSpec]
|
48
|
+
|
49
|
+
def __call__(self, **kwargs):
|
50
|
+
pass
|
51
|
+
|
52
|
+
def dump_spec(self, file: str) -> None:
|
53
|
+
|
54
|
+
dumped = self.spec.model_dump(mode='json',
|
55
|
+
exclude_unset=True, exclude_none=True, by_alias=True)
|
56
|
+
with open(file, 'w', encoding="utf-8") as f:
|
57
|
+
if file.endswith('.yaml') or file.endswith('.yml'):
|
58
|
+
yaml.dump(dumped, f)
|
59
|
+
elif file.endswith('.json'):
|
60
|
+
json.dump(dumped, f, indent=2)
|
61
|
+
else:
|
62
|
+
raise ValueError('file must end in .json, .yaml, or .yml')
|
63
|
+
|
64
|
+
def dumps_spec(self) -> str:
|
65
|
+
|
66
|
+
dumped = self.spec.model_dump(mode='json',
|
67
|
+
exclude_unset=True, exclude_none=True, by_alias=True)
|
68
|
+
return json.dumps(dumped, indent=2)
|
69
|
+
|
70
|
+
def __repr__(self):
|
71
|
+
return f"DataMap(name='{self.spec.name}', description='{self.spec.description}')"
|
72
|
+
|
73
|
+
def to_json(self) -> dict[str, Any]:
|
74
|
+
|
75
|
+
obj = self.get_spec().to_json()
|
76
|
+
|
77
|
+
return { "spec": obj }
|
78
|
+
|
79
|
+
def get_spec(self) -> DataMapSpec:
|
80
|
+
|
81
|
+
return self.spec
|
82
|
+
|
83
|
+
class AssignmentDataMap(DataMap):
|
84
|
+
def get_spec(self) -> AssignmentDataMapSpec:
|
85
|
+
return cast(AssignmentDataMapSpec, self.spec)
|
86
|
+
|
87
|
+
def to_json(self) -> dict[str, Any]:
|
88
|
+
|
89
|
+
obj = super().to_json()
|
90
|
+
return obj
|
91
|
+
|
@@ -0,0 +1,143 @@
|
|
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 user(*args, name: str|None=None, description: str|None=None, owners: Sequence[str] = ()):
|
31
|
+
"""Decorator to mark a function as a task specification."""
|
32
|
+
|
33
|
+
def decorator(func: Callable):
|
34
|
+
node_spec = extract_node_spec(func, name, description)
|
35
|
+
func.__user_spec__ = UserNodeSpec(type = "user",
|
36
|
+
name = node_spec.name,
|
37
|
+
display_name = node_spec.display_name,
|
38
|
+
description = node_spec.description,
|
39
|
+
input_schema = node_spec.input_schema,
|
40
|
+
output_schema = node_spec.output_schema,
|
41
|
+
output_schema_object = node_spec.output_schema_object,
|
42
|
+
owners=owners)
|
43
|
+
|
44
|
+
@wraps(func)
|
45
|
+
def wrapper(*args, **kwargs):
|
46
|
+
logger.error(f"User node {name} is not supported yet.")
|
47
|
+
r = func(*args, **kwargs)
|
48
|
+
return r
|
49
|
+
|
50
|
+
return wrapper
|
51
|
+
|
52
|
+
if len(args) == 1 and callable(args[0]):
|
53
|
+
return decorator(args[0])
|
54
|
+
else:
|
55
|
+
return decorator
|
56
|
+
|
57
|
+
|
58
|
+
def flow_spec(*args,
|
59
|
+
name: Optional[str]=None,
|
60
|
+
description: str|None=None,
|
61
|
+
initiators: Sequence[str] = ()):
|
62
|
+
"""Decorator to mark a function as a flow specification."""
|
63
|
+
|
64
|
+
def decorator(func: Callable):
|
65
|
+
node_spec = extract_node_spec(func, name, description)
|
66
|
+
a_spec = FlowSpec(type = "flow",
|
67
|
+
name = node_spec.name,
|
68
|
+
display_name = node_spec.display_name,
|
69
|
+
description = node_spec.description,
|
70
|
+
input_schema = node_spec.input_schema,
|
71
|
+
output_schema = node_spec.output_schema,
|
72
|
+
output_schema_object = node_spec.output_schema_object,
|
73
|
+
initiators = initiators)
|
74
|
+
|
75
|
+
# we should also check a flow is async
|
76
|
+
if not asyncio.iscoroutinefunction(func):
|
77
|
+
raise ValueError("Flow must be asynchronous.")
|
78
|
+
|
79
|
+
logger.info("Generated flow spec: %s", a_spec)
|
80
|
+
func.__flow_spec__ = a_spec
|
81
|
+
|
82
|
+
@wraps(func)
|
83
|
+
def wrapper(*args, **kwargs):
|
84
|
+
logger.info("Creating flow spec: %s", name)
|
85
|
+
r = func(*args, **kwargs)
|
86
|
+
logger.info("Flow spec %s returned: %s", name, r)
|
87
|
+
return r
|
88
|
+
|
89
|
+
return wrapper
|
90
|
+
|
91
|
+
if len(args) == 1 and callable(args[0]):
|
92
|
+
return decorator(args[0])
|
93
|
+
else:
|
94
|
+
return decorator
|
95
|
+
|
96
|
+
def flow(*args,
|
97
|
+
name: Optional[str]=None,
|
98
|
+
display_name: Optional[str]=None,
|
99
|
+
description: str|None=None,
|
100
|
+
input_schema: type[BaseModel] | None = None,
|
101
|
+
output_schema: type[BaseModel] | None = None,
|
102
|
+
initiators: Sequence[str] = ()):
|
103
|
+
"""Decorator to mark a function as a flow model builder."""
|
104
|
+
|
105
|
+
def decorator(func: Callable):
|
106
|
+
"""
|
107
|
+
Decorator that takes a function as an argument and returns a wrapper function.
|
108
|
+
The wrapper function takes a single argument of type Flow and calls the original function with the created flow as an argument.
|
109
|
+
"""
|
110
|
+
|
111
|
+
sig = inspect.signature(func)
|
112
|
+
if len(sig.parameters) != 1:
|
113
|
+
raise ValueError("Only one argument is allowed")
|
114
|
+
param = list(sig.parameters.values())[0]
|
115
|
+
if param.annotation != Flow:
|
116
|
+
raise ValueError("Argument must be of type Flow")
|
117
|
+
if sig.return_annotation != Flow:
|
118
|
+
raise ValueError("Return value must be of type Flow")
|
119
|
+
|
120
|
+
node_spec = extract_node_spec(func, name, description)
|
121
|
+
a_model = FlowFactory.create_flow(
|
122
|
+
name = node_spec.name,
|
123
|
+
display_name = display_name,
|
124
|
+
description = node_spec.description,
|
125
|
+
input_schema = input_schema,
|
126
|
+
output_schema = output_schema,
|
127
|
+
initiators = initiators)
|
128
|
+
|
129
|
+
# logger.info("Creating flow model: %s", a_model.spec.name)
|
130
|
+
|
131
|
+
# @wraps(func)
|
132
|
+
# def wrapper(*args, **kwargs):
|
133
|
+
# result = func(a_model)
|
134
|
+
# if not isinstance(result, Flow):
|
135
|
+
# raise ValueError("Return value must be of type Flow")
|
136
|
+
# return result
|
137
|
+
|
138
|
+
return FlowWrapper(func, a_model)
|
139
|
+
|
140
|
+
if len(args) == 1 and callable(args[0]):
|
141
|
+
return decorator(args[0])
|
142
|
+
else:
|
143
|
+
return decorator
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import redis
|
2
|
+
import time
|
3
|
+
import json
|
4
|
+
from dotenv import load_dotenv
|
5
|
+
import os
|
6
|
+
|
7
|
+
from typing import (
|
8
|
+
AsyncIterator, Union
|
9
|
+
)
|
10
|
+
|
11
|
+
from ..types import (
|
12
|
+
FlowEventType, TaskEventType, FlowEvent, FlowContext
|
13
|
+
)
|
14
|
+
|
15
|
+
class StreamConsumer:
|
16
|
+
def __init__(self, instance_id: str):
|
17
|
+
|
18
|
+
load_dotenv()
|
19
|
+
self.redis_host = os.getenv("REDIS_HOST", "localhost")
|
20
|
+
self.redis_port = os.getenv("REDIS_PORT", 6379)
|
21
|
+
self.redis_db = os.getenv("REDIS_DB", 0)
|
22
|
+
self.redis = redis.Redis(host=self.redis_host, port=self.redis_port, db=self.redis_db)
|
23
|
+
self.instance_id = instance_id
|
24
|
+
self.stream_name = f"tempus:{self.instance_id}"
|
25
|
+
self.last_processed_id = 0
|
26
|
+
|
27
|
+
|
28
|
+
async def consume(self) -> AsyncIterator[FlowEvent]:
|
29
|
+
|
30
|
+
while True:
|
31
|
+
try:
|
32
|
+
# XREAD command: Read new messages from the stream
|
33
|
+
messages = self.redis.xread({self.stream_name: self.last_processed_id}, block=5000, count=10)
|
34
|
+
|
35
|
+
for stream, events in messages:
|
36
|
+
for event_id, event_data in events:
|
37
|
+
self.last_processed_id = event_id # Update the last read event ID
|
38
|
+
flow_event = deserialize_flow_event(event_data)
|
39
|
+
yield flow_event
|
40
|
+
|
41
|
+
except Exception as e:
|
42
|
+
print(f"Error occurred: {e}")
|
43
|
+
time.sleep(5) # Wait for 5 seconds before retrying
|
44
|
+
|
45
|
+
time.sleep(1) # Sleep for 1 second before checking for new messages
|
46
|
+
|
47
|
+
def deserialize_flow_event(byte_data: bytes) -> FlowEvent:
|
48
|
+
"""Deserialize byte data into a FlowEvent object."""
|
49
|
+
# Decode the byte data
|
50
|
+
decoded_data = byte_data[b'message'].decode('utf-8')
|
51
|
+
|
52
|
+
# Parse the JSON string into a dictionary
|
53
|
+
parsed_data = json.loads(decoded_data)
|
54
|
+
|
55
|
+
# Deserialize into FlowEvent using Pydantic's parsing
|
56
|
+
flow_event = FlowEvent(
|
57
|
+
kind=get_event_type(parsed_data["kind"]),
|
58
|
+
context=FlowContext(**parsed_data["context"]) if "context" in parsed_data and parsed_data["context"] != {} else None,
|
59
|
+
error=json.loads(parsed_data["error"]) if "error" in parsed_data and parsed_data["error"] != {} and len(parsed_data["error"]) > 0 else None,
|
60
|
+
)
|
61
|
+
|
62
|
+
return flow_event
|
63
|
+
|
64
|
+
def get_event_type(selected_event_type: str) -> Union[FlowEventType, TaskEventType]:
|
65
|
+
"""Selects the right event type from the corresponding enumerator"""
|
66
|
+
eventKind = selected_event_type.upper()
|
67
|
+
if eventKind in FlowEventType.__members__:
|
68
|
+
return FlowEventType(selected_event_type)
|
69
|
+
elif eventKind in TaskEventType.__members__:
|
70
|
+
return TaskEventType(selected_event_type)
|
71
|
+
else:
|
72
|
+
raise ValueError(f"Invalid event type: {eventKind}")
|