tabagent 0.1.0__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.
- tabagent/__init__.py +57 -0
- tabagent/adapters/__init__.py +68 -0
- tabagent/adapters/common.py +138 -0
- tabagent/adapters/cuga.py +623 -0
- tabagent/adapters/detect.py +182 -0
- tabagent/adapters/live.py +111 -0
- tabagent/adapters/logfiles.py +728 -0
- tabagent/adapters/openai_messages.py +672 -0
- tabagent/adapters/tau2.py +195 -0
- tabagent/agent.py +219 -0
- tabagent/api.py +1016 -0
- tabagent/artifact.py +262 -0
- tabagent/auto.py +260 -0
- tabagent/baselines.py +62 -0
- tabagent/cards/__init__.py +1 -0
- tabagent/cards/appworld_subagent_routing.yaml +77 -0
- tabagent/cards/llmrouterbench_model_routing.yaml +113 -0
- tabagent/cards/mind2web_element.yaml +88 -0
- tabagent/cards/mind2web_operation.yaml +55 -0
- tabagent/cards/skillsbench_skill_gating.yaml +57 -0
- tabagent/cards/skillsbench_skill_gating_enriched.yaml +81 -0
- tabagent/cards/skillsbench_skill_select.yaml +61 -0
- tabagent/cards/skillsbench_skill_select_noprior.yaml +53 -0
- tabagent/cards/tau2_domain_shortlisting.yaml +85 -0
- tabagent/cards/tau2_tool_shortlisting.yaml +58 -0
- tabagent/cli.py +851 -0
- tabagent/datasets/__init__.py +138 -0
- tabagent/datasets/_core.py +171 -0
- tabagent/datasets/appworld.py +207 -0
- tabagent/datasets/folds/appworld_subagent_routing.json +590 -0
- tabagent/datasets/folds/llmrouterbench_tau2.json +283 -0
- tabagent/datasets/folds/mind2web_element_150.json +155 -0
- tabagent/datasets/folds/mind2web_groupkfold5.json +1014 -0
- tabagent/datasets/folds/mind2web_split.json +1014 -0
- tabagent/datasets/folds/skillsbench_skill_gating.json +89 -0
- tabagent/datasets/folds/skillsbench_skill_gating_enriched.json +92 -0
- tabagent/datasets/folds/skillsbench_skill_select.json +92 -0
- tabagent/datasets/folds/tau2_agents.json +599 -0
- tabagent/datasets/folds/tau2_airline.json +29 -0
- tabagent/datasets/folds/tau2_all_domains.json +144 -0
- tabagent/datasets/folds/tau2_retail.json +59 -0
- tabagent/datasets/folds/tau2_telecom.json +66 -0
- tabagent/datasets/llmrouterbench.py +249 -0
- tabagent/datasets/manifests/llmrouterbench_tau2_dense.json +102 -0
- tabagent/datasets/manifests/mind2web_train.json +71 -0
- tabagent/datasets/manifests/skillsbench_official_selected.json +328 -0
- tabagent/datasets/manifests/tau2_official_final.json +185 -0
- tabagent/datasets/manifests/tau2_sft_final.json +17 -0
- tabagent/datasets/mind2web.py +342 -0
- tabagent/datasets/skillsbench.py +207 -0
- tabagent/datasets/tau2.py +361 -0
- tabagent/defaults.py +23 -0
- tabagent/drift.py +701 -0
- tabagent/evaluation/__init__.py +36 -0
- tabagent/evaluation/cv.py +220 -0
- tabagent/evaluation/discovery.py +320 -0
- tabagent/evaluation/metrics.py +266 -0
- tabagent/evaluation/stats.py +116 -0
- tabagent/hub.py +44 -0
- tabagent/integrations/__init__.py +1 -0
- tabagent/integrations/cuga.py +186 -0
- tabagent/integrations/openai_tools.py +232 -0
- tabagent/prometheus.py +196 -0
- tabagent/py.typed +0 -0
- tabagent/report.py +237 -0
- tabagent/scanner.py +1113 -0
- tabagent/serve.py +873 -0
- tabagent/sklearn.py +353 -0
- tabagent/tabhead.py +427 -0
- tabagent/table.py +178 -0
- tabagent/tabschema/__init__.py +22 -0
- tabagent/tabschema/agents.py +1304 -0
- tabagent/tabschema/audit.py +408 -0
- tabagent/tabschema/card.py +392 -0
- tabagent/tabschema/checks.py +307 -0
- tabagent/tabschema/compile.py +1811 -0
- tabagent/tabschema/estimate.py +326 -0
- tabagent/tabschema/features.py +114 -0
- tabagent/tabschema/fitted.py +474 -0
- tabagent/tabschema/judges.py +203 -0
- tabagent/tabschema/llm.py +908 -0
- tabagent/tabschema/orchestrator.py +1823 -0
- tabagent/tabschema/prompts/__init__.py +80 -0
- tabagent/tabschema/prompts/code_extractor.txt +93 -0
- tabagent/tabschema/prompts/code_validator.txt +39 -0
- tabagent/tabschema/prompts/feature_aggregator.txt +37 -0
- tabagent/tabschema/prompts/feature_analyzer.txt +35 -0
- tabagent/tabschema/prompts/judge_generality.txt +51 -0
- tabagent/tabschema/prompts/judge_impact.txt +52 -0
- tabagent/tabschema/prompts/judge_relevance.txt +49 -0
- tabagent/tabschema/prompts/meta_judge.txt +65 -0
- tabagent/tabschema/prompts/output_auditor.txt +34 -0
- tabagent/tabschema/prompts/tabagent_analyzer_candidates.txt +6 -0
- tabagent/tabschema/prompts/tabagent_auditor_candidates.txt +5 -0
- tabagent/tabschema/prompts/tabagent_candidate_note.txt +3 -0
- tabagent/tabschema/prompts/tabagent_extractor.txt +3 -0
- tabagent/tabschema/prompts/tabagent_extractor_candidates.txt +9 -0
- tabagent/tabschema/prompts/tabagent_validator_candidates.txt +9 -0
- tabagent/tabschema/runlog.py +626 -0
- tabagent/tabschema/runstore.py +267 -0
- tabagent/tabschema/sampling.py +143 -0
- tabagent/tabschema/sandbox.py +623 -0
- tabagent/tabschema/trajectory.py +989 -0
- tabagent/tabschema/usefulness.py +193 -0
- tabagent/tabschema/verify.py +1303 -0
- tabagent/tabschema/warm.py +1073 -0
- tabagent/tabsynth.py +344 -0
- tabagent-0.1.0.dist-info/METADATA +313 -0
- tabagent-0.1.0.dist-info/RECORD +113 -0
- tabagent-0.1.0.dist-info/WHEEL +4 -0
- tabagent-0.1.0.dist-info/entry_points.txt +2 -0
- tabagent-0.1.0.dist-info/licenses/LICENSE +201 -0
- tabagent-0.1.0.dist-info/licenses/NOTICE +39 -0
tabagent/__init__.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""TabAgent: replace an LLM agent's closed-set decision heads with a cheap tabular classifier.
|
|
2
|
+
|
|
3
|
+
The framework has three parts, named as in the paper:
|
|
4
|
+
|
|
5
|
+
* **TabSchema** extracts schema, state and dependency features from an agent's successful trajectories.
|
|
6
|
+
* **TabSynth** adds schema-aligned, label-preserving paraphrase rows where trajectory coverage is thin.
|
|
7
|
+
* **TabHead** is a tabular classifier that scores every candidate of a decision in a single pass.
|
|
8
|
+
|
|
9
|
+
The library logs progress (onboarding stages, TabSchema discovery, cross-validation folds, downloads) to the
|
|
10
|
+
``tabagent`` logger at INFO level; enable it with ``logging.basicConfig(level=logging.INFO)`` or the CLI's
|
|
11
|
+
``-v``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from tabagent.agent import TabAgent
|
|
17
|
+
from tabagent.api import TabAgentHead, load, onboard
|
|
18
|
+
from tabagent.evaluation import cross_validate, cross_validate_discovery
|
|
19
|
+
from tabagent.scanner import LogCheck, ScanReport, check_logs, scan
|
|
20
|
+
from tabagent.sklearn import TabAgentClassifier, TabAgentRanker, TabAgentRouter, pointwise, scorer
|
|
21
|
+
from tabagent.tabhead import TabHead
|
|
22
|
+
from tabagent.table import DecisionSpec, DecisionTable, LeakageError
|
|
23
|
+
from tabagent.tabschema import Feature, FeatureCard, TabSchema, TabSchemaConfig
|
|
24
|
+
from tabagent.tabschema.verify import verify_head
|
|
25
|
+
from tabagent.tabsynth import TabSynth
|
|
26
|
+
|
|
27
|
+
__version__ = "0.1.0"
|
|
28
|
+
|
|
29
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"DecisionSpec",
|
|
33
|
+
"DecisionTable",
|
|
34
|
+
"Feature",
|
|
35
|
+
"FeatureCard",
|
|
36
|
+
"LeakageError",
|
|
37
|
+
"LogCheck",
|
|
38
|
+
"ScanReport",
|
|
39
|
+
"TabAgent",
|
|
40
|
+
"TabAgentClassifier",
|
|
41
|
+
"TabAgentHead",
|
|
42
|
+
"TabAgentRanker",
|
|
43
|
+
"TabAgentRouter",
|
|
44
|
+
"TabHead",
|
|
45
|
+
"TabSchema",
|
|
46
|
+
"TabSchemaConfig",
|
|
47
|
+
"TabSynth",
|
|
48
|
+
"check_logs",
|
|
49
|
+
"cross_validate",
|
|
50
|
+
"cross_validate_discovery",
|
|
51
|
+
"load",
|
|
52
|
+
"onboard",
|
|
53
|
+
"pointwise",
|
|
54
|
+
"scan",
|
|
55
|
+
"scorer",
|
|
56
|
+
"verify_head",
|
|
57
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Trajectory adapters: read an agent's native logs into TabSchema trajectories and decision points.
|
|
2
|
+
|
|
3
|
+
An adapter implements ``load(path) -> list[Trajectory]``, ``decision_points(traj, target)``,
|
|
4
|
+
``candidates(dp)``, ``labels(dp)`` and ``components(trajs)``, and exposes ``outcome_keys``. A decision
|
|
5
|
+
point's ``prefix`` holds only what was known before the decision (``{<task fields>, "steps": [{"name": ...,
|
|
6
|
+
...}]}``); ``labels`` may read the full run (for tool shortlisting, the tools the successful run used).
|
|
7
|
+
|
|
8
|
+
For ``tabagent.scan`` an adapter may also implement ``llm_calls(traj) -> list[LLMCall]``: every LLM call of a
|
|
9
|
+
run, with what it chose, what it was offered, the free text it wrote and the tokens, latency and cost the log
|
|
10
|
+
records (``read_usage`` reads the common shapes), plus ``scan_notes`` on how its logs record them. All the
|
|
11
|
+
adapters below implement it.
|
|
12
|
+
|
|
13
|
+
* ``OpenAIMessagesAdapter``: chat-message runs (OpenAI, Anthropic Messages API format, LangChain dumps);
|
|
14
|
+
* ``Tau2Adapter``: τ²-bench results files;
|
|
15
|
+
* ``CugaAdapter``: CUGA agent trajectories (the API shortlister built in);
|
|
16
|
+
* ``GenericJsonAdapter``: any log with named steps, with your own candidates and labels;
|
|
17
|
+
* ``detect_adapter(path)``: picks one of the above from the first files.
|
|
18
|
+
|
|
19
|
+
Every adapter reads files through ``tabagent.adapters.logfiles``: encodings, damaged JSON and JSON Lines,
|
|
20
|
+
linked folders, values nested too deeply, and large inputs read again from their files instead of held in
|
|
21
|
+
memory. Whatever is skipped or repaired is reported in the adapter's ``warnings``, runs of another log format
|
|
22
|
+
included (``detect_adapter(..., on_other_formats="error")`` refuses them with ``MixedFormatsError``).
|
|
23
|
+
``tabagent.check_logs`` (CLI ``tabagent check-logs``) diagnoses a log folder before an onboarding.
|
|
24
|
+
|
|
25
|
+
At decision time, ``live_prefix(adapter, run, target)`` builds a live run's prefix exactly as in training, and
|
|
26
|
+
``describe_adapter`` / ``adapter_from_description`` record and rebuild the adapter a head was trained with.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from tabagent.adapters.common import parse_catalog
|
|
30
|
+
from tabagent.adapters.cuga import SHORTLISTER, CugaAdapter
|
|
31
|
+
from tabagent.adapters.detect import detect_adapter, detect_format
|
|
32
|
+
from tabagent.adapters.live import describe as describe_adapter
|
|
33
|
+
from tabagent.adapters.live import from_description as adapter_from_description
|
|
34
|
+
from tabagent.adapters.live import live_prefix, run_before
|
|
35
|
+
from tabagent.adapters.logfiles import LogChangedError, MixedFormatsError
|
|
36
|
+
from tabagent.adapters.openai_messages import (
|
|
37
|
+
NEXT_TOOL_CALL,
|
|
38
|
+
TEXT_REPLY,
|
|
39
|
+
TOOL_SHORTLIST,
|
|
40
|
+
OpenAIMessagesAdapter,
|
|
41
|
+
normalize_messages,
|
|
42
|
+
)
|
|
43
|
+
from tabagent.adapters.tau2 import Tau2Adapter
|
|
44
|
+
from tabagent.tabschema.trajectory import GenericJsonAdapter, LLMCall, output_fields, read_usage
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"NEXT_TOOL_CALL",
|
|
48
|
+
"SHORTLISTER",
|
|
49
|
+
"TEXT_REPLY",
|
|
50
|
+
"TOOL_SHORTLIST",
|
|
51
|
+
"CugaAdapter",
|
|
52
|
+
"GenericJsonAdapter",
|
|
53
|
+
"LLMCall",
|
|
54
|
+
"LogChangedError",
|
|
55
|
+
"MixedFormatsError",
|
|
56
|
+
"OpenAIMessagesAdapter",
|
|
57
|
+
"Tau2Adapter",
|
|
58
|
+
"adapter_from_description",
|
|
59
|
+
"describe_adapter",
|
|
60
|
+
"detect_adapter",
|
|
61
|
+
"detect_format",
|
|
62
|
+
"live_prefix",
|
|
63
|
+
"normalize_messages",
|
|
64
|
+
"output_fields",
|
|
65
|
+
"parse_catalog",
|
|
66
|
+
"read_usage",
|
|
67
|
+
"run_before",
|
|
68
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Helpers shared by the trajectory adapters: finding log files and parsing tool catalogs into candidates."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from tabagent.adapters.logfiles import ( # noqa: F401 - re-exported: the adapters' file helpers
|
|
11
|
+
SKIP_FILES,
|
|
12
|
+
LogParseError,
|
|
13
|
+
iter_files,
|
|
14
|
+
parse_bytes,
|
|
15
|
+
run_id,
|
|
16
|
+
)
|
|
17
|
+
from tabagent.tabschema.trajectory import Candidate, parse_json, spec_text
|
|
18
|
+
|
|
19
|
+
_PARAM_KEYS = ("parameters", "input_schema", "inputSchema", "args_schema")
|
|
20
|
+
_SPEC_KEYS = frozenset({"description", "api_name", "function", "properties", *_PARAM_KEYS})
|
|
21
|
+
#: Keys of a JSON Schema (an object's argument schema), never tool names.
|
|
22
|
+
_SCHEMA_KEYS = frozenset(
|
|
23
|
+
{"type", "required", "items", "enum", "additionalProperties", "$schema", "$defs", "title", "default"}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _is_spec(d: Any) -> bool:
|
|
28
|
+
return isinstance(d, dict) and (bool(_SPEC_KEYS & d.keys()) or isinstance(d.get("name"), str))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_tool_schema(d: Any) -> bool:
|
|
32
|
+
"""Whether ``d`` is a tool schema with its parameters (OpenAI, a bare function, the Anthropic Messages API
|
|
33
|
+
format, MCP, CUGA API docs), as opposed to a ``{"name", "description", "args"}`` candidate record."""
|
|
34
|
+
return isinstance(d, dict) and (
|
|
35
|
+
isinstance(d.get("function"), dict) or any(k in d for k in (*_PARAM_KEYS, "properties", "api_name"))
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _arg_names(params: Any) -> list[str]:
|
|
40
|
+
"""Argument names from a parameter schema: the ``properties`` of a JSON Schema, or CUGA's
|
|
41
|
+
``[{"name": ...}, ...]``; anything malformed gives no names."""
|
|
42
|
+
if isinstance(params, dict):
|
|
43
|
+
props = params.get("properties")
|
|
44
|
+
if isinstance(props, dict):
|
|
45
|
+
return [str(a) for a in props]
|
|
46
|
+
params = props
|
|
47
|
+
if isinstance(params, list):
|
|
48
|
+
return [str(a["name"]) for a in params if isinstance(a, dict) and isinstance(a.get("name"), str)]
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _text(value: Any) -> str:
|
|
53
|
+
if value is None:
|
|
54
|
+
return ""
|
|
55
|
+
return value if isinstance(value, str) else json.dumps(value, default=str, ensure_ascii=False)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def tool_candidate(spec: dict[str, Any], name: str = "", volatile_keys: Iterable[str] = ()) -> Candidate:
|
|
59
|
+
"""One tool schema (OpenAI, Anthropic Messages API format, MCP, CUGA API docs) -> ``Candidate``, the
|
|
60
|
+
schema itself kept as its catalog entry (``Candidate.spec``), without its top-level ``volatile_keys``."""
|
|
61
|
+
f = spec["function"] if isinstance(spec.get("function"), dict) else spec
|
|
62
|
+
params = next((f[k] for k in _PARAM_KEYS if k in f), f if "properties" in f else None)
|
|
63
|
+
raw_name = f.get("name") or f.get("api_name") or name
|
|
64
|
+
tid = raw_name.strip() if isinstance(raw_name, str) else str(raw_name) if raw_name is not None else ""
|
|
65
|
+
return Candidate(
|
|
66
|
+
tid, tid, _text(f.get("description")), ", ".join(_arg_names(params)), spec_text(spec, volatile_keys)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def parse_catalog(catalog: Any, volatile_keys: Iterable[str] = ()) -> dict[str, Candidate]:
|
|
71
|
+
"""A tool catalog in any common shape -> ``{tool id: Candidate}``.
|
|
72
|
+
|
|
73
|
+
Accepts a JSON file path; a list of ``Candidate`` objects, tool names or tool schemas (OpenAI
|
|
74
|
+
``{"type": "function", "function": {...}}``, bare functions, the Anthropic Messages API format's
|
|
75
|
+
``input_schema``, MCP ``inputSchema``); a ``{name: description}`` or ``{name: schema}`` mapping; and
|
|
76
|
+
CUGA API docs (``{app: {api_name: {...}}}``). Argument hints are the parameter names, comma-joined, and a
|
|
77
|
+
schema is kept as the candidate's catalog entry (``Candidate.spec``), without its top-level
|
|
78
|
+
``volatile_keys``: fields a catalog writes anew in every run (CUGA's ``canary_string``). A tool listed
|
|
79
|
+
twice keeps its last entry; entries without a name are left out.
|
|
80
|
+
"""
|
|
81
|
+
if catalog is None:
|
|
82
|
+
return {}
|
|
83
|
+
if isinstance(catalog, (str, Path)):
|
|
84
|
+
p = Path(catalog)
|
|
85
|
+
if not p.is_file():
|
|
86
|
+
raise FileNotFoundError(
|
|
87
|
+
f"tool catalog {str(catalog)[:200]!r}: no such file; pass the path of a JSON file with tool "
|
|
88
|
+
"schemas, or the catalog itself (a list of schemas or a {name: description} mapping)"
|
|
89
|
+
)
|
|
90
|
+
try:
|
|
91
|
+
data = parse_bytes(p.read_bytes())
|
|
92
|
+
except LogParseError as e:
|
|
93
|
+
raise ValueError(f"tool catalog {p}: not valid JSON ({e})") from None
|
|
94
|
+
return parse_catalog(data, volatile_keys)
|
|
95
|
+
if isinstance(catalog, dict) and _is_spec(catalog):
|
|
96
|
+
catalog = [catalog]
|
|
97
|
+
out: dict[str, Candidate] = {}
|
|
98
|
+
if isinstance(catalog, dict):
|
|
99
|
+
for key, v in catalog.items():
|
|
100
|
+
if isinstance(v, str):
|
|
101
|
+
out[str(key)] = Candidate(str(key), str(key), v)
|
|
102
|
+
elif _is_spec(v) or (isinstance(v, dict) and _SCHEMA_KEYS & v.keys()): # a bare argument schema
|
|
103
|
+
c = tool_candidate(v, str(key), volatile_keys)
|
|
104
|
+
out[c.id] = c
|
|
105
|
+
elif isinstance(v, (dict, list)): # one level of grouping, e.g. CUGA apps
|
|
106
|
+
out.update(parse_catalog(v, volatile_keys))
|
|
107
|
+
return {k: c for k, c in out.items() if k}
|
|
108
|
+
if not isinstance(catalog, (list, tuple)):
|
|
109
|
+
return {}
|
|
110
|
+
for item in catalog:
|
|
111
|
+
if isinstance(item, Candidate):
|
|
112
|
+
c = item
|
|
113
|
+
elif isinstance(item, str):
|
|
114
|
+
c = Candidate(item.strip(), item.strip())
|
|
115
|
+
elif isinstance(item, dict):
|
|
116
|
+
c = tool_candidate(item, volatile_keys=volatile_keys)
|
|
117
|
+
else:
|
|
118
|
+
continue
|
|
119
|
+
if c.id:
|
|
120
|
+
out[c.id] = c
|
|
121
|
+
return out
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def run_catalog(value: Any) -> tuple[dict[str, Candidate], bool]:
|
|
125
|
+
"""The catalog a run's own ``tools`` field records, and whether the field could be read as one: a list or
|
|
126
|
+
mapping of tools, or JSON text of one (some loggers write the list as a string). Never a file path."""
|
|
127
|
+
if value is None or value == "" or value == [] or value == {}:
|
|
128
|
+
return {}, True
|
|
129
|
+
if isinstance(value, str):
|
|
130
|
+
value = parse_json(value)
|
|
131
|
+
if value is None:
|
|
132
|
+
return {}, False
|
|
133
|
+
if not isinstance(value, (list, dict)):
|
|
134
|
+
return {}, False
|
|
135
|
+
try:
|
|
136
|
+
return parse_catalog(value), True
|
|
137
|
+
except (TypeError, ValueError, AttributeError):
|
|
138
|
+
return {}, False
|