Buckets:
| # -*- coding: utf-8 -*- | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Dict, Iterable, List, Mapping, Sequence, Tuple | |
| import numpy as np | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.preprocessing import normalize | |
| from agent_rec.data import DatasetBundle, collect_data, load_tools | |
| from agent_rec.features import UNK_LLM_TOKEN, UNK_TOOL_TOKEN | |
| from agent_rec.rerank_eval_utils import build_agent_text_cache | |
| class GenerationConfig: | |
| """Config for formatting and retrieval used by the generator.""" | |
| tool_sep_token: str = "<TOOL_SEP>" | |
| end_token: str = "<SPECIAL_END>" | |
| max_tools: int = 4 | |
| tfidf_max_features: int = 5000 | |
| class AgentGenerationInfo: | |
| """Lightweight view over agent metadata needed for generation.""" | |
| aid: str | |
| llm_token: str | |
| tool_tokens: List[str] | |
| text: str | |
| def _normalize_token(token: str) -> str: | |
| token = token.strip() if token else "" | |
| if not token: | |
| return "" | |
| return token.replace(" ", "_") | |
| class GenerativeStructuredRecommender: | |
| """ | |
| Lightweight (inference-only) generative-style recommender that produces | |
| structured token outputs like:: | |
| <LLM_TOKEN> <TOOL_SEP> <TOOL_A> <TOOL_B> <SPECIAL_END> | |
| Retrieval uses TF-IDF over agent descriptions to pick the most relevant | |
| agent(s), then formats the agent's LLM/tool metadata into the requested | |
| token template. | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| vectorizer: TfidfVectorizer, | |
| agent_matrix, | |
| agents: Sequence[AgentGenerationInfo], | |
| config: GenerationConfig, | |
| ) -> None: | |
| self.vectorizer = vectorizer | |
| self.agent_matrix = agent_matrix | |
| self.agents = list(agents) | |
| self.config = config | |
| def from_bundle( | |
| cls, | |
| bundle: DatasetBundle, | |
| *, | |
| tools: Mapping[str, dict], | |
| config: GenerationConfig | None = None, | |
| agent_order: Iterable[str] | None = None, | |
| ) -> "GenerativeStructuredRecommender": | |
| """Build a generator from an in-memory dataset bundle. | |
| ``bundle`` should come from the shared data loader utilities (e.g. | |
| :func:`agent_rec.run_common.bootstrap_run`). This keeps the generator | |
| aligned with the rest of the ``run_*.py`` entrypoints so they share | |
| logging, seeding, and data splits. | |
| """ | |
| cfg = config or GenerationConfig() | |
| agent_text_cache = build_agent_text_cache(bundle.all_agents, tools) | |
| agent_infos: List[AgentGenerationInfo] = [] | |
| agent_ids = list(agent_order) if agent_order is not None else sorted(bundle.all_agents.keys()) | |
| for aid in agent_ids: | |
| agent = bundle.all_agents.get(aid, {}) | |
| m = (agent.get("M") or {}) if isinstance(agent, dict) else {} | |
| t = (agent.get("T") or {}) if isinstance(agent, dict) else {} | |
| llm_token = _normalize_token(m.get("id") or m.get("name") or "") | |
| tool_tokens = [ | |
| tok | |
| for tok in (_normalize_token(x) for x in (t.get("tools") or [])) | |
| if tok | |
| ] | |
| agent_infos.append( | |
| AgentGenerationInfo( | |
| aid=aid, | |
| llm_token=llm_token or UNK_LLM_TOKEN, | |
| tool_tokens=tool_tokens or [UNK_TOOL_TOKEN], | |
| text=agent_text_cache.get(aid, ""), | |
| ) | |
| ) | |
| vec = TfidfVectorizer(max_features=cfg.tfidf_max_features, lowercase=True) | |
| agent_matrix = vec.fit_transform([a.text for a in agent_infos]) | |
| agent_matrix = normalize(agent_matrix, norm="l2", axis=1) | |
| return cls( | |
| vectorizer=vec, | |
| agent_matrix=agent_matrix, | |
| agents=agent_infos, | |
| config=cfg, | |
| ) | |
| def from_data_root( | |
| cls, data_root: str, *, config: GenerationConfig | None = None, parts: Iterable[str] | None = None | |
| ) -> "GenerativeStructuredRecommender": | |
| """ | |
| Build a generator from the benchmark data directory. | |
| This initializes TF-IDF weights over agent descriptions and tracks the | |
| LLM/tool metadata needed to build the structured outputs. | |
| """ | |
| bundle = collect_data(data_root, parts=list(parts) if parts is not None else None) | |
| tools = load_tools(data_root) | |
| return cls.from_bundle(bundle, tools=tools, config=config) | |
| def _score_query(self, query: str) -> Tuple[np.ndarray, np.ndarray]: | |
| q_vec = self.vectorizer.transform([query]) | |
| q_vec = normalize(q_vec, norm="l2", axis=1) | |
| scores = (q_vec @ self.agent_matrix.T).toarray()[0] | |
| order = np.argsort(-scores) | |
| return scores, order | |
| def format_tokens(self, info: AgentGenerationInfo) -> str: | |
| tools = info.tool_tokens[: max(1, self.config.max_tools)] | |
| if not tools: | |
| tools = [UNK_TOOL_TOKEN] | |
| return " ".join( | |
| [info.llm_token, self.config.tool_sep_token, *tools, self.config.end_token] | |
| ) | |
| def generate( | |
| self, query: str, *, top_k: int = 1, with_metadata: bool = False | |
| ) -> List[Dict[str, object] | str]: | |
| """Generate structured sequences for a single query.""" | |
| scores, order = self._score_query(query) | |
| n = min(max(1, top_k), len(order)) | |
| outputs: List[Dict[str, object] | str] = [] | |
| for idx in order[:n]: | |
| info = self.agents[int(idx)] | |
| seq = self.format_tokens(info) | |
| if not with_metadata: | |
| outputs.append(seq) | |
| continue | |
| outputs.append( | |
| { | |
| "sequence": seq, | |
| "agent_id": info.aid, | |
| "llm_token": info.llm_token, | |
| "tool_tokens": info.tool_tokens, | |
| "score": float(scores[int(idx)]), | |
| } | |
| ) | |
| return outputs | |
| def build_supervised_pairs( | |
| self, | |
| *, | |
| rankings: Dict[str, List[str]], | |
| questions: Dict[str, dict], | |
| max_examples: int | None = None, | |
| ) -> List[Dict[str, str]]: | |
| """ | |
| Convert the benchmark's query->ranking annotations into supervised | |
| training pairs for finetuning a seq2seq or instruction model. | |
| """ | |
| aid_to_info = {a.aid: a for a in self.agents} | |
| pairs: List[Dict[str, str]] = [] | |
| for qid, ranked in rankings.items(): | |
| if not ranked: | |
| continue | |
| top_agent = ranked[0] | |
| info = aid_to_info.get(top_agent) | |
| if info is None: | |
| continue | |
| qtext = (questions.get(qid, {}) or {}).get("input", "") | |
| if not qtext: | |
| continue | |
| pairs.append( | |
| { | |
| "qid": qid, | |
| "query": qtext, | |
| "target": self.format_tokens(info), | |
| "agent_id": top_agent, | |
| } | |
| ) | |
| if max_examples is not None and len(pairs) >= max_examples: | |
| break | |
| return pairs | |
| def build_training_pairs_from_data_root( | |
| data_root: str, | |
| *, | |
| config: GenerationConfig | None = None, | |
| max_examples: int | None = None, | |
| parts: Iterable[str] | None = None, | |
| ) -> List[Dict[str, str]]: | |
| """ | |
| Helper wrapper to build supervised pairs directly from the data root. | |
| This is handy when exporting the pairs to JSONL for finetuning a text | |
| generator: the ``target`` string already encodes the | |
| ``LLM_TOKEN <TOOL_SEP> TOOL_TOKEN ... <SPECIAL_END>`` template. | |
| """ | |
| bundle = collect_data(data_root, parts=list(parts) if parts is not None else None) | |
| tools = load_tools(data_root) | |
| gen = GenerativeStructuredRecommender.from_bundle(bundle, tools=tools, config=config) | |
| return gen.build_supervised_pairs( | |
| rankings=bundle.all_rankings, | |
| questions=bundle.all_questions, | |
| max_examples=max_examples, | |
| ) | |
Xet Storage Details
- Size:
- 8.04 kB
- Xet hash:
- 89c5ccd2fd55c2b4aa5b1aea41e25580a1300a1e70e38e616c2658a540ffac31
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.