Files
wehub-resource-sync 85742ab165
CPU Test / Lint - next (push) Waiting to run
Dashboard / Chromatic (push) Waiting to run
CPU Test / Lint - fast (push) Waiting to run
CPU Test / Build documentation (push) Waiting to run
CPU Test / Test (Store, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Weave, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Others, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Store, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Utilities, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Weave, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Others, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Store, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Utilities, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Weave, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Others, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Store, latest, Python 3.13) (push) Waiting to run
CPU Test / Lint - slow (push) Waiting to run
CPU Test / Lint - JavaScript (push) Waiting to run
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Others, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Utilities, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Weave, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (JavaScript) (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:17 +08:00

164 lines
6.8 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
"""Custom adapter module for converting LLM proxy traces to augmented trajectories.
This module provides an augmented LlmProxyTraceToTriplet adapter that converts
LLM proxy spans into augmented trajectories for analysis and evaluation.
It extends the base LlmProxyTraceToTriplet to include additional metadata like chat messages,
log probabilities, and sequence IDs.
"""
import logging
from typing import Any, Dict, List, Optional, Tuple, cast
from agentlightning.adapter.triplet import LlmProxyTraceToTriplet
from agentlightning.types import Span, Triplet
logger = logging.getLogger(__name__)
class ExtendedLlmProxyTraceToTriplet(LlmProxyTraceToTriplet):
"""Convert LLM Proxy spans into trajectories with logprobs and customized metadata.
Augmented fields include:
- chat messages history from [`llm.hosted_vllm.messages`], saved to `Triplet.metadata['messages']`
- logprobs from [`llm.hosted_vllm.choices`], saved to `Triplet.response['logprobs']`
- sequence_id from [`Span.sequence_id`] to locate the order of the span (conversation turn), saved to `Triplet.metadata['sequence_id']`
"""
def _extract_tokens_from_raw(self, attrs: Dict[str, Any]) -> Tuple[List[int], List[int], List[float]]: # type: ignore
"""Extract token ids from raw_gen_ai_request attributes.
- llm.hosted_vllm.prompt_token_ids: string -> List[int]
- llm.hosted_vllm.choices: string -> [{'token_ids': [...]}] -> take first
"""
prompt_ids: List[int] = []
resp_ids: List[int] = []
logprobs: List[float] = []
# prompt
p = attrs.get("llm.hosted_vllm.prompt_token_ids")
p = self._literal_eval_maybe(p)
if isinstance(p, list) and all(isinstance(x, int) for x in p): # type: ignore
prompt_ids = cast(List[int], p)
choices = attrs.get("llm.hosted_vllm.choices")
choices = self._literal_eval_maybe(choices)
if isinstance(choices, list) and choices:
cand = cast(Any, choices[0])
if isinstance(cand, dict):
tids = cast(Dict[str, Any], cand).get("token_ids")
if isinstance(tids, list) and all(isinstance(x, int) for x in tids): # type: ignore
resp_ids = cast(List[int], tids)
if "logprobs" in cand:
logprobs_dict = cast(Dict[str, Any], cand).get("logprobs")
if isinstance(logprobs_dict, dict) and "content" in logprobs_dict:
content = cast(List[Dict[str, Any]], logprobs_dict["content"])
logprobs = [float(item["logprob"]) for item in content if "logprob" in item]
return prompt_ids, resp_ids, logprobs
def adapt(self, source: List[Span], /) -> List[Triplet]: # type: ignore
"""Convert LLM Proxy spans into [`Triplet`][agentlightning.Triplet] trajectories.
Args:
source: Spans emitted by the LLM Proxy containing prompt, response, and reward data.
Returns:
Ordered trajectory transitions matched purely by `sequence_id`.
"""
# 1) Sort deterministically by (sequence_id, start_time).
spans = sorted(
source,
key=lambda s: (s.sequence_id, s.start_time),
)
# 2) Collect LLM calls
llm_items: List[Dict[str, Any]] = []
seen_request_ids: set[str] = set()
for s in spans:
attrs = s.attributes or {}
prompt_ids: List[int] = []
resp_ids: List[int] = []
logprobs: List[float] = []
if s.name == "raw_gen_ai_request":
prompt_ids, resp_ids, logprobs = self._extract_tokens_from_raw(attrs)
if len(prompt_ids) == 0 or len(resp_ids) == 0:
logger.warning(
f"Span {s.span_id} is missing prompt (len={len(prompt_ids)}) or response (len={len(resp_ids)}) token ids. Ignoring this span."
)
continue
elif len(logprobs) == 0:
logger.warning(f"Span {s.span_id} is missing logprobs. Ignoring logprobs for this span.")
continue
elif len(resp_ids) != len(logprobs):
logger.warning(
f"Span {s.span_id} has mismatched response ids and logprobs lengths: "
f"{len(resp_ids)} vs {len(logprobs)}. Ignoring this span."
)
continue
if prompt_ids and resp_ids and logprobs:
rid = self._request_id_from_attrs(attrs)
if rid:
# Duplicated request ID. This request is already handled.
if rid in seen_request_ids:
continue
seen_request_ids.add(rid)
llm_items.append(
dict(
span=s,
seq=s.sequence_id,
response_ids=resp_ids,
prompt_ids=prompt_ids,
request_id=rid,
logprobs=logprobs,
)
)
# Order LLM items by sequence only.
llm_items.sort(key=lambda x: x["seq"])
# Collect rewards by sequence only.
rewards: List[Tuple[int, Optional[float]]] = []
for s in spans:
val = self._maybe_reward_value(s)
if val is not None:
rewards.append((s.sequence_id, val))
# First-occurrence matching by sequence_id only:
# For reward at sequence R, assign to the most recent unmatched LLM with seq < R.
assigned: Dict[str, Optional[float]] = {}
for r_seq, r_val in sorted(rewards, key=lambda x: x[0]):
for item in reversed(llm_items):
sid = item["span"].span_id
if sid in assigned:
continue
if item["seq"] < r_seq:
assigned[sid] = r_val
break
# Build triplets in LLM sequence order.
triplets: List[Triplet] = []
for item in llm_items:
s = item["span"]
triplets.append(
Triplet(
prompt={"token_ids": item["prompt_ids"]},
response={"token_ids": item["response_ids"], "logprobs": item["logprobs"]},
reward=assigned.get(s.span_id, None),
metadata=dict(
# This is called response_id to align with the other adapters.
response_id=item["request_id"],
sequence_id=item["seq"],
messages=self._literal_eval_maybe(s.attributes.get("llm.hosted_vllm.messages")),
),
)
)
return triplets