Skip to content
48 changes: 48 additions & 0 deletions tests/v1/test_graph.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64

import numpy as np
import pytest

import verifiers.v1 as vf
from verifiers.v1 import graph
Expand Down Expand Up @@ -232,6 +233,53 @@ def second_turn(trace, prompt_ids):
]


def test_expanded_prompt_is_canonical_while_bridge_uses_logical_tokens():
trace = vf.Trace(
agent=vf.AgentInfo(config=vf.AgentConfig()),
task=vf.TraceTask(type="Task", data=vf.TaskData(idx=0, prompt="x")),
)
user = vf.UserMessage(content="image")
assistant = vf.AssistantMessage(content="looked")
graph.prepare_turn(trace, [user]).commit(
vf.Response(
id="a",
created=0,
model="t",
message=assistant,
finish_reason="stop",
tokens=TurnTokens(
prompt_ids=[1, 9, 9, 3],
renderer_prompt_ids=[1, 2, 3],
completion_ids=[4],
message_spans=[(0, 2)],
mm_token_type_id_map={9: 1},
),
)
)

assert trace.branches[0].token_ids == [1, 9, 9, 3, 4]
assert trace.branches[0].mm_token_type_ids == [0, 1, 1, 0, 0]
turn = graph.prepare_turn(trace, [user, assistant, vf.UserMessage(content="next")])
assert turn.previous_token_ids() == ([1, 2, 3], [4])

with pytest.raises(ValueError, match="exactly extend"):
turn.commit(
vf.Response(
id="b",
created=0,
model="t",
message=vf.AssistantMessage(content="bad"),
finish_reason="stop",
tokens=TurnTokens(
prompt_ids=[1, 9, 8, 3, 4, 5],
renderer_prompt_ids=[1, 2, 3, 4, 5],
completion_ids=[6],
message_spans=[None, None, (4, 5)],
),
)
)


def test_prompt_supplied_assistant_messages_are_not_sampled_turns():
task = vf.TaskData(idx=0, prompt="few-shot")
trace = vf.Trace(
Expand Down
42 changes: 22 additions & 20 deletions verifiers/v1/clients/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,11 @@ def response_from_generate(
# million-token contexts synchronously on the event loop.
tokens=TurnTokens.model_construct(
prompt_ids=prompt_ids,
renderer_prompt_ids=result.get("renderer_prompt_ids"),
completion_ids=completion_ids,
completion_logprobs=result.get("completion_logprobs") or [],
message_spans=message_spans,
is_content=attribution.is_content if attribution is not None else None,
multi_modal_data=result.get("multi_modal_data"),
mm_token_type_id_map=mm_token_type_id_map,
routed_experts=result.get("routed_experts"),
kept_tokens=KeptTokens(**kept)
Expand Down Expand Up @@ -354,11 +354,9 @@ async def get_response(
from renderers.client import generate

wire_tools = [tool_to_wire(t) for t in tools] if tools else None
wire_messages = (
[message_to_wire(m) for m in turn.tail] if turn is not None else []
)
wire_messages = [message_to_wire(m) for m in prompt]
wire_tail = [message_to_wire(m) for m in turn.tail] if turn is not None else []
prompt_ids: list[int] | None = None
multi_modal_data = None
prompt_attribution: RenderedTokens | None = None
model = body["model"]
sampling_params = sampling.wire_args()
Expand All @@ -376,13 +374,17 @@ async def get_response(
mm_token_type_id_map = (
renderer.mm_token_type_id_map if is_multimodal(renderer) else None
)
# Only build the (O(context)) previous-turn token ids once the cheap guards pass — a
# multimodal prompt or a tail that isn't a clean `[tool*, user?]` extension can't bridge.
can_bridge = (
turn is not None
and not _has_multimodal_content(prompt)
and _is_valid_incremental_tail(wire_messages)
)
has_images = _has_multimodal_content(prompt)
process_multimodal = not has_images
if has_images and not getattr(
renderer, "supports_process_multimodal", False
):
raise NotImplementedError(
f"{type(renderer).__name__} does not support process_multimodal=False"
)
render_kwargs = {} if process_multimodal else {"process_multimodal": False}
# Only build the O(context) previous token stream for a bridgeable tail.
can_bridge = turn is not None and _is_valid_incremental_tail(wire_tail)
previous_ids = turn.previous_token_ids() if can_bridge else None
if previous_ids is not None:
previous_prompt_ids, previous_completion_ids = previous_ids
Expand All @@ -391,33 +393,33 @@ def bridge():
return renderer.bridge_to_next_turn(
previous_prompt_ids,
previous_completion_ids,
wire_messages,
wire_tail,
tools=wire_tools,
**render_kwargs,
)

bridged = await slot.run(bridge)
if bridged is not None:
prompt_ids = bridged.token_ids
multi_modal_data = bridged.multi_modal_data
prompt_attribution = bridged
bridged_turn = turn
sampling_params["routed_experts_prompt_start"] = max(
len(previous_prompt_ids) + len(previous_completion_ids) - 1,
0,
turn.path_len - 1, 0
)

# Render here (encode-side, so through the slot) rather than inside `generate`:
# handed prebuilt prompt_ids, generate's own renderer touches are decode-side
# and stop-id reads, safe on a bare renderer without lock or thread hop.
if prompt_ids is None:
wire_messages = [message_to_wire(m) for m in prompt]
rendered = await slot.run(
lambda: renderer.render(
wire_messages, tools=wire_tools, add_generation_prompt=True
wire_messages,
tools=wire_tools,
add_generation_prompt=True,
**render_kwargs,
)
)
prompt_ids = rendered.token_ids
multi_modal_data = rendered.multi_modal_data
prompt_attribution = rendered

try:
Expand All @@ -427,10 +429,10 @@ def bridge():
messages=wire_messages,
model=model,
prompt_ids=prompt_ids,
multi_modal_data=multi_modal_data,
prompt_attribution=prompt_attribution,
tools=wire_tools,
sampling_params=sampling_params,
process_multimodal=process_multimodal,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High clients/train.py:435

Every TrainClient.get_response call raises TypeError before inference because the pinned renderers==0.1.10 generate() does not accept process_multimodal, including text-only requests. Remove this unsupported keyword (or upgrade the renderer dependency) so requests reach the inference endpoint.

-                    process_multimodal=process_multimodal,
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/clients/train.py around line 435:

Every `TrainClient.get_response` call raises `TypeError` before inference because the pinned `renderers==0.1.10` `generate()` does not accept `process_multimodal`, including text-only requests. Remove this unsupported keyword (or upgrade the renderer dependency) so requests reach the inference endpoint.

extra_headers={SESSION_ID_HEADER: session_id}
if session_id
else None,
Expand Down
Loading
Loading