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.
Files changed (113) hide show
  1. tabagent/__init__.py +57 -0
  2. tabagent/adapters/__init__.py +68 -0
  3. tabagent/adapters/common.py +138 -0
  4. tabagent/adapters/cuga.py +623 -0
  5. tabagent/adapters/detect.py +182 -0
  6. tabagent/adapters/live.py +111 -0
  7. tabagent/adapters/logfiles.py +728 -0
  8. tabagent/adapters/openai_messages.py +672 -0
  9. tabagent/adapters/tau2.py +195 -0
  10. tabagent/agent.py +219 -0
  11. tabagent/api.py +1016 -0
  12. tabagent/artifact.py +262 -0
  13. tabagent/auto.py +260 -0
  14. tabagent/baselines.py +62 -0
  15. tabagent/cards/__init__.py +1 -0
  16. tabagent/cards/appworld_subagent_routing.yaml +77 -0
  17. tabagent/cards/llmrouterbench_model_routing.yaml +113 -0
  18. tabagent/cards/mind2web_element.yaml +88 -0
  19. tabagent/cards/mind2web_operation.yaml +55 -0
  20. tabagent/cards/skillsbench_skill_gating.yaml +57 -0
  21. tabagent/cards/skillsbench_skill_gating_enriched.yaml +81 -0
  22. tabagent/cards/skillsbench_skill_select.yaml +61 -0
  23. tabagent/cards/skillsbench_skill_select_noprior.yaml +53 -0
  24. tabagent/cards/tau2_domain_shortlisting.yaml +85 -0
  25. tabagent/cards/tau2_tool_shortlisting.yaml +58 -0
  26. tabagent/cli.py +851 -0
  27. tabagent/datasets/__init__.py +138 -0
  28. tabagent/datasets/_core.py +171 -0
  29. tabagent/datasets/appworld.py +207 -0
  30. tabagent/datasets/folds/appworld_subagent_routing.json +590 -0
  31. tabagent/datasets/folds/llmrouterbench_tau2.json +283 -0
  32. tabagent/datasets/folds/mind2web_element_150.json +155 -0
  33. tabagent/datasets/folds/mind2web_groupkfold5.json +1014 -0
  34. tabagent/datasets/folds/mind2web_split.json +1014 -0
  35. tabagent/datasets/folds/skillsbench_skill_gating.json +89 -0
  36. tabagent/datasets/folds/skillsbench_skill_gating_enriched.json +92 -0
  37. tabagent/datasets/folds/skillsbench_skill_select.json +92 -0
  38. tabagent/datasets/folds/tau2_agents.json +599 -0
  39. tabagent/datasets/folds/tau2_airline.json +29 -0
  40. tabagent/datasets/folds/tau2_all_domains.json +144 -0
  41. tabagent/datasets/folds/tau2_retail.json +59 -0
  42. tabagent/datasets/folds/tau2_telecom.json +66 -0
  43. tabagent/datasets/llmrouterbench.py +249 -0
  44. tabagent/datasets/manifests/llmrouterbench_tau2_dense.json +102 -0
  45. tabagent/datasets/manifests/mind2web_train.json +71 -0
  46. tabagent/datasets/manifests/skillsbench_official_selected.json +328 -0
  47. tabagent/datasets/manifests/tau2_official_final.json +185 -0
  48. tabagent/datasets/manifests/tau2_sft_final.json +17 -0
  49. tabagent/datasets/mind2web.py +342 -0
  50. tabagent/datasets/skillsbench.py +207 -0
  51. tabagent/datasets/tau2.py +361 -0
  52. tabagent/defaults.py +23 -0
  53. tabagent/drift.py +701 -0
  54. tabagent/evaluation/__init__.py +36 -0
  55. tabagent/evaluation/cv.py +220 -0
  56. tabagent/evaluation/discovery.py +320 -0
  57. tabagent/evaluation/metrics.py +266 -0
  58. tabagent/evaluation/stats.py +116 -0
  59. tabagent/hub.py +44 -0
  60. tabagent/integrations/__init__.py +1 -0
  61. tabagent/integrations/cuga.py +186 -0
  62. tabagent/integrations/openai_tools.py +232 -0
  63. tabagent/prometheus.py +196 -0
  64. tabagent/py.typed +0 -0
  65. tabagent/report.py +237 -0
  66. tabagent/scanner.py +1113 -0
  67. tabagent/serve.py +873 -0
  68. tabagent/sklearn.py +353 -0
  69. tabagent/tabhead.py +427 -0
  70. tabagent/table.py +178 -0
  71. tabagent/tabschema/__init__.py +22 -0
  72. tabagent/tabschema/agents.py +1304 -0
  73. tabagent/tabschema/audit.py +408 -0
  74. tabagent/tabschema/card.py +392 -0
  75. tabagent/tabschema/checks.py +307 -0
  76. tabagent/tabschema/compile.py +1811 -0
  77. tabagent/tabschema/estimate.py +326 -0
  78. tabagent/tabschema/features.py +114 -0
  79. tabagent/tabschema/fitted.py +474 -0
  80. tabagent/tabschema/judges.py +203 -0
  81. tabagent/tabschema/llm.py +908 -0
  82. tabagent/tabschema/orchestrator.py +1823 -0
  83. tabagent/tabschema/prompts/__init__.py +80 -0
  84. tabagent/tabschema/prompts/code_extractor.txt +93 -0
  85. tabagent/tabschema/prompts/code_validator.txt +39 -0
  86. tabagent/tabschema/prompts/feature_aggregator.txt +37 -0
  87. tabagent/tabschema/prompts/feature_analyzer.txt +35 -0
  88. tabagent/tabschema/prompts/judge_generality.txt +51 -0
  89. tabagent/tabschema/prompts/judge_impact.txt +52 -0
  90. tabagent/tabschema/prompts/judge_relevance.txt +49 -0
  91. tabagent/tabschema/prompts/meta_judge.txt +65 -0
  92. tabagent/tabschema/prompts/output_auditor.txt +34 -0
  93. tabagent/tabschema/prompts/tabagent_analyzer_candidates.txt +6 -0
  94. tabagent/tabschema/prompts/tabagent_auditor_candidates.txt +5 -0
  95. tabagent/tabschema/prompts/tabagent_candidate_note.txt +3 -0
  96. tabagent/tabschema/prompts/tabagent_extractor.txt +3 -0
  97. tabagent/tabschema/prompts/tabagent_extractor_candidates.txt +9 -0
  98. tabagent/tabschema/prompts/tabagent_validator_candidates.txt +9 -0
  99. tabagent/tabschema/runlog.py +626 -0
  100. tabagent/tabschema/runstore.py +267 -0
  101. tabagent/tabschema/sampling.py +143 -0
  102. tabagent/tabschema/sandbox.py +623 -0
  103. tabagent/tabschema/trajectory.py +989 -0
  104. tabagent/tabschema/usefulness.py +193 -0
  105. tabagent/tabschema/verify.py +1303 -0
  106. tabagent/tabschema/warm.py +1073 -0
  107. tabagent/tabsynth.py +344 -0
  108. tabagent-0.1.0.dist-info/METADATA +313 -0
  109. tabagent-0.1.0.dist-info/RECORD +113 -0
  110. tabagent-0.1.0.dist-info/WHEEL +4 -0
  111. tabagent-0.1.0.dist-info/entry_points.txt +2 -0
  112. tabagent-0.1.0.dist-info/licenses/LICENSE +201 -0
  113. 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