Skip to content

Commit 5e76d33

Browse files
Dylan Huangcursoragent
andauthored
Add eval-protocol user-agent to fireworks api requests (#311)
* Add User-Agent header to Fireworks API requests Co-authored-by: dhuang <dhuang@fireworks.ai> * Remove unused User-Agent header from RewardFunction Co-authored-by: dhuang <dhuang@fireworks.ai> * Remove unnecessary User-Agent header from Fireworks adapter Co-authored-by: dhuang <dhuang@fireworks.ai> * Refactor: Use FireworksAPIClient for all API requests This change centralizes API request logic into a new FireworksAPIClient class, simplifying and standardizing how the Fireworks API is interacted with across the project. It removes redundant request setup code and ensures consistent headers are sent. Co-authored-by: dhuang <dhuang@fireworks.ai> * fix lint errors * fix lint errors * Update User-Agent format in get_user_agent function to use 'eval-protocol' prefix * added tests * update * reject absolute within path arg * revert --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent c3de2a2 commit 5e76d33

File tree

8 files changed

+93
-24
lines changed

8 files changed

+93
-24
lines changed

eval_protocol/adapters/fireworks_tracing.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,12 @@ def search_logs(self, tags: List[str], limit: int = 100, hours_back: int = 24) -
273273
if not tags:
274274
raise ValueError("At least one tag is required to fetch logs")
275275

276-
headers = {"Authorization": f"Bearer {os.environ.get('FIREWORKS_API_KEY')}"}
276+
from ..common_utils import get_user_agent
277+
278+
headers = {
279+
"Authorization": f"Bearer {os.environ.get('FIREWORKS_API_KEY')}",
280+
"User-Agent": get_user_agent(),
281+
}
277282
params: Dict[str, Any] = {"tags": tags, "limit": limit, "hours_back": hours_back, "program": "eval_protocol"}
278283

279284
# Try /logs first, fall back to /v1/logs if not found
@@ -398,7 +403,12 @@ def get_evaluation_rows(
398403
else:
399404
url = f"{self.base_url}/v1/traces/pointwise"
400405

401-
headers = {"Authorization": f"Bearer {os.environ.get('FIREWORKS_API_KEY')}"}
406+
from ..common_utils import get_user_agent
407+
408+
headers = {
409+
"Authorization": f"Bearer {os.environ.get('FIREWORKS_API_KEY')}",
410+
"User-Agent": get_user_agent(),
411+
}
402412

403413
result = None
404414
try:

eval_protocol/auth.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,16 @@ def verify_api_key_and_get_account_id(
242242
if not resolved_key:
243243
return None
244244
resolved_base = api_base or get_fireworks_api_base()
245+
246+
from .common_utils import get_user_agent
247+
245248
url = f"{resolved_base.rstrip('/')}/verifyApiKey"
246-
headers = {"Authorization": f"Bearer {resolved_key}"}
249+
headers = {
250+
"Authorization": f"Bearer {resolved_key}",
251+
"User-Agent": get_user_agent(),
252+
}
247253
resp = requests.get(url, headers=headers, timeout=10)
254+
248255
if resp.status_code != 200:
249256
logger.debug("verifyApiKey returned status %s", resp.status_code)
250257
return None

eval_protocol/common_utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
import requests
66

77

8+
def get_user_agent() -> str:
9+
"""
10+
Returns the user-agent string for eval-protocol CLI requests.
11+
12+
Format: eval-protocol-cli/{version}
13+
14+
Returns:
15+
User-agent string identifying the eval-protocol CLI and version.
16+
"""
17+
try:
18+
from . import __version__
19+
20+
return f"eval-protocol/{__version__}"
21+
except Exception:
22+
return "eval-protocol/unknown"
23+
24+
825
def load_jsonl(file_path: str) -> List[Dict[str, Any]]:
926
"""
1027
Reads a JSONL file where each line is a valid JSON object and returns a list of these objects.

eval_protocol/evaluation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
get_fireworks_api_key,
2121
verify_api_key_and_get_account_id,
2222
)
23+
from eval_protocol.common_utils import get_user_agent
2324
from eval_protocol.typed_interface import EvaluationMode
2425

2526
from eval_protocol.get_pep440_version import get_pep440_version
@@ -405,6 +406,7 @@ def preview(self, sample_file, max_samples=5):
405406
headers = {
406407
"Authorization": f"Bearer {auth_token}",
407408
"Content-Type": "application/json",
409+
"User-Agent": get_user_agent(),
408410
}
409411
logger.info(f"Previewing evaluator using API endpoint: {url} with account: {account_id}")
410412
logger.debug(f"Preview API Request URL: {url}")
@@ -748,6 +750,7 @@ def create(self, evaluator_id, display_name=None, description=None, force=False)
748750
headers = {
749751
"Authorization": f"Bearer {auth_token}",
750752
"Content-Type": "application/json",
753+
"User-Agent": get_user_agent(),
751754
}
752755

753756
self._ensure_requirements_present(os.getcwd())

eval_protocol/fireworks_rft.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import requests
1212

1313
from .auth import get_fireworks_account_id, get_fireworks_api_base, get_fireworks_api_key
14+
from .common_utils import get_user_agent
1415

1516

1617
def _map_api_host_to_app_host(api_base: str) -> str:
@@ -157,12 +158,17 @@ def create_dataset_from_jsonl(
157158
display_name: Optional[str],
158159
jsonl_path: str,
159160
) -> Tuple[str, Dict[str, Any]]:
160-
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
161+
headers = {
162+
"Authorization": f"Bearer {api_key}",
163+
"Content-Type": "application/json",
164+
"User-Agent": get_user_agent(),
165+
}
161166
# Count examples quickly
162167
example_count = 0
163168
with open(jsonl_path, "r", encoding="utf-8") as f:
164169
for _ in f:
165170
example_count += 1
171+
166172
dataset_url = f"{api_base.rstrip('/')}/v1/accounts/{account_id}/datasets"
167173
payload = {
168174
"dataset": {
@@ -181,7 +187,10 @@ def create_dataset_from_jsonl(
181187
upload_url = f"{api_base.rstrip('/')}/v1/accounts/{account_id}/datasets/{dataset_id}:upload"
182188
with open(jsonl_path, "rb") as f:
183189
files = {"file": f}
184-
up_headers = {"Authorization": f"Bearer {api_key}"}
190+
up_headers = {
191+
"Authorization": f"Bearer {api_key}",
192+
"User-Agent": get_user_agent(),
193+
}
185194
up_resp = requests.post(upload_url, files=files, headers=up_headers, timeout=600)
186195
if up_resp.status_code not in (200, 201):
187196
raise RuntimeError(f"Dataset upload failed: {up_resp.status_code} {up_resp.text}")
@@ -195,7 +204,12 @@ def create_reinforcement_fine_tuning_job(
195204
body: Dict[str, Any],
196205
) -> Dict[str, Any]:
197206
url = f"{api_base.rstrip('/')}/v1/accounts/{account_id}/reinforcementFineTuningJobs"
198-
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "Accept": "application/json"}
207+
headers = {
208+
"Authorization": f"Bearer {api_key}",
209+
"Content-Type": "application/json",
210+
"Accept": "application/json",
211+
"User-Agent": get_user_agent(),
212+
}
199213
resp = requests.post(url, json=body, headers=headers, timeout=60)
200214
if resp.status_code not in (200, 201):
201215
raise RuntimeError(f"RFT job creation failed: {resp.status_code} {resp.text}")

eval_protocol/generation/clients.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from omegaconf import DictConfig
1414
from pydantic import BaseModel # Added for new models
1515

16+
from ..common_utils import get_user_agent
17+
1618
logger = logging.getLogger(__name__)
1719

1820

@@ -101,6 +103,7 @@ async def generate(
101103
"Authorization": f"Bearer {self.api_key}",
102104
"Content-Type": "application/json",
103105
"Accept": "application/json",
106+
"User-Agent": get_user_agent(),
104107
}
105108

106109
debug_payload_log = json.loads(json.dumps(payload))

eval_protocol/platform_api.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
get_fireworks_api_base,
1212
get_fireworks_api_key,
1313
)
14+
from eval_protocol.common_utils import get_user_agent
1415

1516
logger = logging.getLogger(__name__)
1617

@@ -95,6 +96,7 @@ def create_or_update_fireworks_secret(
9596
headers = {
9697
"Authorization": f"Bearer {resolved_api_key}",
9798
"Content-Type": "application/json",
99+
"User-Agent": get_user_agent(),
98100
}
99101

100102
# The secret_id for GET/PATCH/DELETE operations is the key_name.
@@ -107,10 +109,10 @@ def create_or_update_fireworks_secret(
107109

108110
# Check if secret exists using GET (path uses normalized resource id)
109111
resource_id = _normalize_secret_resource_id(key_name)
110-
get_url = f"{resolved_api_base.rstrip('/')}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
111112
secret_exists = False
112113
try:
113-
response = requests.get(get_url, headers=headers, timeout=10)
114+
url = f"{resolved_api_base}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
115+
response = requests.get(url, headers=headers, timeout=10)
114116
if response.status_code == 200:
115117
secret_exists = True
116118
logger.info(f"Secret '{key_name}' already exists. Will attempt to update.")
@@ -131,7 +133,6 @@ def create_or_update_fireworks_secret(
131133

132134
if secret_exists:
133135
# Update existing secret (PATCH)
134-
patch_url = f"{resolved_api_base.rstrip('/')}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
135136
# Body for PATCH requires 'keyName' and 'value'.
136137
# Transform key_name for payload: uppercase and underscores
137138
payload_key_name = key_name.upper().replace("-", "_")
@@ -146,7 +147,8 @@ def create_or_update_fireworks_secret(
146147
payload = {"keyName": payload_key_name, "value": secret_value}
147148
try:
148149
logger.debug(f"PATCH payload for '{key_name}': {payload}")
149-
response = requests.patch(patch_url, headers=headers, json=payload, timeout=30)
150+
url = f"{resolved_api_base}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
151+
response = requests.patch(url, json=payload, headers=headers, timeout=30)
150152
response.raise_for_status()
151153
logger.info(f"Successfully updated secret '{key_name}' on Fireworks platform.")
152154
return True
@@ -158,7 +160,6 @@ def create_or_update_fireworks_secret(
158160
return False
159161
else:
160162
# Create new secret (POST)
161-
post_url = f"{resolved_api_base.rstrip('/')}/v1/accounts/{resolved_account_id}/secrets"
162163
# Body for POST is gatewaySecret. 'name' field in payload is the resource path.
163164
# Let's assume for POST, the 'name' in payload can be omitted or is the key_name.
164165
# The API should ideally use 'keyName' from URL or a specific 'secretId' in payload for creation if 'name' is server-assigned.
@@ -183,7 +184,8 @@ def create_or_update_fireworks_secret(
183184
}
184185
try:
185186
logger.debug(f"POST payload for '{key_name}': {payload}")
186-
response = requests.post(post_url, headers=headers, json=payload, timeout=30)
187+
url = f"{resolved_api_base}/v1/accounts/{resolved_account_id}/secrets"
188+
response = requests.post(url, json=payload, headers=headers, timeout=30)
187189
response.raise_for_status()
188190
logger.info(
189191
f"Successfully created secret '{key_name}' on Fireworks platform. Full name: {response.json().get('name')}"
@@ -217,11 +219,14 @@ def get_fireworks_secret(
217219
logger.error("Missing Fireworks API key, base URL, or account ID for getting secret.")
218220
return None
219221

220-
headers = {"Authorization": f"Bearer {resolved_api_key}"}
222+
headers = {
223+
"Authorization": f"Bearer {resolved_api_key}",
224+
"User-Agent": get_user_agent(),
225+
}
221226
resource_id = _normalize_secret_resource_id(key_name)
222-
url = f"{resolved_api_base.rstrip('/')}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
223227

224228
try:
229+
url = f"{resolved_api_base}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
225230
response = requests.get(url, headers=headers, timeout=10)
226231
if response.status_code == 200:
227232
logger.info(f"Successfully retrieved secret '{key_name}'.")
@@ -254,11 +259,14 @@ def delete_fireworks_secret(
254259
logger.error("Missing Fireworks API key, base URL, or account ID for deleting secret.")
255260
return False
256261

257-
headers = {"Authorization": f"Bearer {resolved_api_key}"}
262+
headers = {
263+
"Authorization": f"Bearer {resolved_api_key}",
264+
"User-Agent": get_user_agent(),
265+
}
258266
resource_id = _normalize_secret_resource_id(key_name)
259-
url = f"{resolved_api_base.rstrip('/')}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
260267

261268
try:
269+
url = f"{resolved_api_base}/v1/accounts/{resolved_account_id}/secrets/{resource_id}"
262270
response = requests.delete(url, headers=headers, timeout=30)
263271
if response.status_code == 200 or response.status_code == 204: # 204 No Content is also success for DELETE
264272
logger.info(f"Successfully deleted secret '{key_name}'.")

eval_protocol/pytest/handle_persist_flow.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import re
88
from typing import Any
99

10+
from eval_protocol.common_utils import get_user_agent
1011
from eval_protocol.directory_utils import find_eval_protocol_dir
1112
from eval_protocol.models import EvaluationRow
1213
from eval_protocol.pytest.store_experiment_link import store_experiment_link
14+
1315
import requests
1416

1517

@@ -127,10 +129,14 @@ def get_auth_value(key: str) -> str | None:
127129
)
128130
continue
129131

130-
headers = {"Authorization": f"Bearer {fireworks_api_key}", "Content-Type": "application/json"}
132+
api_base = "https://api.fireworks.ai"
133+
headers = {
134+
"Authorization": f"Bearer {fireworks_api_key}",
135+
"Content-Type": "application/json",
136+
"User-Agent": get_user_agent(),
137+
}
131138

132139
# Make dataset first
133-
dataset_url = f"https://api.fireworks.ai/v1/accounts/{fireworks_account_id}/datasets"
134140

135141
dataset_payload = { # pyright: ignore[reportUnknownVariableType]
136142
"dataset": {
@@ -142,6 +148,7 @@ def get_auth_value(key: str) -> str | None:
142148
"datasetId": dataset_name,
143149
}
144150

151+
dataset_url = f"{api_base}/v1/accounts/{fireworks_account_id}/datasets"
145152
dataset_response = requests.post(dataset_url, json=dataset_payload, headers=headers) # pyright: ignore[reportUnknownArgumentType]
146153

147154
# Skip if dataset creation failed
@@ -157,13 +164,13 @@ def get_auth_value(key: str) -> str | None:
157164
dataset_id = dataset_data.get("datasetId", dataset_name) # pyright: ignore[reportAny]
158165

159166
# Upload the JSONL file content
160-
upload_url = (
161-
f"https://api.fireworks.ai/v1/accounts/{fireworks_account_id}/datasets/{dataset_id}:upload"
162-
)
163-
upload_headers = {"Authorization": f"Bearer {fireworks_api_key}"}
164-
167+
upload_url = f"{api_base}/v1/accounts/{fireworks_account_id}/datasets/{dataset_id}:upload"
165168
with open(exp_file, "rb") as f:
166169
files = {"file": f}
170+
upload_headers = {
171+
"Authorization": f"Bearer {fireworks_api_key}",
172+
"User-Agent": get_user_agent(),
173+
}
167174
upload_response = requests.post(upload_url, files=files, headers=upload_headers)
168175

169176
# Skip if upload failed
@@ -176,7 +183,6 @@ def get_auth_value(key: str) -> str | None:
176183
continue
177184

178185
# Create evaluation job (optional - don't skip experiment if this fails)
179-
eval_job_url = f"https://api.fireworks.ai/v1/accounts/{fireworks_account_id}/evaluationJobs"
180186
# Truncate job ID to fit 63 character limit
181187
job_id_base = f"{dataset_name}-job"
182188
if len(job_id_base) > 63:
@@ -194,6 +200,7 @@ def get_auth_value(key: str) -> str | None:
194200
},
195201
}
196202

203+
eval_job_url = f"{api_base}/v1/accounts/{fireworks_account_id}/evaluationJobs"
197204
eval_response = requests.post(eval_job_url, json=eval_job_payload, headers=headers)
198205

199206
if eval_response.status_code in [200, 201]:

0 commit comments

Comments
 (0)