ibm-watsonx-orchestrate 1.6.0b0__py3-none-any.whl → 1.6.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.
Files changed (42) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
  3. ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -1
  4. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
  5. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
  7. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
  8. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +21 -7
  9. ibm_watsonx_orchestrate/agent_builder/connections/types.py +39 -36
  10. ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
  11. ibm_watsonx_orchestrate/agent_builder/tools/types.py +7 -1
  12. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +56 -18
  13. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -21
  14. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  15. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
  16. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
  17. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +1 -1
  18. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +118 -30
  19. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +22 -9
  20. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
  21. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +123 -5
  22. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -3
  23. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +107 -22
  24. ibm_watsonx_orchestrate/client/agents/agent_client.py +74 -6
  25. ibm_watsonx_orchestrate/client/base_api_client.py +2 -1
  26. ibm_watsonx_orchestrate/client/connections/connections_client.py +18 -9
  27. ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
  28. ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
  29. ibm_watsonx_orchestrate/client/service_instance.py +3 -3
  30. ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
  31. ibm_watsonx_orchestrate/client/utils.py +10 -0
  32. ibm_watsonx_orchestrate/docker/compose-lite.yml +228 -67
  33. ibm_watsonx_orchestrate/docker/default.env +32 -13
  34. ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
  35. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +15 -5
  36. ibm_watsonx_orchestrate/flow_builder/utils.py +78 -48
  37. ibm_watsonx_orchestrate/run/connections.py +4 -4
  38. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.2.dist-info}/METADATA +5 -3
  39. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.2.dist-info}/RECORD +42 -37
  40. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.2.dist-info}/WHEEL +0 -0
  41. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.2.dist-info}/entry_points.txt +0 -0
  42. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.2.dist-info}/licenses/LICENSE +0 -0
@@ -5,10 +5,12 @@ import os
5
5
  import yaml
6
6
  import csv
7
7
  import rich
8
- from pathlib import Path
9
8
  import sys
10
- from dotenv import dotenv_values
9
+ import shutil
11
10
 
11
+ from rich.panel import Panel
12
+ from pathlib import Path
13
+ from dotenv import dotenv_values
12
14
  from typing import Optional
13
15
  from typing_extensions import Annotated
14
16
 
@@ -46,6 +48,32 @@ def validate_watsonx_credentials(user_env_file: str) -> bool:
46
48
  os.environ.update({key: user_env[key] for key in required_keys})
47
49
  logger.info("WatsonX credentials validated successfully.")
48
50
 
51
+ def read_csv(data_path: str, delimiter="\t"):
52
+ data = []
53
+ with open(data_path, "r") as f:
54
+ tsv_reader = csv.reader(f, delimiter=delimiter)
55
+ for line in tsv_reader:
56
+ data.append(line)
57
+
58
+ return data
59
+
60
+ def performance_test(agent_name, data_path, output_dir = None, user_env_file = None):
61
+ test_data = read_csv(data_path)
62
+
63
+ controller = EvaluationsController()
64
+ generated_performance_tests = controller.generate_performance_test(agent_name, test_data)
65
+
66
+ generated_perf_test_dir = Path(output_dir) / "generated_performance_tests"
67
+ generated_perf_test_dir.mkdir(exist_ok=True, parents=True)
68
+
69
+ for idx, test in enumerate(generated_performance_tests):
70
+ test_name = f"validate_external_agent_evaluation_test_{idx}.json"
71
+ with open(generated_perf_test_dir / test_name, encoding="utf-8", mode="w+") as f:
72
+ json.dump(test, f, indent=4)
73
+
74
+ rich.print(f"Performance test cases saved at path '{str(generated_perf_test_dir)}'")
75
+ rich.print("[gold3]Running Performance Test")
76
+ evaluate(output_dir=output_dir, test_paths=str(generated_perf_test_dir))
49
77
 
50
78
  @evaluation_app.command(name="evaluate", help="Evaluate an agent against a set of test cases")
51
79
  def evaluate(
@@ -115,7 +143,7 @@ def generate(
115
143
  stories_path: Annotated[
116
144
  str,
117
145
  typer.Option(
118
- "--stories_path", "-s",
146
+ "--stories-path", "-s",
119
147
  help="Path to the CSV file containing user stories for test case generation. "
120
148
  "The file has 'story' and 'agent' columns."
121
149
  )
@@ -123,14 +151,14 @@ def generate(
123
151
  tools_path: Annotated[
124
152
  str,
125
153
  typer.Option(
126
- "--tools_path", "-t",
154
+ "--tools-path", "-t",
127
155
  help="Path to the directory containing tool definitions."
128
156
  )
129
157
  ],
130
158
  output_dir: Annotated[
131
159
  Optional[str],
132
160
  typer.Option(
133
- "--output_dir", "-o",
161
+ "--output-dir", "-o",
134
162
  help="Directory to save the generated test cases."
135
163
  )
136
164
  ] = None,
@@ -151,7 +179,7 @@ def generate(
151
179
  def analyze(data_path: Annotated[
152
180
  str,
153
181
  typer.Option(
154
- "--data_path", "-d",
182
+ "--data-path", "-d",
155
183
  help="Path to the directory that has the saved results"
156
184
  )
157
185
  ],
@@ -167,30 +195,31 @@ def analyze(data_path: Annotated[
167
195
  controller = EvaluationsController()
168
196
  controller.analyze(data_path=data_path)
169
197
 
170
-
171
- @evaluation_app.command(name="validate_external", help="Validate an external agent against a set of inputs")
198
+ @evaluation_app.command(name="validate-external", help="Validate an external agent against a set of inputs")
172
199
  def validate_external(
173
200
  data_path: Annotated[
174
201
  str,
175
202
  typer.Option(
176
- "--csv", "-c",
177
- help="Path to .csv file of inputs"
203
+ "--tsv", "-t",
204
+ help="Path to .tsv file of inputs"
178
205
  )
179
206
  ],
180
- config: Annotated[
207
+ external_agent_config: Annotated[
181
208
  str,
182
209
  typer.Option(
183
- "--config", "-cf",
184
- help="Path to the external agent yaml"
210
+ "--external-agent-config", "-ext",
211
+ help="Path to the external agent yaml",
212
+
185
213
  )
186
214
  ],
187
215
  credential: Annotated[
188
216
  str,
189
217
  typer.Option(
190
218
  "--credential", "-crd",
191
- help="credential string"
219
+ help="credential string",
220
+ rich_help_panel="Parameters for Validation"
192
221
  )
193
- ],
222
+ ] = None,
194
223
  output_dir: Annotated[
195
224
  str,
196
225
  typer.Option(
@@ -204,21 +233,80 @@ def validate_external(
204
233
  "--env-file", "-e",
205
234
  help="Path to a .env file that overrides default.env. Then environment variables override both."
206
235
  ),
236
+ ] = None,
237
+ agent_name: Annotated[
238
+ str,
239
+ typer.Option(
240
+ "--agent_name", "-a",
241
+ help="Name of the native agent which has the external agent to test registered as a collaborater. See: https://developer.watson-orchestrate.ibm.com/agents/build_agent#native-agents)." \
242
+ " If this parameter is pased, validation of the external agent is not run.",
243
+ rich_help_panel="Parameters for Input Evaluation"
244
+ )
207
245
  ] = None
208
246
  ):
209
-
247
+
210
248
  validate_watsonx_credentials(user_env_file)
211
- with open(config, "r") as f:
212
- config = yaml.safe_load(f)
213
- controller = EvaluationsController()
214
- test_data = []
215
- with open(data_path, "r") as f:
216
- csv_reader = csv.reader(f)
217
- for line in csv_reader:
218
- test_data.append(line[0])
219
- results = controller.external_validate(config, test_data, credential)
220
- os.makedirs(output_dir, exist_ok=True)
221
- with open(os.path.join(output_dir, "validation_results.json"), "w") as f:
222
- json.dump(results, f)
223
-
224
- rich.print(f"[green] validation result is saved to {output_dir} [/green]")
249
+ Path(output_dir).mkdir(exist_ok=True)
250
+ shutil.copy(data_path, os.path.join(output_dir, "input_sample.tsv"))
251
+
252
+ if agent_name is not None:
253
+ eval_dir = os.path.join(output_dir, "evaluation")
254
+ if os.path.exists(eval_dir):
255
+ rich.print(f"[yellow]: found existing {eval_dir} in target directory. All content is removed.")
256
+ shutil.rmtree(os.path.join(output_dir, "evaluation"))
257
+ Path(eval_dir).mkdir(exist_ok=True)
258
+ # save external agent config even though its not used for evaluation
259
+ # it can help in later debugging customer agents
260
+ with open(os.path.join(eval_dir, "external_agent_cfg.yaml"), "w+") as f:
261
+ with open(external_agent_config, "r") as cfg:
262
+ external_agent_config = yaml.safe_load(cfg)
263
+ yaml.safe_dump(external_agent_config, f, indent=4)
264
+
265
+ rich.print(f"[gold3]Starting evaluation of inputs in '{data_path}' against '{agent_name}'[/gold3]")
266
+ performance_test(
267
+ agent_name=agent_name,
268
+ data_path=data_path,
269
+ output_dir=eval_dir,
270
+ user_env_file=user_env_file
271
+ )
272
+
273
+ else:
274
+ with open(external_agent_config, "r") as f:
275
+ external_agent_config = yaml.safe_load(f)
276
+ controller = EvaluationsController()
277
+ test_data = []
278
+ with open(data_path, "r") as f:
279
+ csv_reader = csv.reader(f, delimiter="\t")
280
+ for line in csv_reader:
281
+ test_data.append(line[0])
282
+
283
+ # save validation results in "validation_results" sub-dir
284
+ validation_folder = Path(output_dir) / "validation_results"
285
+ if os.path.exists(validation_folder):
286
+ rich.print(f"[yellow]: found existing {validation_folder} in target directory. All content is removed.")
287
+ shutil.rmtree(validation_folder)
288
+ validation_folder.mkdir(exist_ok=True, parents=True)
289
+
290
+ # validate the inputs in the provided csv file
291
+ summary = controller.external_validate(external_agent_config, test_data, credential)
292
+ with open(validation_folder / "validation_results.json", "w") as f:
293
+ json.dump(summary, f, indent=4)
294
+
295
+ # validate sample block inputs
296
+ rich.print("[gold3]Validating external agent to see if it can handle an array of messages.")
297
+ block_input_summary = controller.external_validate(external_agent_config, test_data, credential, add_context=True)
298
+ with open(validation_folder / "sample_block_validation_results.json", "w") as f:
299
+ json.dump(block_input_summary, f, indent=4)
300
+
301
+ user_validation_successful = all([item["success"] for item in summary])
302
+ block_validation_successful = all([item["success"] for item in block_input_summary])
303
+
304
+ if user_validation_successful and block_validation_successful:
305
+ msg = (
306
+ f"[green]Validation is successful. The result is saved to '{str(validation_folder)}'.[/green]\n"
307
+ "You can add the external agent as a collaborator agent. See: https://developer.watson-orchestrate.ibm.com/agents/build_agent#native-agents."
308
+ )
309
+ else:
310
+ msg = f"[dark_orange]Schema validation did not succeed. See '{str(validation_folder)}' for failures.[/dark_orange]"
311
+
312
+ rich.print(Panel(msg))
@@ -1,5 +1,6 @@
1
1
  import logging
2
- from typing import List, Dict, Optional
2
+ import os.path
3
+ from typing import List, Dict, Optional, Tuple
3
4
  import csv
4
5
  from pathlib import Path
5
6
  import rich
@@ -10,12 +11,13 @@ from wxo_agentic_evaluation.batch_annotate import generate_test_cases_from_stori
10
11
  from wxo_agentic_evaluation.arg_configs import TestConfig, AuthConfig, LLMUserConfig, ChatRecordingConfig, AnalyzeConfig
11
12
  from wxo_agentic_evaluation.record_chat import record_chats
12
13
  from wxo_agentic_evaluation.external_agent.external_validate import ExternalAgentValidation
14
+ from wxo_agentic_evaluation.external_agent.performance_test import ExternalAgentPerformanceTest
13
15
  from ibm_watsonx_orchestrate import __version__
14
16
  from ibm_watsonx_orchestrate.cli.config import Config, ENV_WXO_URL_OPT, AUTH_CONFIG_FILE, AUTH_CONFIG_FILE_FOLDER, AUTH_SECTION_HEADER, AUTH_MCSP_TOKEN_OPT
15
17
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
16
18
  from ibm_watsonx_orchestrate.cli.commands.agents.agents_controller import AgentsController
17
19
  from ibm_watsonx_orchestrate.agent_builder.agents import AgentKind
18
-
20
+ import uuid
19
21
 
20
22
  logger = logging.getLogger(__name__)
21
23
 
@@ -75,9 +77,13 @@ class EvaluationsController:
75
77
  evaluate.main(config)
76
78
 
77
79
  def record(self, output_dir) -> None:
80
+
81
+
82
+ random_uuid = str(uuid.uuid4())
83
+
78
84
  url, tenant_name, token = self._get_env_config()
79
85
  config_data = {
80
- "output_dir": Path.cwd() if output_dir is None else Path(output_dir),
86
+ "output_dir": Path(os.path.join(Path.cwd(), random_uuid)) if output_dir is None else Path(os.path.join(output_dir,random_uuid)),
81
87
  "service_url": url,
82
88
  "tenant_name": tenant_name,
83
89
  "token": token
@@ -143,16 +149,23 @@ class EvaluationsController:
143
149
  def summarize(self) -> None:
144
150
  pass
145
151
 
146
- def external_validate(self, config: Dict, data: List[str], credential:str):
152
+ def external_validate(self, config: Dict, data: List[str], credential:str, add_context: bool = False):
147
153
  validator = ExternalAgentValidation(credential=credential,
148
154
  auth_scheme=config["auth_scheme"],
149
155
  service_url=config["api_url"])
156
+
150
157
  summary = []
151
158
  for entry in data:
152
- results = validator.call_validation(entry)
153
- if len(results) == 0:
154
- rich.print(f"[red] No events are generated for input {entry} [/red]")
155
- summary.append({entry: results})
159
+ results = validator.call_validation(entry, add_context)
160
+ summary.append(results)
156
161
 
157
162
  return summary
158
-
163
+
164
+ def generate_performance_test(self, agent_name: str, test_data: List[Tuple[str, str]]):
165
+ performance_test = ExternalAgentPerformanceTest(
166
+ agent_name=agent_name,
167
+ test_data=test_data
168
+ )
169
+ generated_performance_tests = performance_test.generate_tests()
170
+
171
+ return generated_performance_tests
@@ -197,10 +197,10 @@ class KnowledgeBaseController:
197
197
  )
198
198
 
199
199
  column_args = {
200
- "Name": {},
200
+ "Name": {"overflow": "fold"},
201
201
  "Description": {},
202
202
  "App ID": {},
203
- "ID": {}
203
+ "ID": {"overflow": "fold"}
204
204
  }
205
205
 
206
206
  for column in column_args:
@@ -240,6 +240,27 @@ def apply_llm_api_key_defaults(env_dict: dict) -> None:
240
240
  env_dict.setdefault("ASSISTANT_EMBEDDINGS_SPACE_ID", space_value)
241
241
  env_dict.setdefault("ROUTING_LLM_SPACE_ID", space_value)
242
242
 
243
+ def _is_docker_container_running(container_name):
244
+ ensure_docker_installed()
245
+ command = [ "docker",
246
+ "ps",
247
+ "-f",
248
+ f"name={container_name}"
249
+ ]
250
+ result = subprocess.run(command, env=os.environ, capture_output=True)
251
+ if container_name in str(result.stdout):
252
+ return True
253
+ return False
254
+
255
+ def _check_exclusive_observibility(langfuse_enabled: bool, ibm_tele_enabled: bool):
256
+ if langfuse_enabled and ibm_tele_enabled:
257
+ return False
258
+ if langfuse_enabled and _is_docker_container_running("docker-frontend-server-1"):
259
+ return False
260
+ if ibm_tele_enabled and _is_docker_container_running("docker-langfuse-web-1"):
261
+ return False
262
+ return True
263
+
243
264
  def write_merged_env_file(merged_env: dict) -> Path:
244
265
  tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
245
266
  with tmp:
@@ -292,7 +313,8 @@ def get_persisted_user_env() -> dict | None:
292
313
  user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
293
314
  return user_env
294
315
 
295
- def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) -> None:
316
+
317
+ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False) -> None:
296
318
  compose_path = get_compose_file()
297
319
  compose_command = ensure_docker_compose_installed()
298
320
  db_tag = read_env_file(final_env_file).get('DBTAG', None)
@@ -324,9 +346,15 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) ->
324
346
  '--profile',
325
347
  'langfuse'
326
348
  ]
349
+ elif experimental_with_ibm_telemetry:
350
+ command = compose_command + [
351
+ '--profile',
352
+ 'ibm-telemetry'
353
+ ]
327
354
  else:
328
355
  command = compose_command
329
356
 
357
+
330
358
  command += [
331
359
  "-f", str(compose_path),
332
360
  "--env-file", str(final_env_file),
@@ -444,6 +472,9 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
444
472
  # do nothing, as the docker login here is not mandatory
445
473
  pass
446
474
 
475
+ # Auto-configure callback IP for async tools
476
+ merged_env_dict = auto_configure_callback_ip(merged_env_dict)
477
+
447
478
  #These are to removed warning and not used in UI component
448
479
  if not 'WATSONX_SPACE_ID' in merged_env_dict:
449
480
  merged_env_dict['WATSONX_SPACE_ID']='X'
@@ -621,8 +652,80 @@ def confirm_accepts_license_agreement(accepts_by_argument: bool):
621
652
  logger.error('The terms and conditions were not accepted, exiting.')
622
653
  exit(1)
623
654
 
624
-
625
-
655
+ def auto_configure_callback_ip(merged_env_dict: dict) -> dict:
656
+ """
657
+ Automatically detect and configure CALLBACK_HOST_URL if it's empty.
658
+
659
+ Args:
660
+ merged_env_dict: The merged environment dictionary
661
+
662
+ Returns:
663
+ Updated environment dictionary with CALLBACK_HOST_URL set
664
+ """
665
+ callback_url = merged_env_dict.get('CALLBACK_HOST_URL', '').strip()
666
+
667
+ # Only auto-configure if CALLBACK_HOST_URL is empty
668
+ if not callback_url:
669
+ logger.info("Auto-detecting local IP address for async tool callbacks...")
670
+
671
+ system = platform.system()
672
+ ip = None
673
+
674
+ try:
675
+ if system in ("Linux", "Darwin"):
676
+ result = subprocess.run(["ifconfig"], capture_output=True, text=True, check=True)
677
+ lines = result.stdout.splitlines()
678
+
679
+ for line in lines:
680
+ line = line.strip()
681
+ # Unix ifconfig output format: "inet 192.168.1.100 netmask 0xffffff00 broadcast 192.168.1.255"
682
+ if line.startswith("inet ") and "127.0.0.1" not in line:
683
+ candidate_ip = line.split()[1]
684
+ # Validate IP is not loopback or link-local
685
+ if (candidate_ip and
686
+ not candidate_ip.startswith("127.") and
687
+ not candidate_ip.startswith("169.254")):
688
+ ip = candidate_ip
689
+ break
690
+
691
+ elif system == "Windows":
692
+ result = subprocess.run(["ipconfig"], capture_output=True, text=True, check=True)
693
+ lines = result.stdout.splitlines()
694
+
695
+ for line in lines:
696
+ line = line.strip()
697
+ # Windows ipconfig output format: " IPv4 Address. . . . . . . . . . . : 192.168.1.100"
698
+ if "IPv4 Address" in line and ":" in line:
699
+ candidate_ip = line.split(":")[-1].strip()
700
+ # Validate IP is not loopback or link-local
701
+ if (candidate_ip and
702
+ not candidate_ip.startswith("127.") and
703
+ not candidate_ip.startswith("169.254")):
704
+ ip = candidate_ip
705
+ break
706
+
707
+ else:
708
+ logger.warning(f"Unsupported platform: {system}")
709
+ ip = None
710
+
711
+ except Exception as e:
712
+ logger.debug(f"IP detection failed on {system}: {e}")
713
+ ip = None
714
+
715
+ if ip:
716
+ callback_url = f"http://{ip}:4321"
717
+ merged_env_dict['CALLBACK_HOST_URL'] = callback_url
718
+ logger.info(f"Auto-configured CALLBACK_HOST_URL to: {callback_url}")
719
+ else:
720
+ # Fallback for localhost
721
+ callback_url = "http://host.docker.internal:4321"
722
+ merged_env_dict['CALLBACK_HOST_URL'] = callback_url
723
+ logger.info(f"Using Docker internal URL: {callback_url}")
724
+ logger.info("For external tools, consider using ngrok or similar tunneling service.")
725
+ else:
726
+ logger.info(f"Using existing CALLBACK_HOST_URL: {callback_url}")
727
+
728
+ return merged_env_dict
626
729
 
627
730
  @server_app.command(name="start")
628
731
  def server_start(
@@ -636,6 +739,11 @@ def server_start(
636
739
  '--with-langfuse', '-l',
637
740
  help='Option to enable Langfuse support.'
638
741
  ),
742
+ experimental_with_ibm_telemetry: bool = typer.Option(
743
+ False,
744
+ '--with-ibm-telemetry', '-i',
745
+ help=''
746
+ ),
639
747
  persist_env_secrets: bool = typer.Option(
640
748
  False,
641
749
  '--persist-env-secrets', '-p',
@@ -675,10 +783,18 @@ def server_start(
675
783
 
676
784
  merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
677
785
 
786
+ # Auto-configure callback IP for async tools
787
+ merged_env_dict = auto_configure_callback_ip(merged_env_dict)
788
+ if not _check_exclusive_observibility(experimental_with_langfuse, experimental_with_ibm_telemetry):
789
+ logger.error("Please select either langfuse or ibm telemetry for observability not both")
790
+ sys.exit(1)
791
+
678
792
  # Add LANGFUSE_ENABLED into the merged_env_dict, for tempus to pick up.
679
793
  if experimental_with_langfuse:
680
794
  merged_env_dict['LANGFUSE_ENABLED'] = 'true'
681
-
795
+
796
+ if experimental_with_ibm_telemetry:
797
+ merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
682
798
 
683
799
  try:
684
800
  docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
@@ -690,7 +806,9 @@ def server_start(
690
806
 
691
807
 
692
808
  final_env_file = write_merged_env_file(merged_env_dict)
693
- run_compose_lite(final_env_file=final_env_file, experimental_with_langfuse=experimental_with_langfuse)
809
+ run_compose_lite(final_env_file=final_env_file,
810
+ experimental_with_langfuse=experimental_with_langfuse,
811
+ experimental_with_ibm_telemetry=experimental_with_ibm_telemetry)
694
812
 
695
813
  run_db_migration()
696
814
 
@@ -243,9 +243,15 @@ class ToolkitController:
243
243
  rich.print(JSON(json.dumps(tools_list, indent=4)))
244
244
  else:
245
245
  table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
246
- columns = ["Name", "Kind", "Description", "Tools", "App ID"]
247
- for column in columns:
248
- table.add_column(column)
246
+ column_args = {
247
+ "Name": {"overflow": "fold"},
248
+ "Kind": {},
249
+ "Description": {},
250
+ "Tools": {},
251
+ "App ID": {"overflow": "fold"}
252
+ }
253
+ for column in column_args:
254
+ table.add_column(column,**column_args[column])
249
255
 
250
256
  tools_client = instantiate_client(ToolClient)
251
257