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.
- ibm_watsonx_orchestrate_evaluation_framework-1.1.3.dist-info/METADATA +35 -0
- {ibm_watsonx_orchestrate_evaluation_framework-1.1.1.dist-info → ibm_watsonx_orchestrate_evaluation_framework-1.1.3.dist-info}/RECORD +65 -60
- wxo_agentic_evaluation/analytics/tools/analyzer.py +36 -21
- wxo_agentic_evaluation/analytics/tools/main.py +18 -7
- wxo_agentic_evaluation/analytics/tools/types.py +26 -11
- wxo_agentic_evaluation/analytics/tools/ux.py +75 -31
- wxo_agentic_evaluation/analyze_run.py +69 -48
- wxo_agentic_evaluation/annotate.py +6 -4
- wxo_agentic_evaluation/arg_configs.py +9 -3
- wxo_agentic_evaluation/batch_annotate.py +78 -25
- wxo_agentic_evaluation/data_annotator.py +18 -13
- wxo_agentic_evaluation/description_quality_checker.py +20 -14
- wxo_agentic_evaluation/evaluation.py +42 -0
- wxo_agentic_evaluation/evaluation_package.py +117 -70
- wxo_agentic_evaluation/external_agent/__init__.py +18 -7
- wxo_agentic_evaluation/external_agent/external_validate.py +46 -35
- wxo_agentic_evaluation/external_agent/performance_test.py +32 -20
- wxo_agentic_evaluation/external_agent/types.py +12 -5
- wxo_agentic_evaluation/inference_backend.py +183 -79
- wxo_agentic_evaluation/llm_matching.py +4 -3
- wxo_agentic_evaluation/llm_rag_eval.py +7 -4
- wxo_agentic_evaluation/llm_user.py +7 -3
- wxo_agentic_evaluation/main.py +175 -67
- wxo_agentic_evaluation/metrics/llm_as_judge.py +2 -2
- wxo_agentic_evaluation/metrics/metrics.py +26 -12
- wxo_agentic_evaluation/otel_support/evaluate_tau.py +67 -0
- wxo_agentic_evaluation/otel_support/evaluate_tau_traces.py +176 -0
- wxo_agentic_evaluation/otel_support/otel_message_conversion.py +21 -0
- wxo_agentic_evaluation/otel_support/tasks_test.py +1226 -0
- wxo_agentic_evaluation/prompt/template_render.py +32 -11
- wxo_agentic_evaluation/quick_eval.py +49 -23
- wxo_agentic_evaluation/record_chat.py +70 -33
- wxo_agentic_evaluation/red_teaming/attack_evaluator.py +58 -18
- wxo_agentic_evaluation/red_teaming/attack_generator.py +38 -18
- wxo_agentic_evaluation/red_teaming/attack_runner.py +43 -27
- wxo_agentic_evaluation/referenceless_eval/function_calling/metrics/base.py +3 -1
- wxo_agentic_evaluation/referenceless_eval/function_calling/metrics/loader.py +23 -15
- wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/adapters.py +13 -8
- wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/pipeline.py +41 -13
- wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/semantic_checker.py +26 -16
- wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/static_checker.py +17 -11
- wxo_agentic_evaluation/referenceless_eval/function_calling/pipeline/types.py +44 -29
- wxo_agentic_evaluation/referenceless_eval/metrics/field.py +13 -5
- wxo_agentic_evaluation/referenceless_eval/metrics/metric.py +16 -5
- wxo_agentic_evaluation/referenceless_eval/metrics/metrics_runner.py +8 -3
- wxo_agentic_evaluation/referenceless_eval/metrics/prompt.py +6 -2
- wxo_agentic_evaluation/referenceless_eval/metrics/utils.py +5 -1
- wxo_agentic_evaluation/referenceless_eval/prompt/runner.py +16 -3
- wxo_agentic_evaluation/referenceless_eval/referenceless_eval.py +23 -12
- wxo_agentic_evaluation/resource_map.py +2 -1
- wxo_agentic_evaluation/service_instance.py +103 -21
- wxo_agentic_evaluation/service_provider/__init__.py +33 -13
- wxo_agentic_evaluation/service_provider/model_proxy_provider.py +216 -34
- wxo_agentic_evaluation/service_provider/ollama_provider.py +10 -11
- wxo_agentic_evaluation/service_provider/provider.py +0 -1
- wxo_agentic_evaluation/service_provider/referenceless_provider_wrapper.py +34 -21
- wxo_agentic_evaluation/service_provider/watsonx_provider.py +50 -22
- wxo_agentic_evaluation/tool_planner.py +128 -44
- wxo_agentic_evaluation/type.py +12 -9
- wxo_agentic_evaluation/utils/__init__.py +1 -0
- wxo_agentic_evaluation/utils/open_ai_tool_extractor.py +41 -20
- wxo_agentic_evaluation/utils/rich_utils.py +23 -9
- wxo_agentic_evaluation/utils/utils.py +83 -52
- ibm_watsonx_orchestrate_evaluation_framework-1.1.1.dist-info/METADATA +0 -386
- {ibm_watsonx_orchestrate_evaluation_framework-1.1.1.dist-info → ibm_watsonx_orchestrate_evaluation_framework-1.1.3.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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,
|
|
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)
|
|
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),
|
|
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(
|
|
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
|
|
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 =
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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(
|
|
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(
|
|
26
|
-
|
|
27
|
-
|
|
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 "
|
|
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 =
|
|
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 = {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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.
|
|
41
|
-
|
|
42
|
-
self.
|
|
43
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
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(
|
|
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 = {
|
|
84
|
-
|
|
85
|
-
|
|
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 = {
|
|
99
|
-
|
|
100
|
-
|
|
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(
|
|
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
|
|
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"))
|