ibm-watsonx-orchestrate-evaluation-framework 1.1.1__py3-none-any.whl → 1.1.3__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 (66) hide show
  1. ibm_watsonx_orchestrate_evaluation_framework-1.1.3.dist-info/METADATA +35 -0
  2. {ibm_watsonx_orchestrate_evaluation_framework-1.1.1.dist-info → ibm_watsonx_orchestrate_evaluation_framework-1.1.3.dist-info}/RECORD +65 -60
  3. wxo_agentic_evaluation/analytics/tools/analyzer.py +36 -21
  4. wxo_agentic_evaluation/analytics/tools/main.py +18 -7
  5. wxo_agentic_evaluation/analytics/tools/types.py +26 -11
  6. wxo_agentic_evaluation/analytics/tools/ux.py +75 -31
  7. wxo_agentic_evaluation/analyze_run.py +69 -48
  8. wxo_agentic_evaluation/annotate.py +6 -4
  9. wxo_agentic_evaluation/arg_configs.py +9 -3
  10. wxo_agentic_evaluation/batch_annotate.py +78 -25
  11. wxo_agentic_evaluation/data_annotator.py +18 -13
  12. wxo_agentic_evaluation/description_quality_checker.py +20 -14
  13. wxo_agentic_evaluation/evaluation.py +42 -0
  14. wxo_agentic_evaluation/evaluation_package.py +117 -70
  15. wxo_agentic_evaluation/external_agent/__init__.py +18 -7
  16. wxo_agentic_evaluation/external_agent/external_validate.py +46 -35
  17. wxo_agentic_evaluation/external_agent/performance_test.py +32 -20
  18. wxo_agentic_evaluation/external_agent/types.py +12 -5
  19. wxo_agentic_evaluation/inference_backend.py +183 -79
  20. wxo_agentic_evaluation/llm_matching.py +4 -3
  21. wxo_agentic_evaluation/llm_rag_eval.py +7 -4
  22. wxo_agentic_evaluation/llm_user.py +7 -3
  23. wxo_agentic_evaluation/main.py +175 -67
  24. wxo_agentic_evaluation/metrics/llm_as_judge.py +2 -2
  25. wxo_agentic_evaluation/metrics/metrics.py +26 -12
  26. wxo_agentic_evaluation/otel_support/evaluate_tau.py +67 -0
  27. wxo_agentic_evaluation/otel_support/evaluate_tau_traces.py +176 -0
  28. wxo_agentic_evaluation/otel_support/otel_message_conversion.py +21 -0
  29. wxo_agentic_evaluation/otel_support/tasks_test.py +1226 -0
  30. wxo_agentic_evaluation/prompt/template_render.py +32 -11
  31. wxo_agentic_evaluation/quick_eval.py +49 -23
  32. wxo_agentic_evaluation/record_chat.py +70 -33
  33. wxo_agentic_evaluation/red_teaming/attack_evaluator.py +58 -18
  34. wxo_agentic_evaluation/red_teaming/attack_generator.py +38 -18
  35. wxo_agentic_evaluation/red_teaming/attack_runner.py +43 -27
  36. wxo_agentic_evaluation/referenceless_eval/function_calling/metrics/base.py +3 -1
  37. wxo_agentic_evaluation/referenceless_eval/function_calling/metrics/loader.py +23 -15
  38. wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/adapters.py +13 -8
  39. wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/pipeline.py +41 -13
  40. wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/semantic_checker.py +26 -16
  41. wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/static_checker.py +17 -11
  42. wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/types.py +44 -29
  43. wxo_agentic_evaluation/referenceless_eval/metrics/field.py +13 -5
  44. wxo_agentic_evaluation/referenceless_eval/metrics/metric.py +16 -5
  45. wxo_agentic_evaluation/referenceless_eval/metrics/metrics_runner.py +8 -3
  46. wxo_agentic_evaluation/referenceless_eval/metrics/prompt.py +6 -2
  47. wxo_agentic_evaluation/referenceless_eval/metrics/utils.py +5 -1
  48. wxo_agentic_evaluation/referenceless_eval/prompt/runner.py +16 -3
  49. wxo_agentic_evaluation/referenceless_eval/referenceless_eval.py +23 -12
  50. wxo_agentic_evaluation/resource_map.py +2 -1
  51. wxo_agentic_evaluation/service_instance.py +103 -21
  52. wxo_agentic_evaluation/service_provider/__init__.py +33 -13
  53. wxo_agentic_evaluation/service_provider/model_proxy_provider.py +216 -34
  54. wxo_agentic_evaluation/service_provider/ollama_provider.py +10 -11
  55. wxo_agentic_evaluation/service_provider/provider.py +0 -1
  56. wxo_agentic_evaluation/service_provider/referenceless_provider_wrapper.py +34 -21
  57. wxo_agentic_evaluation/service_provider/watsonx_provider.py +50 -22
  58. wxo_agentic_evaluation/tool_planner.py +128 -44
  59. wxo_agentic_evaluation/type.py +12 -9
  60. wxo_agentic_evaluation/utils/__init__.py +1 -0
  61. wxo_agentic_evaluation/utils/open_ai_tool_extractor.py +41 -20
  62. wxo_agentic_evaluation/utils/rich_utils.py +23 -9
  63. wxo_agentic_evaluation/utils/utils.py +83 -52
  64. ibm_watsonx_orchestrate_evaluation_framework-1.1.1.dist-info/METADATA +0 -386
  65. {ibm_watsonx_orchestrate_evaluation_framework-1.1.1.dist-info → ibm_watsonx_orchestrate_evaluation_framework-1.1.3.dist-info}/WHEEL +0 -0
  66. {ibm_watsonx_orchestrate_evaluation_framework-1.1.1.dist-info → ibm_watsonx_orchestrate_evaluation_framework-1.1.3.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,13 @@
1
1
  import logging
2
- import yaml
3
2
  import os
3
+
4
4
  import requests
5
- from wxo_agentic_evaluation.utils.utils import is_saas_url, is_ibm_cloud_url
5
+ import shutil
6
+ from pathlib import Path
7
+ from typing import Optional, Any, Dict, Iterable, Tuple
8
+ import yaml
9
+
10
+ from wxo_agentic_evaluation.utils.utils import is_ibm_cloud_url, is_saas_url
6
11
 
7
12
  logger = logging.getLogger(__name__)
8
13
 
@@ -11,13 +16,15 @@ USER = {"username": "wxo.archer@ibm.com", "password": "watsonx"}
11
16
 
12
17
  class ServiceInstance:
13
18
  def __init__(
14
- self, service_url, tenant_name, is_saas: bool = None, is_ibm_cloud: bool = None
19
+ self,
20
+ service_url,
21
+ tenant_name,
22
+ is_saas: bool = None,
23
+ is_ibm_cloud: bool = None,
15
24
  ) -> None:
16
25
  self.service_url = service_url
17
26
  self.tenant_name = tenant_name
18
- STAGING_AUTH_ENDPOINT = (
19
- "https://iam.platform.test.saas.ibm.com/siusermgr/api/1.0/apikeys/token"
20
- )
27
+ STAGING_AUTH_ENDPOINT = "https://iam.platform.test.saas.ibm.com/siusermgr/api/1.0/apikeys/token"
21
28
  PROD_AUTH_ENDPOINT = (
22
29
  "https://iam.platform.saas.ibm.com/siusermgr/api/1.0/apikeys/token"
23
30
  )
@@ -25,7 +32,9 @@ class ServiceInstance:
25
32
 
26
33
  self.is_saas = is_saas_url(service_url) if is_saas is None else is_saas
27
34
  self.is_ibm_cloud = (
28
- is_ibm_cloud_url(service_url) if is_ibm_cloud is None else is_ibm_cloud
35
+ is_ibm_cloud_url(service_url)
36
+ if is_ibm_cloud is None
37
+ else is_ibm_cloud
29
38
  )
30
39
 
31
40
  if self.is_saas:
@@ -88,7 +97,8 @@ class ServiceInstance:
88
97
 
89
98
  def _get_tenant_token(self, tenant_id: str):
90
99
  resp = requests.post(
91
- self.tenant_auth_endpoint.format(self.service_url, tenant_id), data=USER
100
+ self.tenant_auth_endpoint.format(self.service_url, tenant_id),
101
+ data=USER,
92
102
  )
93
103
  if resp.status_code == 200:
94
104
  return resp.json()["access_token"]
@@ -122,7 +132,9 @@ class ServiceInstance:
122
132
  "tags": ["test"],
123
133
  }
124
134
 
125
- resp = requests.post(self.tenant_url, headers=headers, json=tenant_config)
135
+ resp = requests.post(
136
+ self.tenant_url, headers=headers, json=tenant_config
137
+ )
126
138
  if resp.status_code == 201:
127
139
  return True
128
140
  else:
@@ -147,8 +159,51 @@ class ServiceInstance:
147
159
 
148
160
  return default_tenant["id"]
149
161
 
162
+ def get_env_settings(
163
+ tenant_name: str,
164
+ env_config_path: Optional[str] = None
165
+ ) -> Dict[str, Any]:
166
+ if env_config_path is None:
167
+ env_config_path = f"{os.path.expanduser('~')}/.config/orchestrate/config.yaml"
168
+
169
+ try:
170
+ with open(env_config_path, "r", encoding="utf-8") as f:
171
+ cfg = yaml.safe_load(f) or {}
172
+ except FileNotFoundError:
173
+ return {}
174
+
175
+ tenant_env = (cfg.get("environments") or {}).get(tenant_name) or {}
176
+ cached_user_env = cfg.get("cached_user_env") or {}
177
+
178
+ merged = cached_user_env | tenant_env
179
+
180
+ return dict(merged)
181
+
182
+
150
183
 
151
- def tenant_setup(service_url: str, tenant_name: str):
184
+ def apply_env_overrides(
185
+ base: Dict[str, Any],
186
+ tenant_name: str,
187
+ keys: Optional[Iterable[str]] = None,
188
+ env_config_path: Optional[str] = None
189
+ ) -> Dict[str, Any]:
190
+ """
191
+ Returns a new dict where base is overridden by tenant-defined values.
192
+ - If keys is None, tries to override any keys present in tenant env.
193
+ - Only overrides when the tenant value is present and not None.
194
+ """
195
+ env = get_env_settings(tenant_name, env_config_path=env_config_path)
196
+ merged = dict(base)
197
+ keys_to_consider = keys if keys is not None else env.keys()
198
+
199
+ for k in keys_to_consider:
200
+ if k in env and env[k] is not None:
201
+ merged[k] = env[k]
202
+ return merged
203
+
204
+
205
+
206
+ def tenant_setup(service_url: Optional[str], tenant_name: str) -> Tuple[Optional[str], Optional[str], Dict[str, Any]]:
152
207
  # service_instance = ServiceInstance(
153
208
  # service_url=service_url,
154
209
  # tenant_name=tenant_name
@@ -159,21 +214,48 @@ def tenant_setup(service_url: str, tenant_name: str):
159
214
  # else:
160
215
  # tenant_token = service_instance._get_tenant_token(tenant_id)
161
216
 
162
- auth_config_path = f"{os.path.expanduser('~')}/.cache/orchestrate/credentials.yaml"
163
- env_config_path = f"{os.path.expanduser('~')}/.config/orchestrate/config.yaml"
217
+ auth_config_path = (
218
+ f"{os.path.expanduser('~')}/.cache/orchestrate/credentials.yaml"
219
+ )
220
+ env_config_path = (
221
+ f"{os.path.expanduser('~')}/.config/orchestrate/config.yaml"
222
+ )
223
+
224
+ try:
225
+ with open(auth_config_path, "r", encoding="utf-8") as f:
226
+ auth_config = yaml.safe_load(f) or {}
227
+ except FileNotFoundError:
228
+ auth_config = {}
229
+
230
+ try:
231
+ with open(env_config_path, "r", encoding="utf-8") as f:
232
+ env_config = yaml.safe_load(f) or {}
233
+ except FileNotFoundError:
234
+ env_config = {}
164
235
 
165
- # TO-DO: update SDK and use SDK to manage this
166
- with open(auth_config_path, "r") as f:
167
- auth_config = yaml.safe_load(f)
168
- # auth_config["auth"][tenant_name] = {"wxo_mcsp_token": tenant_token}
236
+ environments = env_config.setdefault("environments", {})
237
+ context = env_config.setdefault("context", {})
169
238
 
170
- with open(env_config_path, "r") as f:
171
- env_config = yaml.safe_load(f)
172
- env_config["environments"][tenant_name] = {"wxo_url": service_url}
173
- env_config["context"]["active_environment"] = tenant_name
239
+ tenant_env = environments.setdefault(tenant_name, {})
240
+
241
+ if service_url and str(service_url).strip():
242
+ tenant_env["wxo_url"] = service_url
243
+
244
+ resolved_service_url = tenant_env.get("wxo_url")
245
+
246
+ context["active_environment"] = tenant_name
174
247
 
175
248
  with open(auth_config_path, "w") as f:
176
249
  yaml.dump(auth_config, f)
177
250
  with open(env_config_path, "w") as f:
178
251
  yaml.dump(env_config, f)
179
- return auth_config["auth"][tenant_name]["wxo_mcsp_token"]
252
+
253
+ token = (
254
+ auth_config.get("auth", {})
255
+ .get(tenant_name, {})
256
+ .get("wxo_mcsp_token")
257
+ )
258
+
259
+ env_merged = get_env_settings(tenant_name, env_config_path=env_config_path)
260
+
261
+ return token, resolved_service_url, env_merged
@@ -1,12 +1,24 @@
1
- from wxo_agentic_evaluation.service_provider.ollama_provider import OllamaProvider
2
- from wxo_agentic_evaluation.service_provider.watsonx_provider import WatsonXProvider
3
- from wxo_agentic_evaluation.service_provider.model_proxy_provider import ModelProxyProvider
4
- from wxo_agentic_evaluation.service_provider.referenceless_provider_wrapper import ModelProxyProviderLLMKitWrapper, WatsonXLLMKitWrapper
1
+ import os
2
+
5
3
  from wxo_agentic_evaluation.arg_configs import ProviderConfig
4
+ from wxo_agentic_evaluation.service_provider.model_proxy_provider import (
5
+ ModelProxyProvider,
6
+ )
7
+ from wxo_agentic_evaluation.service_provider.ollama_provider import (
8
+ OllamaProvider,
9
+ )
10
+ from wxo_agentic_evaluation.service_provider.referenceless_provider_wrapper import (
11
+ ModelProxyProviderLLMKitWrapper,
12
+ WatsonXLLMKitWrapper,
13
+ )
14
+ from wxo_agentic_evaluation.service_provider.watsonx_provider import (
15
+ WatsonXProvider,
16
+ )
6
17
 
7
- import os
8
18
 
9
- def _instantiate_provider(config: ProviderConfig, is_referenceless_eval: bool = False, **kwargs):
19
+ def _instantiate_provider(
20
+ config: ProviderConfig, is_referenceless_eval: bool = False, **kwargs
21
+ ):
10
22
  if config.provider == "watsonx":
11
23
  if is_referenceless_eval:
12
24
  provider = WatsonXLLMKitWrapper
@@ -22,12 +34,17 @@ def _instantiate_provider(config: ProviderConfig, is_referenceless_eval: bool =
22
34
  provider = ModelProxyProvider
23
35
  return provider(model_id=config.model_id, **kwargs)
24
36
  else:
25
- raise RuntimeError(f"target provider is not supported {config.provider}")
26
-
27
- def get_provider(config: ProviderConfig = None, model_id: str = None, referenceless_eval: bool = False, **kwargs):
28
- if config:
29
- return _instantiate_provider(config, **kwargs)
37
+ raise RuntimeError(
38
+ f"target provider is not supported {config.provider}"
39
+ )
30
40
 
41
+
42
+ def get_provider(
43
+ config: ProviderConfig = None,
44
+ model_id: str = None,
45
+ referenceless_eval: bool = False,
46
+ **kwargs,
47
+ ):
31
48
  if not model_id:
32
49
  raise ValueError("model_id must be provided if config is not supplied")
33
50
 
@@ -35,10 +52,13 @@ def get_provider(config: ProviderConfig = None, model_id: str = None, referencel
35
52
  config = ProviderConfig(provider="watsonx", model_id=model_id)
36
53
  return _instantiate_provider(config, referenceless_eval, **kwargs)
37
54
 
38
- if "WO_API_KEY" in os.environ and "WO_INSTANCE" in os.environ:
55
+ if "WO_INSTANCE" in os.environ:
39
56
  config = ProviderConfig(provider="model_proxy", model_id=model_id)
40
57
  return _instantiate_provider(config, referenceless_eval, **kwargs)
41
58
 
59
+ if config:
60
+ return _instantiate_provider(config, **kwargs)
61
+
42
62
  raise RuntimeError(
43
63
  "No provider found. Please either provide a config or set the required environment variables."
44
- )
64
+ )
@@ -1,16 +1,39 @@
1
1
  import os
2
- import requests
3
2
  import time
4
-
5
- from typing import List
6
3
  from threading import Lock
4
+ from typing import List, Tuple
5
+
6
+ import requests
7
7
 
8
8
  from wxo_agentic_evaluation.service_provider.provider import Provider
9
9
  from wxo_agentic_evaluation.utils.utils import is_ibm_cloud_url
10
10
 
11
- AUTH_ENDPOINT_AWS = "https://iam.platform.saas.ibm.com/siusermgr/api/1.0/apikeys/token"
11
+ AUTH_ENDPOINT_AWS = (
12
+ "https://iam.platform.saas.ibm.com/siusermgr/api/1.0/apikeys/token"
13
+ )
12
14
  AUTH_ENDPOINT_IBM_CLOUD = "https://iam.cloud.ibm.com/identity/token"
13
- DEFAULT_PARAM = {"min_new_tokens": 1, "decoding_method": "greedy", "max_new_tokens": 400}
15
+ DEFAULT_PARAM = {
16
+ "min_new_tokens": 1,
17
+ "decoding_method": "greedy",
18
+ "max_new_tokens": 400,
19
+ }
20
+
21
+
22
+ def _infer_cpd_auth_url(instance_url: str) -> str:
23
+ inst = (instance_url or "").rstrip("/")
24
+ if not inst:
25
+ return "/icp4d-api/v1/authorize"
26
+ if "/orchestrate" in inst:
27
+ base = inst.split("/orchestrate", 1)[0].rstrip("/")
28
+ return base + "/icp4d-api/v1/authorize"
29
+ return inst + "/icp4d-api/v1/authorize"
30
+
31
+
32
+ def _normalize_cpd_auth_url(url: str) -> str:
33
+ u = (url or "").rstrip("/")
34
+ if u.endswith("/icp4d-api"):
35
+ return u + "/v1/authorize"
36
+ return url
14
37
 
15
38
 
16
39
  class ModelProxyProvider(Provider):
@@ -21,46 +44,184 @@ class ModelProxyProvider(Provider):
21
44
  instance_url=None,
22
45
  timeout=300,
23
46
  embedding_model_id=None,
24
- params=None
47
+ params=None,
25
48
  ):
26
49
  super().__init__()
27
50
 
28
51
  instance_url = os.environ.get("WO_INSTANCE", instance_url)
29
- api_key = os.environ.get("WO_API_KEY", api_key)
30
- if not instance_url or not api_key:
31
- raise RuntimeError("instance url and WO apikey must be specified to use WO model proxy")
52
+ if not instance_url:
53
+ raise RuntimeError(
54
+ "instance url must be specified to use WO model proxy"
55
+ )
32
56
 
33
57
  self.timeout = timeout
34
- self.model_id = model_id
35
-
58
+ self.model_id = os.environ.get("MODEL_OVERRIDE", model_id)
36
59
  self.embedding_model_id = embedding_model_id
37
60
 
38
- self.api_key = api_key
61
+ self.api_key = os.environ.get("WO_API_KEY", api_key)
62
+ self.username = os.environ.get("WO_USERNAME", None)
63
+ self.password = os.environ.get("WO_PASSWORD", None)
64
+ self.auth_type = os.environ.get(
65
+ "WO_AUTH_TYPE", ""
66
+ ).lower() # explicit override if set, otherwise inferred- match ADK values
67
+ explicit_auth_url = os.environ.get("AUTHORIZATION_URL", None)
68
+
39
69
  self.is_ibm_cloud = is_ibm_cloud_url(instance_url)
40
- self.auth_url = AUTH_ENDPOINT_IBM_CLOUD if self.is_ibm_cloud else AUTH_ENDPOINT_AWS
41
-
42
- self.instance_url = instance_url
43
- self.url = self.instance_url + "/ml/v1/text/generation?version=2024-05-01"
70
+ self.instance_url = instance_url.rstrip("/")
71
+
72
+ self.auth_mode, self.auth_url = self._resolve_auth_mode_and_url(
73
+ explicit_auth_url=explicit_auth_url
74
+ )
75
+ self._wo_ssl_verify = (
76
+ os.environ.get("WO_SSL_VERIFY", "true").lower() != "false"
77
+ )
78
+ env_space_id = os.environ.get("WATSONX_SPACE_ID", None)
79
+ if self.auth_mode == "cpd":
80
+ if not env_space_id or not env_space_id.strip():
81
+ raise RuntimeError(
82
+ "CPD mode requires WATSONX_SPACE_ID environment variable to be set"
83
+ )
84
+ self.space_id = env_space_id.strip()
85
+ else:
86
+ self.space_id = (
87
+ env_space_id.strip()
88
+ if env_space_id and env_space_id.strip()
89
+ else "1"
90
+ )
91
+
92
+ if self.auth_mode == "cpd":
93
+ if "/orchestrate" in self.instance_url:
94
+ self.instance_url = self.instance_url.split("/orchestrate", 1)[
95
+ 0
96
+ ].rstrip("/")
97
+ if not self.username:
98
+ raise RuntimeError("CPD auth requires WO_USERNAME to be set")
99
+ if not (self.password or self.api_key):
100
+ raise RuntimeError(
101
+ "CPD auth requires either WO_PASSWORD or WO_API_KEY to be set (with WO_USERNAME)"
102
+ )
103
+ else:
104
+ if not self.api_key:
105
+ raise RuntimeError(
106
+ "WO_API_KEY must be specified for SaaS or IBM IAM auth"
107
+ )
108
+
109
+ self.url = (
110
+ self.instance_url + "/ml/v1/text/generation?version=2024-05-01"
111
+ )
44
112
  self.embedding_url = self.instance_url + "/ml/v1/text/embeddings"
45
113
 
46
114
  self.lock = Lock()
47
115
  self.token, self.refresh_time = self.get_token()
48
116
  self.params = params if params else DEFAULT_PARAM
49
117
 
50
- def get_token(self):
118
+ def _resolve_auth_mode_and_url(
119
+ self, explicit_auth_url: str | None
120
+ ) -> Tuple[str, str]:
121
+ """
122
+ Returns (auth_mode, auth_url)
123
+ - auth_mode: "cpd" | "ibm_iam" | "saas"
124
+ """
125
+ if explicit_auth_url:
126
+ if "/icp4d-api" in explicit_auth_url:
127
+ return "cpd", _normalize_cpd_auth_url(explicit_auth_url)
128
+ if self.auth_type == "ibm_iam":
129
+ return "ibm_iam", explicit_auth_url
130
+ elif self.auth_type == "saas":
131
+ return "saas", explicit_auth_url
132
+ else:
133
+ mode = "ibm_iam" if self.is_ibm_cloud else "saas"
134
+ return mode, explicit_auth_url
135
+
136
+ if self.auth_type == "cpd":
137
+ inferred_cpd_url = _infer_cpd_auth_url(self.instance_url)
138
+ return "cpd", inferred_cpd_url
139
+ if self.auth_type == "ibm_iam":
140
+ return "ibm_iam", AUTH_ENDPOINT_IBM_CLOUD
141
+ if self.auth_type == "saas":
142
+ return "saas", AUTH_ENDPOINT_AWS
143
+
144
+ if "/orchestrate" in self.instance_url:
145
+ inferred_cpd_url = _infer_cpd_auth_url(self.instance_url)
146
+ return "cpd", inferred_cpd_url
147
+
51
148
  if self.is_ibm_cloud:
52
- payload = {"grant_type": "urn:ibm:params:oauth:grant-type:apikey", "apikey": self.api_key}
53
- resp = requests.post(self.auth_url, data=payload)
54
- token_key = "access_token"
149
+ return "ibm_iam", AUTH_ENDPOINT_IBM_CLOUD
55
150
  else:
56
- payload = {"apikey": self.api_key}
57
- resp = requests.post(self.auth_url, json=payload)
58
- token_key = "token"
151
+ return "saas", AUTH_ENDPOINT_AWS
152
+
153
+ def get_token(self):
154
+ headers = {}
155
+ post_args = {}
156
+ timeout = 10
157
+ exchange_url = self.auth_url
158
+
159
+ if self.auth_mode == "ibm_iam":
160
+ headers = {
161
+ "Accept": "application/json",
162
+ "Content-Type": "application/x-www-form-urlencoded",
163
+ }
164
+ form_data = {
165
+ "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
166
+ "apikey": self.api_key,
167
+ }
168
+ post_args = {"data": form_data}
169
+ resp = requests.post(
170
+ exchange_url,
171
+ headers=headers,
172
+ timeout=timeout,
173
+ verify=self._wo_ssl_verify,
174
+ **post_args,
175
+ )
176
+ elif self.auth_mode == "cpd":
177
+ headers = {
178
+ "Accept": "application/json",
179
+ "Content-Type": "application/json",
180
+ }
181
+ body = {"username": self.username}
182
+ if self.password:
183
+ body["password"] = self.password
184
+ else:
185
+ body["api_key"] = self.api_key
186
+ timeout = self.timeout
187
+ resp = requests.post(
188
+ exchange_url,
189
+ headers=headers,
190
+ json=body,
191
+ timeout=timeout,
192
+ verify=self._wo_ssl_verify,
193
+ )
194
+ else:
195
+ headers = {
196
+ "Accept": "application/json",
197
+ "Content-Type": "application/json",
198
+ }
199
+ post_args = {"json": {"apikey": self.api_key}}
200
+ resp = requests.post(
201
+ exchange_url,
202
+ headers=headers,
203
+ timeout=timeout,
204
+ verify=self._wo_ssl_verify,
205
+ **post_args,
206
+ )
207
+
59
208
  if resp.status_code == 200:
60
209
  json_obj = resp.json()
61
- token = json_obj[token_key]
62
- expires_in = json_obj["expires_in"]
63
- refresh_time = time.time() + int(0.8*expires_in)
210
+ token = json_obj.get("access_token") or json_obj.get("token")
211
+ if not token:
212
+ raise RuntimeError(
213
+ f"No token field found in response: {json_obj!r}"
214
+ )
215
+
216
+ expires_in = json_obj.get("expires_in")
217
+ try:
218
+ expires_in = int(expires_in) if expires_in is not None else None
219
+ except Exception:
220
+ expires_in = None
221
+ if not expires_in or expires_in <= 0:
222
+ expires_in = int(os.environ.get("TOKEN_DEFAULT_EXPIRES_IN", 1))
223
+
224
+ refresh_time = time.time() + int(0.8 * expires_in)
64
225
  return token, refresh_time
65
226
 
66
227
  resp.raise_for_status()
@@ -76,13 +237,24 @@ class ModelProxyProvider(Provider):
76
237
 
77
238
  def encode(self, sentences: List[str]) -> List[list]:
78
239
  if self.embedding_model_id is None:
79
- raise Exception("embedding model id must be specified for text generation")
240
+ raise Exception(
241
+ "embedding model id must be specified for text generation"
242
+ )
80
243
 
81
244
  self.refresh_token_if_expires()
82
245
  headers = self.get_header()
83
- payload = {"inputs": sentences, "model_id": self.embedding_model_id, "space_id": "1"}
84
- #"timeout": self.timeout}
85
- resp = requests.post(self.embedding_url, json=payload, headers=headers)
246
+ payload = {
247
+ "inputs": sentences,
248
+ "model_id": self.embedding_model_id,
249
+ "space_id": self.space_id,
250
+ }
251
+ # "timeout": self.timeout}
252
+ resp = requests.post(
253
+ self.embedding_url,
254
+ json=payload,
255
+ headers=headers,
256
+ verify=self._wo_ssl_verify,
257
+ )
86
258
 
87
259
  if resp.status_code == 200:
88
260
  json_obj = resp.json()
@@ -95,9 +267,16 @@ class ModelProxyProvider(Provider):
95
267
  raise Exception("model id must be specified for text generation")
96
268
  self.refresh_token_if_expires()
97
269
  headers = self.get_header()
98
- payload = {"input": sentence, "model_id": self.model_id, "space_id": "1",
99
- "timeout": self.timeout, "parameters": self.params}
100
- resp = requests.post(self.url, json=payload, headers=headers)
270
+ payload = {
271
+ "input": sentence,
272
+ "model_id": self.model_id,
273
+ "space_id": self.space_id,
274
+ "timeout": self.timeout,
275
+ "parameters": self.params,
276
+ }
277
+ resp = requests.post(
278
+ self.url, json=payload, headers=headers, verify=self._wo_ssl_verify
279
+ )
101
280
  if resp.status_code == 200:
102
281
  return resp.json()["results"][0]["generated_text"]
103
282
 
@@ -105,5 +284,8 @@ class ModelProxyProvider(Provider):
105
284
 
106
285
 
107
286
  if __name__ == "__main__":
108
- provider = ModelProxyProvider(model_id="meta-llama/llama-3-3-70b-instruct", embedding_model_id="ibm/slate-30m-english-rtrvr")
287
+ provider = ModelProxyProvider(
288
+ model_id="meta-llama/llama-3-3-70b-instruct",
289
+ embedding_model_id="ibm/slate-30m-english-rtrvr",
290
+ )
109
291
  print(provider.query("ok"))
@@ -1,17 +1,16 @@
1
- import requests
2
1
  import json
3
- from wxo_agentic_evaluation.service_provider.provider import Provider
4
- from typing import List
5
2
  import os
3
+ from typing import List
4
+
5
+ import requests
6
+
7
+ from wxo_agentic_evaluation.service_provider.provider import Provider
6
8
 
7
9
  OLLAMA_URL = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
8
10
 
9
11
 
10
12
  class OllamaProvider(Provider):
11
- def __init__(
12
- self,
13
- model_id=None
14
- ):
13
+ def __init__(self, model_id=None):
15
14
  self.url = OLLAMA_URL + "/api/generate"
16
15
  self.model_id = model_id
17
16
  super().__init__()
@@ -20,14 +19,14 @@ class OllamaProvider(Provider):
20
19
  payload = {"model": self.model_id, "prompt": sentence}
21
20
  resp = requests.post(self.url, json=payload, stream=True)
22
21
  final_text = ""
23
- data = b''
22
+ data = b""
24
23
  for chunk in resp:
25
24
  data += chunk
26
- if data.endswith(b'\n'):
25
+ if data.endswith(b"\n"):
27
26
  json_obj = json.loads(data)
28
27
  if not json_obj["done"] and json_obj["response"]:
29
28
  final_text += json_obj["response"]
30
- data = b''
29
+ data = b""
31
30
 
32
31
  return final_text
33
32
 
@@ -37,4 +36,4 @@ class OllamaProvider(Provider):
37
36
 
38
37
  if __name__ == "__main__":
39
38
  provider = OllamaProvider(model_id="llama3.1:8b")
40
- print(provider.query("ok"))
39
+ print(provider.query("ok"))
@@ -16,4 +16,3 @@ class Provider(ABC):
16
16
  @abstractmethod
17
17
  def encode(self, sentences: List[str]) -> List[list]:
18
18
  pass
19
-