Optimization runs without a config

This topic explains how to define judges, model choices, and evaluation parameters directly in code without a LaunchDarkly configuration object. This lets you perform optimization runs without an optimization config.

Code example

Here is a complete example using optimize_from_options with the Claude Agent SDK:

Python
1# LD imports
2from ldai_optimization import (
3 OptimizationClient,
4 OptimizationOptions,
5 OptimizationJudge,
6 OptimizationResponse,
7 LLMCallConfig,
8 LLMCallContext,
9)
10from ldai import LDAIClient
11from ldai.tracker import TokenUsage
12from ldclient import Context
13
14# anthropic imports
15from claude_agent_sdk import query, ClaudeAgentOptions
16from claude_agent_sdk.types import ResultMessage
17
18acceptance_statement_prompt = """\
19The orchestrator should appropriately fetch the user preferences and \
20route to the correct sub-agent, \
21carrying through any relevant information from the users' query.
22The orchestrator should not provide any answers itself, \
23just pass to the correct sub-agent.
24Inability to fetch user preferences or mentions of missing data should \
25be automatic failures.
26If preferences are not included, that should be an automatic failure.
27If the orchestrator does not mention the sub-agent it will hand off to, \
28that is an automatic failure."""
29
30default_fallback_model = "claude-opus-4-5-20251101"
31
32async def handle_agent_call(
33 key: str,
34 config: LLMCallConfig,
35 context: LLMCallContext,
36 is_evaluation: bool = False,
37) -> OptimizationResponse:
38 model = config.model.name if config.model else default_fallback_model
39 final_message = None
40 async for message in query(
41 prompt=context.user_input or "",
42 options=ClaudeAgentOptions(
43 system_prompt=config.instructions or "",
44 model=model,
45 ),
46 ):
47 final_message = message
48
49 if not isinstance(final_message, ResultMessage):
50 raise ValueError(f"Unexpected final message type: {type(final_message)}")
51
52 u = final_message.usage or {}
53 input_tokens = u.get("input_tokens", 0)
54 output_tokens = u.get("output_tokens", 0)
55
56 return OptimizationResponse(
57 output=final_message.result or "",
58 usage=TokenUsage(
59 total=input_tokens + output_tokens,
60 input=input_tokens,
61 output=output_tokens,
62 ),
63 )
64
65options = OptimizationOptions(
66 judges={
67 "acceptance": OptimizationJudge(
68 acceptance_statement=acceptance_statement_prompt,
69 threshold=0.95,
70 ),
71 "accuracy": OptimizationJudge(
72 judge_key="my-accuracy-judge",
73 threshold=0.8,
74 ),
75 },
76 context_choices=[
77 Context.builder("user-123").set("user_id", "user-123").build(),
78 ],
79 max_attempts=25,
80 model_choices=["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5"],
81 judge_model="claude-haiku-4-5",
82 variable_choices=[
83 {
84 "user_id": "user-123",
85 "trip_purpose": "business",
86 },
87 {
88 "user_id": "user-125",
89 "trip_purpose": "personal",
90 },
91 ],
92 user_input_options=[
93 "I'm going to tokyo next week, where should I stay near Shinjuku?",
94 "where to eat in anchorage",
95 "airbnbs near tahoe",
96 "what are some food options in sf near the airport"
97 ],
98 handle_agent_call=handle_agent_call,
99 handle_judge_call=handle_agent_call,
100)
101
102client = OptimizationClient(ld_ai_client)
103result = await client.optimize_from_options("travel-agent-orchestrator", options)

Code explanation

Section 1: Handlers

Section 1 sets up the agent call for our provider to handle the LLM invocations. These methods are intended to be provider-agnostic, so you can use your own models as long as your orchestrator or framework can reach them.

There are two different handlers. They are:

  • handle_agent_call executes the actual agent and receives a response. This code path is also used to generate new variations when the system requires it. To allow this, pass the calls through as generically as possible. Evaluators may need access to your tools for evaluation, so make sure it’s configured for both execution and new variation generation.
  • handle_judge_call (optional) executes the evaluation calls. It falls back to handle_agent_call if not provided. Specify this as a discrete handler if you want to capture response information, log metrics, or otherwise run judge calls differently from agent calls.

Your evaluator may need access to the same tools as your model. If you separate evaluator and model calls, specify tools for both.

Section 2: Optimization parameters

Section 2 sets up the optimization parameters. Here’s how:

  • judges defines the scoring mechanisms used to find prompt acceptance. Judges can be acceptance statements or judge configs. To learn more, read Performing optimization runs.
  • context_choices (optional) defines the list of LaunchDarkly contexts that can be automatically chosen from when running a completion.
  • max_attempts sets the maximum number of iterations allowed before failing. Use this to prevent over-spending on complex optimizations that may not reach a useful outcome. This does not correlate to the exact number of LLM calls made. Each optimization attempt incurs multiple LLM calls.
  • model_choices defines the models that the optimizer is allowed to choose between throughout the optimization process.
  • judge_model defines the model used for judging. This remains consistent across executions unless you intervene.
  • variable_choices defines a list of variable choice sets that are randomly chosen when executing the agent. Larger numbers of variable_choices and user_input_options leads to more possible permutations and lessens the chance of instruction overfitting.
  • user_input_options defines a list of user inputs that are randomly chosen when executing the agent.
  • handle_agent_call executes the agent calls.
  • handle_judge_call (optional) executes the evaluation (judge) calls. It falls back to handle_agent_call if not provided.

Output

In (3), we’re initializing the client and running the actual optimization. If you have info logging turned on, you can read the process as it runs in your logs. Here’s an example of the output with info logging turned on:

Bash
$INFO:ldai_optimization.client:[Iteration 1] -> Starting (attempt 1/10, model=claude-haiku-4-5)
$INFO:ldai_optimization.client:[Iteration 1] -> Calling agent (model=claude-haiku-4-5)...
$INFO:ldai_optimization.client:[Iteration 1] -> Executing evaluation...
$INFO:ldai_optimization.client:[Iteration 1] -> Running judge 1/2 'acceptance-statement-0' (acceptance)...
$INFO:ldai_optimization.client:[Iteration 1] -> Running judge 2/2 'ld-ai-judge-accuracy-1761841832389' (config)...
$INFO:ldai_optimization.client:[Iteration 1] -> One or more judges failed (attempt 1/10) — generating new variation
$INFO:ldai_optimization.client:[Iteration 1] -> Generating new variation...
$INFO:ldai_optimization.client:[Iteration 1] -> Model updated from 'claude-haiku-4-5' to 'claude-sonnet-4-5'
$INFO:ldai_optimization.client:[Iteration 2] -> Starting (attempt 2/10, model=claude-sonnet-4-5)
$INFO:ldai_optimization.client:[Iteration 2] -> Calling agent (model=claude-sonnet-4-5)...
$INFO:ldai_optimization.client:[Iteration 2] -> Executing evaluation...
$INFO:ldai_optimization.client:[Iteration 2] -> Running judge 1/2 'acceptance-statement-0' (acceptance)...
$INFO:ldai_optimization.client:[Iteration 2] -> Running judge 2/2 'ld-ai-judge-accuracy-1761841832389' (config)...
$INFO:ldai_optimization.client:[Iteration 2] -> One or more judges failed (attempt 2/10) — generating new variation
$INFO:ldai_optimization.client:[Iteration 2] -> Generating new variation...

This call returns an OptimizationContext representing the final output of the optimization. You can access OptimizationContext.history to get all of the historical OptimizationContexts generated during the run.

Here is an abbreviated example output:

JSON
1{
2 "scores": {
3 "acceptance-statement-0": {
4 "score": 1.0,
5 "rationale": "The response cleanly routes to the lodging-agent...",
6 "duration_ms": 9795.460292021744,
7 "usage": {
8 "total": 4050,
9 "input": 3088,
10 "output": 962
11 }
12 }
13 },
14 "completion_response": "Routing to **lodging-agent**.\n\nHandoff context:\n- User ID placeholder: **user-125**...",
15 "current_parameters": {
16 // ... model parameters used for this iteration ...
17 },
18 "current_variables": {
19 "trip_purpose": "business",
20 "user_id": "user-125"
21 },
22 "current_model": "claude-sonnet-4-5",
23 "user_input": "airbnbs near tahoe",
24 "history": [
25 // ... optimization context history ...
26 ],
27 "iteration": 5,
28 "duration_ms": 2536.8339580018073,
29 "usage": {
30 "total": 1234,
31 "input": 1109,
32 "output": 125
33 }
34}