This example demonstrates usage of a Conversation in an async context
(e.g.: From a fastapi server). The conversation is run in a background
thread and a callback with results is executed in the main runloop
examples/01_standalone_sdk/11_async.py
"""This example demonstrates usage of a Conversation in an async context(e.g.: From a fastapi server). The conversation is run in a backgroundthread and a callback with results is executed in the main runloop"""import asyncioimport osfrom pydantic import SecretStrfrom openhands.sdk import ( LLM, Agent, Conversation, Event, LLMConvertibleEvent, get_logger,)from openhands.sdk.conversation.types import ConversationCallbackTypefrom openhands.sdk.tool import Toolfrom openhands.sdk.utils.async_utils import AsyncCallbackWrapperfrom openhands.tools.file_editor import FileEditorToolfrom openhands.tools.task_tracker import TaskTrackerToolfrom openhands.tools.terminal import TerminalToollogger = get_logger(__name__)# Configure LLMapi_key = os.getenv("LLM_API_KEY")assert api_key is not None, "LLM_API_KEY environment variable is not set."model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929")base_url = os.getenv("LLM_BASE_URL")llm = LLM( usage_id="agent", model=model, base_url=base_url, api_key=SecretStr(api_key),)# Toolscwd = os.getcwd()tools = [ Tool( name=TerminalTool.name, ), Tool(name=FileEditorTool.name), Tool(name=TaskTrackerTool.name),]# Agentagent = Agent(llm=llm, tools=tools)llm_messages = [] # collect raw LLM messages# Callback coroutineasync def callback_coro(event: Event): if isinstance(event, LLMConvertibleEvent): llm_messages.append(event.to_llm_message())# Synchronous run conversationdef run_conversation(callback: ConversationCallbackType): conversation = Conversation(agent=agent, callbacks=[callback]) conversation.send_message( "Hello! Can you create a new Python file named hello.py that prints " "'Hello, World!'? Use task tracker to plan your steps." ) conversation.run() conversation.send_message("Great! Now delete that file.") conversation.run()async def main(): loop = asyncio.get_running_loop() # Create the callback callback = AsyncCallbackWrapper(callback_coro, loop) # Run the conversation in a background thread and wait for it to finish... await loop.run_in_executor(None, run_conversation, callback) print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): print(f"Message {i}: {str(message)[:200]}") # Report cost cost = llm.metrics.accumulated_cost print(f"EXAMPLE_COST: {cost}")if __name__ == "__main__": asyncio.run(main())
You can run the example code as-is.
The model name should follow the LiteLLM convention: provider/model_name (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o).
The LLM_API_KEY should be the API key for your chosen provider.
ChatGPT Plus/Pro subscribers: You can use LLM.subscription_login() to authenticate with your ChatGPT account and access Codex models without consuming API credits. See the LLM Subscriptions guide for details.