# Copyright (c) Microsoft. All rights reserved. """This example code illustrates several approaches to debugging an agent in agent-lightning.""" import argparse import asyncio from typing import cast from apo_custom_algorithm import apo_rollout from agentlightning import Trainer, setup_logging from agentlightning.litagent import LitAgent from agentlightning.runner import LitAgentRunner from agentlightning.store import InMemoryLightningStore from agentlightning.tracer import AgentOpsTracer, OtelTracer from agentlightning.types import Dataset, Hook, PromptTemplate, Rollout async def debug_with_runner(): """This approach requires no dataset, no trainer, and no algorithm. It only needs a runner and you can run get full control of the runner. However, you need to manually create other components like tracer and store, because trainer does not exist and it will not create for you. """ # You need to manually create a tracer here because the runner will not create for you currently. # Tracer is used to record the events (spans) in background during the agent's execution. # If you don't need any tracing functionality yet, you can use a dummy OtelTracer. tracer = OtelTracer() runner = LitAgentRunner[str](tracer) # You also need a store here to store the data collected. store = InMemoryLightningStore() # This is what needs to be tuned (i.e., prompt template) resource = PromptTemplate(template="You are a helpful assistant. {any_question}", engine="f-string") # The agent here must be the same agent that will be used in the real run. with runner.run_context(agent=apo_rollout, store=store): await runner.step( "Explain why the sky appears blue using principles of light scattering in 100 words.", resources={"main_prompt": resource}, ) async def debug_with_hooks(): """This approach also uses Runner, but allows you to hook into the runner's lifecycle events. We use an AgentOpsTracer here so that the tracing is non-empty. """ tracer = AgentOpsTracer() # The rest part are the same as debug_with_runner runner = LitAgentRunner[str](tracer) store = InMemoryLightningStore() resource = PromptTemplate(template="You are a helpful assistant. {any_question}", engine="f-string") class DebugHook(Hook): async def on_trace_end( # type: ignore self, *, agent: LitAgent[str], runner: LitAgentRunner[str], tracer: AgentOpsTracer, rollout: Rollout ) -> None: """We use `tracer.get_last_trace()` to get all raw OpenTelemetry spans from the Rollout. The last reward span is not available yet. """ trace = tracer.get_last_trace() print("Trace spans collected during the rollout:") for span in trace: print(f"- {span.name} (status: {span.status}):\n {span.attributes}") with runner.run_context( agent=apo_rollout, store=store, # Send the hooks into `run_context` hooks=[DebugHook()], ): await runner.step( "Explain why the sky appears blue using principles of light scattering in 100 words.", resources={"main_prompt": resource}, ) def debug_with_trainer(): """This approach integrates the trainer and is very similar to the real `fit()` loop. The trainer will create a mock algorithm which will communicates with the runner. Do this for end-to-end testing and debugging purposes. """ # To debug with trainer, we need a dataset dataset = cast( Dataset[str], [ "Explain why the sky appears blue using principles of light scattering in 100 words.", "What's the capital of France?", ], ) # We also need a resource that is to be tuned (i.e., prompt template) resource = PromptTemplate(template="You are a helpful assistant. {any_question}", engine="f-string") trainer = Trainer( n_workers=1, # This is very critical. It will be the only prompt template that will be passed to the agent. initial_resources={"main_prompt": resource}, ) trainer.dev(apo_rollout, dataset) if __name__ == "__main__": setup_logging() parser = argparse.ArgumentParser(description="Debug APO with runner or trainer approach.") parser.add_argument( "--mode", choices=["runner", "hook", "trainer"], default="runner", help="Choose which debugging approach to use: 'runner' (default), 'hook', or 'trainer'.", ) args = parser.parse_args() if args.mode == "runner": asyncio.run(debug_with_runner()) elif args.mode == "hook": asyncio.run(debug_with_hooks()) elif args.mode == "trainer": # Don't want two mode consecutively in one process, # unless you are sure the tracer won't conflict. debug_with_trainer()