corethink 1.0.2__tar.gz → 1.0.4__tar.gz
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.
- {corethink-1.0.2 → corethink-1.0.4}/PKG-INFO +1 -1
- {corethink-1.0.2 → corethink-1.0.4}/pyproject.toml +1 -1
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/_version.py +1 -1
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/endpoint.py +28 -9
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/train.py +21 -3
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink.egg-info/PKG-INFO +1 -1
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_endpoint.py +30 -1
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_train.py +44 -0
- {corethink-1.0.2 → corethink-1.0.4}/README.md +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/setup.cfg +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/__init__.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/__main__.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/account.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/cli.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/client.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/__init__.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/admin.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/api.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/archive.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/auth.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/billing.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/capture.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/connection.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/env.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/hub.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/library.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/models.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/commands/workspace.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/config.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/context.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/env_template.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/environment_http_store.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/experiment_cli.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/hosted_profiles.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/hosted_rl.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/hosted_rl_archive.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/hosted_rl_attribution.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/hosted_rl_billing.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/hosted_rl_state.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/jobs_cli.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/main.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/output.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/py.typed +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/rl_cli.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/rl_inspect.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/rl_playground.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/rl_reassessment.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/rl_settings.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/sdk.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink/settings.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink.egg-info/SOURCES.txt +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink.egg-info/dependency_links.txt +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink.egg-info/entry_points.txt +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink.egg-info/requires.txt +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/src/corethink.egg-info/top_level.txt +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_admin.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_archive.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_auth.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_billing.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_capture.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_connection.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_env.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_hub.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_library.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_main_json.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_models.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_version.py +0 -0
- {corethink-1.0.2 → corethink-1.0.4}/tests/test_workspace.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "corethink"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.4"
|
|
8
8
|
description = "The CoreThink Workbench from your terminal: environments, training, endpoints and more."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -291,7 +291,7 @@ def _canary(percent: float | None, minutes: int | None) -> dict[str, Any]:
|
|
|
291
291
|
def list_() -> None:
|
|
292
292
|
"""List every environment's endpoint and what staging and production serve.
|
|
293
293
|
|
|
294
|
-
One row per environment in your workspace: its name and ID, the version on production and on staging (a dash when nothing is released there), and the model name to call. Below the table: how many endpoints serve a program, and the gateway's base URL. Use it to find an environment's ID and model name.
|
|
294
|
+
One row per environment in your workspace: its name and ID, the version on production and on staging (a dash when nothing is released there), and the model name to call. Below the table: how many endpoints serve a program, and the gateway's base URL. "unavailable" means that endpoint could not be read just then; the reason is printed below, and trying again usually works. Use it to find an environment's ID and model name.
|
|
295
295
|
|
|
296
296
|
With `--json`: `gateway` (the base URL) and `endpoints`, each with `id`, `name`, `program`, `production`, `staging` and `error`. Next: `corethink endpoint show ENV`.
|
|
297
297
|
|
|
@@ -354,6 +354,11 @@ def list_() -> None:
|
|
|
354
354
|
if rows:
|
|
355
355
|
live = sum(1 for r in rows if r["production"] or r["staging"])
|
|
356
356
|
ui.info(f"{live} of {len(rows)} serving a program · base URL {base} · model <program>@<alias>")
|
|
357
|
+
failed = [r for r in rows if r["error"] and not r["staging"]]
|
|
358
|
+
if failed:
|
|
359
|
+
ui.warn(
|
|
360
|
+
f"Could not read {len(failed)} endpoint{'' if len(failed) == 1 else 's'} just now ({failed[0]['error']}). Try again."
|
|
361
|
+
)
|
|
357
362
|
|
|
358
363
|
ui.emit({"gateway": base, "endpoints": rows}, human)
|
|
359
364
|
|
|
@@ -937,7 +942,7 @@ def keys_usage(
|
|
|
937
942
|
) -> None:
|
|
938
943
|
"""Show requests, tokens and cost per day for your endpoint keys.
|
|
939
944
|
|
|
940
|
-
Every key of the workspace by default; one key (give its ID or name) or one environment's keys (`--env`).
|
|
945
|
+
Every key of the workspace by default; one key (give its ID or name) or one environment's keys (`--env`). The table lists the days with requests in the last 30 days (or fewer with `--days`), and the totals below.
|
|
941
946
|
|
|
942
947
|
With `--json`: `daily` (each day's `at`, `requests`, `tokens` and `cost`) and `total`.
|
|
943
948
|
|
|
@@ -969,14 +974,14 @@ def keys_usage(
|
|
|
969
974
|
|
|
970
975
|
def human() -> None:
|
|
971
976
|
ui.table(
|
|
972
|
-
rows,
|
|
977
|
+
[r for r in rows if int(r.get("requests") or 0) > 0],
|
|
973
978
|
[
|
|
974
979
|
("Day", lambda r: str(r.get("at") or "")[:10]),
|
|
975
980
|
("Requests", lambda r: f"{int(r.get('requests') or 0):,}"),
|
|
976
981
|
("Tokens", lambda r: f"{int(r.get('tokens') or 0):,}"),
|
|
977
982
|
("Cost", lambda r: ui.usd(r.get("cost"), 4)),
|
|
978
983
|
],
|
|
979
|
-
empty="No
|
|
984
|
+
empty=f"No requests in the last {days} days.",
|
|
980
985
|
)
|
|
981
986
|
ui.info(
|
|
982
987
|
f"{scope}, last {days} days: {total['requests']:,} requests · {total['tokens']:,} tokens · {ui.usd(total['cost'])}"
|
|
@@ -1057,7 +1062,7 @@ def call(
|
|
|
1057
1062
|
) -> None:
|
|
1058
1063
|
"""Send one chat completion to an endpoint and print the answer.
|
|
1059
1064
|
|
|
1060
|
-
Sends your message (after `--system` and the earlier turns in `--messages`, with the tools from `--tools`) to the program on staging (the default), on production (`--alias production`) or on a fixed version, and prints its answer, any tool calls it asks for, and which version answered. The program asks for tool calls the way any OpenAI model does; your code runs them. Use it to try a program before you wire it into your app.
|
|
1065
|
+
Sends your message (after `--system` and the earlier turns in `--messages`, with the tools from `--tools`) to the program on staging (the default), on production (`--alias production`) or on a fixed version, and prints its answer, any tool calls it asks for, and which version answered. The program asks for tool calls the way any OpenAI model does; your code runs them. If the environment has tools, pass them (`--tools usecase.json`): without them the program answers without your data, and the command warns you. Use it to try a program before you wire it into your app.
|
|
1061
1066
|
|
|
1062
1067
|
Calling needs only an endpoint key, from `CORETHINK_API_KEY` (make one with `corethink endpoint keys create ENV`). Give the program as `<workspace>/<environment>`, optionally with `@alias`, to skip the Workbench lookup (no sign-in needed). With `--json`: the whole response, including which program version answered (`corethink_program`).
|
|
1063
1068
|
|
|
@@ -1095,12 +1100,14 @@ def call(
|
|
|
1095
1100
|
ui.error('Give a message: corethink endpoint call ENV "your message".')
|
|
1096
1101
|
raise typer.Exit(2)
|
|
1097
1102
|
|
|
1098
|
-
program, given_alias,
|
|
1103
|
+
program, given_alias, found = _target(env)
|
|
1099
1104
|
model = f"{program}@{given_alias or alias}"
|
|
1100
1105
|
base = _gateway_base(gateway_url)
|
|
1101
1106
|
body: dict[str, Any] = {"model": model, "messages": chat}
|
|
1102
1107
|
if tools_file:
|
|
1103
1108
|
body["tools"] = _tools_from(tools_file)
|
|
1109
|
+
elif found and found.get("tools"):
|
|
1110
|
+
ui.warn(_tools_hint(found, "Without them it answers without your data: pass them with --tools usecase.json"))
|
|
1104
1111
|
try:
|
|
1105
1112
|
with (
|
|
1106
1113
|
httpx.Client(
|
|
@@ -1155,6 +1162,14 @@ def call(
|
|
|
1155
1162
|
ui.emit(answer, human)
|
|
1156
1163
|
|
|
1157
1164
|
|
|
1165
|
+
def _tools_hint(env: dict[str, Any], then: str) -> str:
|
|
1166
|
+
names = ", ".join(env.get("tools") or [])
|
|
1167
|
+
return (
|
|
1168
|
+
f"This program works with tools ({names}). {then} "
|
|
1169
|
+
f"(`corethink env cat {env.get('id')} usecase.json > usecase.json` has them)."
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
|
|
1158
1173
|
def _snippets(base: str, model: str) -> dict[str, str]:
|
|
1159
1174
|
"""The console's examples (Account.tsx apiSnippets), for this endpoint."""
|
|
1160
1175
|
curl = (
|
|
@@ -1194,7 +1209,9 @@ def snippet(
|
|
|
1194
1209
|
|
|
1195
1210
|
A curl command (the default) or a Python script with the OpenAI client (`--lang python`), with this endpoint's base URL and model name filled in. Nothing is called. Set `CORETHINK_API_KEY` to a key of this endpoint before you run it. Streaming is not supported yet.
|
|
1196
1211
|
|
|
1197
|
-
|
|
1212
|
+
If the environment has tools, it also names them and where to get their definitions (usecase.json's `tools`).
|
|
1213
|
+
|
|
1214
|
+
With `--json`: `model`, `base_url`, both examples (`curl` and `python`), and `tools` (the environment's tool names).
|
|
1198
1215
|
|
|
1199
1216
|
Examples:
|
|
1200
1217
|
corethink endpoint snippet refunds --lang python > call.py
|
|
@@ -1204,11 +1221,11 @@ def snippet(
|
|
|
1204
1221
|
if lang not in ("curl", "python"):
|
|
1205
1222
|
ui.error("--lang is curl or python.")
|
|
1206
1223
|
raise typer.Exit(2)
|
|
1207
|
-
program, given_alias,
|
|
1224
|
+
program, given_alias, found = _target(env)
|
|
1208
1225
|
model = f"{program}@{given_alias or alias}"
|
|
1209
1226
|
base = _gateway_base(gateway_url)
|
|
1210
1227
|
snippets = _snippets(base, model)
|
|
1211
|
-
data = {"model": model, "base_url": base, **snippets}
|
|
1228
|
+
data = {"model": model, "base_url": base, **snippets, "tools": (found or {}).get("tools") or []}
|
|
1212
1229
|
|
|
1213
1230
|
def human() -> None:
|
|
1214
1231
|
sys.stdout.write(snippets[lang] + "\n")
|
|
@@ -1216,6 +1233,8 @@ def snippet(
|
|
|
1216
1233
|
ui.info(
|
|
1217
1234
|
"Set CORETHINK_API_KEY to a key of this endpoint (`corethink endpoint keys create`). Streaming is not supported yet."
|
|
1218
1235
|
)
|
|
1236
|
+
if found and found.get("tools"):
|
|
1237
|
+
ui.info(_tools_hint(found, "Send them as `tools`; your code runs each call and sends back its result"))
|
|
1219
1238
|
|
|
1220
1239
|
ui.emit(data, human)
|
|
1221
1240
|
|
|
@@ -1021,6 +1021,12 @@ def credit_available() -> tuple[float | None, dict[str, Any] | None]:
|
|
|
1021
1021
|
return (float(available) if available is not None else None), me.get("welcomeCredit")
|
|
1022
1022
|
|
|
1023
1023
|
|
|
1024
|
+
def likely_cost(estimate: dict[str, Any] | None) -> float | None:
|
|
1025
|
+
"""What a run is likely to cost: from past runs, else the first-run estimate (a Hub copy's: what its Hub program cost)."""
|
|
1026
|
+
estimate = estimate or {}
|
|
1027
|
+
return estimate.get("costUsd") or (estimate.get("firstRun") or {}).get("typicalUsd")
|
|
1028
|
+
|
|
1029
|
+
|
|
1024
1030
|
def default_budget(estimate_usd: Any, available: float | None, most: float | None, least: float = 1) -> float:
|
|
1025
1031
|
"""About 1.5 times the estimate (whole dollars), else $20; never above the most or the credit (Training.tsx)."""
|
|
1026
1032
|
wanted = math.ceil(float(estimate_usd) * 1.5) if estimate_usd and float(estimate_usd) > 0 else FIRST_RUN_BUDGET
|
|
@@ -1075,6 +1081,8 @@ def estimate_lines(estimate: dict[str, Any] | None, budget: float | None = None)
|
|
|
1075
1081
|
text += " The limit is lower; it may stop early."
|
|
1076
1082
|
elif estimate.get("costUsd") is None and first and (first.get("lowUsd") or 0) > budget:
|
|
1077
1083
|
text += " The limit is below the low end; it will likely stop early."
|
|
1084
|
+
elif estimate.get("costUsd") is None and first and (first.get("typicalUsd") or 0) > budget:
|
|
1085
|
+
text += " The limit is below the likely cost; it may stop early."
|
|
1078
1086
|
if estimate.get("waiting"):
|
|
1079
1087
|
text += f" {plural(estimate['waiting'], 'run')} waiting ahead."
|
|
1080
1088
|
lines = [text]
|
|
@@ -1206,6 +1214,16 @@ def _estimate(plan: Plan) -> dict[str, Any] | None:
|
|
|
1206
1214
|
return None
|
|
1207
1215
|
|
|
1208
1216
|
|
|
1217
|
+
def eval_tasks(plan: Plan) -> int:
|
|
1218
|
+
"""Tasks in the final check: at most those held out from training."""
|
|
1219
|
+
usecase = plan.usecase or {}
|
|
1220
|
+
if usecase.get("split"):
|
|
1221
|
+
return int(usecase["split"].get("test") or 0)
|
|
1222
|
+
if usecase.get("scenarios"):
|
|
1223
|
+
return min(plan.check_tasks, withheld_tasks(int(usecase["scenarios"]), plan.share))
|
|
1224
|
+
return plan.check_tasks
|
|
1225
|
+
|
|
1226
|
+
|
|
1209
1227
|
def _limits_panel(plan: Plan, budget: float, available: float | None, estimate: dict[str, Any] | None) -> None:
|
|
1210
1228
|
"""The console's "Limits for this run" box, on stderr (stdout is for the run itself)."""
|
|
1211
1229
|
usecase = plan.usecase
|
|
@@ -1227,7 +1245,7 @@ def _limits_panel(plan: Plan, budget: float, available: float | None, estimate:
|
|
|
1227
1245
|
("Hyperparameters", "Advanced" if advanced else "Automatic"),
|
|
1228
1246
|
("Spending", f"max {ui.usd(budget, 0 if float(budget).is_integer() else 2)}"),
|
|
1229
1247
|
("Trains on", trains),
|
|
1230
|
-
("Eval set tasks", str(
|
|
1248
|
+
("Eval set tasks", str(eval_tasks(plan))),
|
|
1231
1249
|
]
|
|
1232
1250
|
if plan.method:
|
|
1233
1251
|
rows.append(("Training method", METHOD_LABEL.get(plan.method, plan.method)))
|
|
@@ -1350,7 +1368,7 @@ def start(
|
|
|
1350
1368
|
least, most = run_limits()
|
|
1351
1369
|
available, welcome = credit_available()
|
|
1352
1370
|
estimate = _estimate(plan)
|
|
1353
|
-
budget = limit if limit is not None else default_budget((estimate
|
|
1371
|
+
budget = limit if limit is not None else default_budget(likely_cost(estimate), available, most, least)
|
|
1354
1372
|
problem = budget_problem(budget, least, most, available, welcome)
|
|
1355
1373
|
if problem:
|
|
1356
1374
|
raise fail(problem)
|
|
@@ -1546,7 +1564,7 @@ def estimate(
|
|
|
1546
1564
|
def human() -> None:
|
|
1547
1565
|
least, most = run_limits()
|
|
1548
1566
|
available, _ = credit_available()
|
|
1549
|
-
budget = default_budget(answer
|
|
1567
|
+
budget = default_budget(likely_cost(answer), available, most, least)
|
|
1550
1568
|
for line in estimate_lines(answer):
|
|
1551
1569
|
ui.out.print(line, style="dim" if line.startswith(" ") else None)
|
|
1552
1570
|
ui.out.print(
|
|
@@ -5,7 +5,7 @@ import pytest
|
|
|
5
5
|
ENVS = [
|
|
6
6
|
{"id": "refunds-22982c", "org": "acme", "name": "Refunds"},
|
|
7
7
|
{"id": "refunds-old-1a2b3c", "org": "acme", "name": "Refunds (old)"},
|
|
8
|
-
{"id": "triage-77aa00", "org": "acme", "name": "Triage"},
|
|
8
|
+
{"id": "triage-77aa00", "org": "acme", "name": "Triage", "tools": ["lookup_order", "refund"]},
|
|
9
9
|
]
|
|
10
10
|
STAGING_HASH = "b" * 64
|
|
11
11
|
PROD_HASH = "a" * 64
|
|
@@ -143,6 +143,7 @@ def test_list(served, run):
|
|
|
143
143
|
assert table.code == 0, table.err
|
|
144
144
|
assert "v-aaaaaaaaaaaa" in table.out and "acme/refunds-22982c@production" in table.out
|
|
145
145
|
assert "unavailable" in table.out
|
|
146
|
+
assert "Could not read 1 endpoint just now (unavailable)" in table.err
|
|
146
147
|
assert "1 of 3 serving" in table.err and "https://gateway.test/v1" in table.err
|
|
147
148
|
data = run("--json", "endpoint", "list").json()
|
|
148
149
|
assert data["gateway"] == "https://gateway.test/v1"
|
|
@@ -416,6 +417,14 @@ def test_keys_usage(served, run):
|
|
|
416
417
|
assert served.called("GET", "/api/v1/harnesses/keys/usage")[-1].params == {"environment": "triage-77aa00"}
|
|
417
418
|
one = run("endpoint", "keys", "usage", "web-app")
|
|
418
419
|
assert one.code == 0 and "2026-10-03" in one.out and "web-app, last 30 days: 6 requests" in one.err
|
|
420
|
+
# the table lists the days with requests (JSON keeps every day)
|
|
421
|
+
quiet = [{**day, "requests": 0, "tokens": 0, "cost": 0} for day in daily[:-1]] + daily[-1:]
|
|
422
|
+
served.on("GET", "/api/v1/harnesses/keys/usage", {"daily": quiet})
|
|
423
|
+
table = run("endpoint", "keys", "usage")
|
|
424
|
+
assert "2026-10-30" in table.out and "2026-10-29" not in table.out
|
|
425
|
+
assert len(run("--json", "endpoint", "keys", "usage").json()["daily"]) == 30
|
|
426
|
+
served.on("GET", "/api/v1/harnesses/keys/usage", {"daily": [{**day, "requests": 0} for day in quiet[:-1]]})
|
|
427
|
+
assert "No requests in the last 30 days." in run("endpoint", "keys", "usage").err
|
|
419
428
|
served.on("GET", "/api/v1/harnesses/keys/usage", {"daily": None})
|
|
420
429
|
assert "cannot be read" in run("endpoint", "keys", "usage").err
|
|
421
430
|
|
|
@@ -460,6 +469,20 @@ def test_call_with_the_env_key(served, run, monkeypatch):
|
|
|
460
469
|
as_json = run("--json", "endpoint", "call", "refunds", "Hi", "--alias", "production").json()
|
|
461
470
|
assert as_json["corethink_program"]["version"] == "harness-bbbbbbbbbbbb"
|
|
462
471
|
assert served.called("POST", "/v1/chat/completions")[-1].body["model"] == "acme/refunds-22982c@production"
|
|
472
|
+
assert "works with tools" not in result.err
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def test_call_warns_when_the_program_needs_tools_and_none_are_given(served, run, tmp_path, monkeypatch):
|
|
476
|
+
monkeypatch.setenv("CORETHINK_API_KEY", "ctk_endpointkey")
|
|
477
|
+
monkeypatch.setenv("CORETHINK_GATEWAY_URL", "https://gateway.test/v1")
|
|
478
|
+
served.on("POST", "/v1/chat/completions", ANSWER)
|
|
479
|
+
bare = run("endpoint", "call", "triage", "Refund order 7")
|
|
480
|
+
assert bare.code == 0, bare.err
|
|
481
|
+
assert "This program works with tools (lookup_order, refund)" in bare.err
|
|
482
|
+
assert "corethink env cat triage-77aa00 usecase.json" in bare.err
|
|
483
|
+
tools = tmp_path / "tools.json"
|
|
484
|
+
tools.write_text(json.dumps([{"type": "function", "function": {"name": "lookup_order"}}]))
|
|
485
|
+
assert "works with tools" not in run("endpoint", "call", "triage", "Hi", "--tools", str(tools)).err
|
|
463
486
|
|
|
464
487
|
|
|
465
488
|
def test_call_a_program_directly_with_tools_from_usecase_json(wb, run, tmp_path, monkeypatch):
|
|
@@ -606,11 +629,17 @@ def test_snippets(served, run):
|
|
|
606
629
|
"https://gateway.test/v1",
|
|
607
630
|
)
|
|
608
631
|
assert 'base_url="https://gateway.test/v1"' in python.out and 'model="acme/refunds-22982c@production"' in python.out
|
|
632
|
+
assert "works with tools" not in curl.err
|
|
609
633
|
data = run("--json", "endpoint", "snippet", "acme/x@production").json()
|
|
610
634
|
assert data["model"] == "acme/x@production" and set(data) == {
|
|
611
635
|
"model",
|
|
612
636
|
"base_url",
|
|
613
637
|
"curl",
|
|
614
638
|
"python",
|
|
639
|
+
"tools",
|
|
615
640
|
}
|
|
641
|
+
assert data["tools"] == []
|
|
642
|
+
tooled = run("endpoint", "snippet", "triage")
|
|
643
|
+
assert "This program works with tools (lookup_order, refund). Send them as `tools`" in tooled.err
|
|
644
|
+
assert run("--json", "endpoint", "snippet", "triage").json()["tools"] == ["lookup_order", "refund"]
|
|
616
645
|
assert run("endpoint", "snippet", "refunds", "--lang", "go").code == 2
|
|
@@ -250,6 +250,28 @@ def test_start_default_limit_follows_the_estimate(form, run):
|
|
|
250
250
|
assert result.json()["job_id"] == RUN
|
|
251
251
|
|
|
252
252
|
|
|
253
|
+
def test_start_default_limit_follows_a_first_run_estimate_within_the_credit(form, run):
|
|
254
|
+
"""No past run: the first-run estimate sets the limit (it was $20 whatever the estimate: MDM's Hub program cost $57)."""
|
|
255
|
+
first = {"typicalUsd": 12, "lowUsd": 8.4, "highUsd": 18, "drivers": [], "assumptions": []}
|
|
256
|
+
form.on(
|
|
257
|
+
"GET",
|
|
258
|
+
f"{JOBS}:estimate",
|
|
259
|
+
{"domain": "custom", "costUsd": None, "minutes": None, "basedOn": 0, "firstRun": first},
|
|
260
|
+
)
|
|
261
|
+
result = run("--json", "train", "start", "Payroll", "--yes")
|
|
262
|
+
assert result.code == 0, result.err
|
|
263
|
+
assert form.called("POST", JOBS)[-1].body["budgetUsd"] == 18
|
|
264
|
+
form.on(
|
|
265
|
+
"GET",
|
|
266
|
+
f"{JOBS}:estimate",
|
|
267
|
+
{"domain": "custom", "costUsd": None, "firstRun": {**first, "typicalUsd": 57.3, "lowUsd": 60}},
|
|
268
|
+
)
|
|
269
|
+
dear = run("train", "start", "Payroll", "--yes")
|
|
270
|
+
assert dear.code == 0, dear.err
|
|
271
|
+
assert form.called("POST", JOBS)[-1].body["budgetUsd"] == 50 # the credit available
|
|
272
|
+
assert "will likely stop early" in " ".join(dear.err.split())
|
|
273
|
+
|
|
274
|
+
|
|
253
275
|
def test_start_advanced_settings_payload_with_providers_chosen(form, run):
|
|
254
276
|
providers = {
|
|
255
277
|
"qwen/qwen3.7-flash": {
|
|
@@ -957,3 +979,25 @@ def test_start_from_an_earlier_run_repeats_its_environment_and_settings(form, ru
|
|
|
957
979
|
|
|
958
980
|
def test_start_needs_an_environment_or_a_run(form, run):
|
|
959
981
|
assert run("train", "start", "--yes").code == 2
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def test_a_first_runs_limit_below_its_likely_cost_is_flagged():
|
|
985
|
+
from corethink.commands.train import estimate_lines
|
|
986
|
+
|
|
987
|
+
first = {"typicalUsd": 57.3, "lowUsd": 40.1, "highUsd": 85.9, "drivers": [], "assumptions": []}
|
|
988
|
+
estimate = {"costUsd": None, "minutes": None, "firstRun": first}
|
|
989
|
+
assert "may stop early" not in estimate_lines(estimate, 86)[0]
|
|
990
|
+
assert estimate_lines(estimate, 45)[0].endswith("The limit is below the likely cost; it may stop early.")
|
|
991
|
+
assert estimate_lines(estimate, 30)[0].endswith("The limit is below the low end; it will likely stop early.")
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
def test_the_eval_set_is_at_most_the_tasks_held_out():
|
|
995
|
+
from corethink.commands.train import Plan, eval_tasks
|
|
996
|
+
|
|
997
|
+
made = Plan.__new__(Plan)
|
|
998
|
+
made.usecase, made.share, made.check_tasks = {"scenarios": 80}, 0.6, 40
|
|
999
|
+
assert eval_tasks(made) == 32 # 80 tasks, 48 to train on: 32 held out, not the 40 asked for
|
|
1000
|
+
made.usecase = {"scenarios": 200}
|
|
1001
|
+
assert eval_tasks(made) == 40
|
|
1002
|
+
made.usecase = {"scenarios": 200, "split": {"train": 150, "test": 50}}
|
|
1003
|
+
assert eval_tasks(made) == 50
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|