chore: import upstream snapshot with attribution
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
@@ -0,0 +1,274 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""This file includes some basic tests for the integration of Tinker's sampling client
|
||||
with LiteLLM and Agent-lightning.
|
||||
|
||||
It should be included in CI in future if we decided to maintain this example.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any, Awaitable, Callable, Dict, cast
|
||||
|
||||
import openai
|
||||
import tinker
|
||||
from agl_tinker.llm import TinkerLLM
|
||||
from agl_tinker.rollout import reconstruct_transitions
|
||||
from rich.console import Console
|
||||
from tinker_cookbook.renderers import Qwen3InstructRenderer
|
||||
from transformers import AutoTokenizer, PreTrainedTokenizer
|
||||
|
||||
from agentlightning import (
|
||||
AgentOpsTracer,
|
||||
InMemoryLightningStore,
|
||||
LLMProxy,
|
||||
LlmProxyTraceToTriplet,
|
||||
TracerTraceToTriplet,
|
||||
emit_reward,
|
||||
setup_logging,
|
||||
)
|
||||
from agentlightning.store import LightningStoreThreaded
|
||||
|
||||
setup_logging(apply_to=["agl_tinker"])
|
||||
|
||||
_tool_call_system_prompt = """
|
||||
You must call the provided tool once before responding to the user.
|
||||
|
||||
You are provided with function signatures within <tools></tools> XML tags:
|
||||
<tools>
|
||||
{"name": "echo_text", "description": "Echo back any provided text.", "parameters": {"type": "object", "properties": {"text": {"type": "string", "description": "Text to repeat back."}}, "required": ["text"]}}
|
||||
</tools>
|
||||
|
||||
For each function call, return a json object with function name and args within <tool_call></tool_call> XML tags:
|
||||
<tool_call>
|
||||
{"name": <function-name>, "args": <args-json-object>}
|
||||
</tool_call>
|
||||
"""
|
||||
|
||||
|
||||
def _run_tool_call_roundtrip(client: openai.OpenAI, *, model_name: str) -> None:
|
||||
"""Force a tool call, parse the args, and feed back the tool result."""
|
||||
prompt_messages: list[Dict[str, str]] = [
|
||||
# FIXME: Currently the tool call definition needs to be hard-coded into the system prompt.
|
||||
{"role": "system", "content": _tool_call_system_prompt},
|
||||
{"role": "user", "content": "Use the tool to echo 'Agent Lightning loves tool calls'."},
|
||||
]
|
||||
response = client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=cast(Any, prompt_messages),
|
||||
max_tokens=256,
|
||||
temperature=0.0,
|
||||
# tools=cast(Any, tools),
|
||||
# tool_choice="auto",
|
||||
)
|
||||
print("First response:", response)
|
||||
tool_calls = response.choices[0].message.tool_calls or []
|
||||
if not tool_calls:
|
||||
raise AssertionError("Model did not emit a tool call when forced to do so.")
|
||||
tool_call = tool_calls[0]
|
||||
if tool_call.type != "function" or tool_call.function is None: # pyright: ignore[reportUnnecessaryComparison]
|
||||
raise AssertionError("Unexpected tool call payload from model.")
|
||||
arguments = tool_call.function.arguments or "{}"
|
||||
tool_args = json.loads(arguments)
|
||||
tool_result = tool_args.get("text", "")
|
||||
followup_messages: list[Dict[str, Any]] = [
|
||||
*prompt_messages,
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "", # FIXME: Content must be here to make validation happy.
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": tool_call.id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool_call.function.name,
|
||||
"arguments": tool_call.function.arguments,
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"name": tool_call.function.name,
|
||||
"content": f"Echoed text: {tool_result}",
|
||||
},
|
||||
]
|
||||
followup_response = client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=cast(Any, followup_messages),
|
||||
max_tokens=64,
|
||||
temperature=0.5,
|
||||
)
|
||||
print("Followup response:", followup_response)
|
||||
|
||||
|
||||
def _run_text_completion(client: openai.OpenAI, *, model_name: str) -> None:
|
||||
"""Simple text-only completion to contrast the tool-call scenario."""
|
||||
response = client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=[{"role": "user", "content": "Hello world!"}],
|
||||
max_tokens=20,
|
||||
temperature=0.5,
|
||||
top_p=0.9,
|
||||
seed=11,
|
||||
)
|
||||
print(response)
|
||||
|
||||
|
||||
async def _run_tracer_test(*, use_tool_call: bool) -> None:
|
||||
console = Console()
|
||||
model_name = "Qwen/Qwen3-30B-A3B-Instruct-2507"
|
||||
|
||||
tokenizer = cast(PreTrainedTokenizer, AutoTokenizer.from_pretrained(model_name)) # type: ignore
|
||||
renderer = Qwen3InstructRenderer(tokenizer) # type: ignore
|
||||
service_client = tinker.ServiceClient()
|
||||
sampling_client = service_client.create_sampling_client(base_model=model_name)
|
||||
tinker_llm = TinkerLLM(
|
||||
model_name=model_name, renderer=renderer, tokenizer=tokenizer, sampling_client=sampling_client, max_tokens=20
|
||||
)
|
||||
tinker_llm.rewrite_litellm_custom_providers()
|
||||
|
||||
store = LightningStoreThreaded(InMemoryLightningStore())
|
||||
rollout = await store.start_rollout("dummy", "train")
|
||||
llm_proxy = LLMProxy(
|
||||
port=4000,
|
||||
store=store,
|
||||
model_list=tinker_llm.as_model_list(),
|
||||
num_retries=0,
|
||||
launch_mode="thread",
|
||||
)
|
||||
|
||||
scenario = "tool-call" if use_tool_call else "text-only"
|
||||
console.print(f"Running tracer test scenario: {scenario}")
|
||||
|
||||
try:
|
||||
tracer = AgentOpsTracer()
|
||||
tracer.init()
|
||||
tracer.init_worker(worker_id=0, store=store)
|
||||
|
||||
# init tracer before llm_proxy to avoid tracer provider being not active.
|
||||
console.print("Starting LLM proxy...")
|
||||
await llm_proxy.start()
|
||||
console.print("LLM proxy started")
|
||||
|
||||
client = openai.OpenAI(base_url="http://localhost:4000/v1", api_key="dummy")
|
||||
|
||||
async with tracer.trace_context(
|
||||
name=f"test_llm_{scenario}", rollout_id=rollout.rollout_id, attempt_id=rollout.attempt.attempt_id
|
||||
):
|
||||
if use_tool_call:
|
||||
_run_tool_call_roundtrip(client, model_name=model_name)
|
||||
else:
|
||||
_run_text_completion(client, model_name=model_name)
|
||||
emit_reward(8.0)
|
||||
|
||||
print(f"Found {len(tracer.get_last_trace())} spans in the tracer")
|
||||
|
||||
tracer.teardown_worker(0)
|
||||
tracer.teardown()
|
||||
|
||||
for store_span in await store.query_spans(rollout.rollout_id):
|
||||
print(store_span)
|
||||
|
||||
spans = await store.query_spans(rollout.rollout_id)
|
||||
console.print(f"Found {len(spans)} spans")
|
||||
adapter = TracerTraceToTriplet()
|
||||
trajectory = reconstruct_transitions(spans, adapter, rollout.rollout_id)
|
||||
print(trajectory)
|
||||
assert len(trajectory.transitions) > 0
|
||||
assert len(trajectory.transitions[0].ac.tokens) > 0
|
||||
finally:
|
||||
console.print("Stopping LLM proxy...")
|
||||
await llm_proxy.stop()
|
||||
console.print("LLM proxy stopped")
|
||||
|
||||
|
||||
async def test_tracer_text_only():
|
||||
await _run_tracer_test(use_tool_call=False)
|
||||
|
||||
|
||||
async def test_tracer_tool_call():
|
||||
await _run_tracer_test(use_tool_call=True)
|
||||
|
||||
|
||||
async def test_tracer():
|
||||
await test_tracer_tool_call()
|
||||
|
||||
|
||||
async def test_llm_proxy():
|
||||
# FIXME: The llm proxy adapter needs some fixes to make this test work
|
||||
console = Console()
|
||||
model_name = "Qwen/Qwen3-30B-A3B-Instruct-2507"
|
||||
|
||||
tokenizer = cast(PreTrainedTokenizer, AutoTokenizer.from_pretrained(model_name)) # type: ignore
|
||||
renderer = Qwen3InstructRenderer(tokenizer) # type: ignore
|
||||
service_client = tinker.ServiceClient()
|
||||
sampling_client = service_client.create_sampling_client(base_model=model_name)
|
||||
tinker_llm = TinkerLLM(
|
||||
model_name=model_name, renderer=renderer, tokenizer=tokenizer, sampling_client=sampling_client, max_tokens=20
|
||||
)
|
||||
tinker_llm.rewrite_litellm_custom_providers()
|
||||
|
||||
store = LightningStoreThreaded(InMemoryLightningStore())
|
||||
rollout = await store.start_rollout("dummy", "train")
|
||||
llm_proxy = LLMProxy(
|
||||
port=4000,
|
||||
store=store,
|
||||
model_list=tinker_llm.as_model_list(),
|
||||
num_retries=0,
|
||||
launch_mode="thread",
|
||||
)
|
||||
|
||||
try:
|
||||
# init tracer before llm_proxy to avoid tracer provider being not active.
|
||||
console.print("Starting LLM proxy...")
|
||||
await llm_proxy.start()
|
||||
console.print("LLM proxy started")
|
||||
|
||||
client = openai.OpenAI(
|
||||
base_url=f"http://localhost:4000/rollout/{rollout.rollout_id}/attempt/{rollout.attempt.attempt_id}",
|
||||
api_key="dummy",
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=[{"role": "user", "content": "Hello world!"}],
|
||||
max_tokens=10,
|
||||
temperature=0.5,
|
||||
top_p=0.9,
|
||||
seed=43,
|
||||
)
|
||||
print(response)
|
||||
|
||||
for store_span in await store.query_spans(rollout.rollout_id):
|
||||
print(store_span)
|
||||
|
||||
spans = await store.query_spans(rollout.rollout_id)
|
||||
console.print(f"Found {len(spans)} spans")
|
||||
adapter = LlmProxyTraceToTriplet()
|
||||
trajectory = reconstruct_transitions(spans, adapter, rollout.rollout_id)
|
||||
print(trajectory)
|
||||
finally:
|
||||
console.print("Stopping LLM proxy...")
|
||||
await llm_proxy.stop()
|
||||
console.print("LLM proxy stopped")
|
||||
|
||||
|
||||
CLI_VARIANTS: Dict[str, Callable[[], Awaitable[None]]] = {
|
||||
"tracer-tool": test_tracer_tool_call,
|
||||
"tracer-text": test_tracer_text_only,
|
||||
"llm-proxy": test_llm_proxy,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Manually run the async Tinker LLM integration tests.")
|
||||
parser.add_argument(
|
||||
"variant",
|
||||
choices=sorted(CLI_VARIANTS.keys()),
|
||||
help="Which async test to run.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
asyncio.run(CLI_VARIANTS[args.variant]()) # type: ignore
|
||||
Reference in New Issue
Block a user