@@ -824,37 +824,68 @@ async def _evaluate_acceptance_judge(
824824 return dataclasses .replace (judge_result , duration_ms = judge_duration_ms , usage = judge_response .usage )
825825
826826 async def _get_agent_config (
827- self , agent_key : str , context : Context
827+ self ,
828+ agent_key : str ,
829+ context : Context ,
830+ variation_key : Optional [str ] = None ,
831+ project_key : Optional [str ] = None ,
832+ api_client : Optional ["LDApiClient" ] = None ,
833+ base_url : Optional [str ] = None ,
828834 ) -> AIAgentConfig :
829835 """
830836 Fetch the agent configuration, replacing the instructions with the raw variation
831837 template so that {{placeholder}} tokens are preserved for client-side interpolation.
832838
833839 agent_config() is called normally so we get a fully populated AIAgentConfig
834- (including the tracker). We then call variation() separately to retrieve the
835- unrendered instruction template and swap it in, keeping everything else intact.
840+ (including the tracker). When variation_key is set, the specific variation's
841+ data (instructions, model, tools) is fetched via the REST API and used as the
842+ base instead of the SDK-evaluated default. Otherwise, variation() is called to
843+ retrieve the unrendered instruction template for the SDK-evaluated variation.
836844
837845 :param agent_key: The key for the agent to get the configuration for
838846 :param context: The evaluation context
847+ :param variation_key: If set, fetch this specific variation from the API as the base.
848+ :param project_key: Required when variation_key is set.
849+ :param api_client: Optional pre-built LDApiClient to reuse (e.g. from optimize_from_config).
850+ :param base_url: Optional base URL override for a newly created LDApiClient.
839851 :return: AIAgentConfig with raw {{placeholder}} instruction templates intact
840852 """
841853 try :
842854 agent_config = self ._ldClient .agent_config (agent_key , context )
843855
844- # variation() returns the raw JSON before chevron.render(), so instructions
845- # still contain {{placeholder}} tokens rather than empty strings.
846- raw_variation = self ._ldClient ._client .variation (agent_key , context , {})
847- raw_instructions = raw_variation .get (
848- "instructions" , agent_config .instructions
849- )
856+ if variation_key :
857+ # Fetch the specific variation from the REST API so instructions,
858+ # model, and tools all come from the requested base variation rather
859+ # than whatever the SDK evaluates for the given context.
860+ client = api_client or LDApiClient (
861+ self ._api_key , # type: ignore[arg-type]
862+ ** ({"base_url" : base_url } if base_url else {}),
863+ )
864+ variation_data = client .get_ai_config_variation (project_key , agent_key , variation_key ) # type: ignore[arg-type]
865+ raw_instructions = variation_data .get ("instructions" ) or ""
866+ raw_tools = variation_data .get ("tools" ) or []
867+ model_config_key = variation_data .get ("modelConfigKey" ) or ""
868+ if model_config_key :
869+ agent_config = dataclasses .replace (
870+ agent_config ,
871+ model = ModelConfig (name = model_config_key , parameters = {}),
872+ )
873+ else :
874+ # variation() returns the raw JSON before chevron.render(), so instructions
875+ # still contain {{placeholder}} tokens rather than empty strings.
876+ raw_variation = self ._ldClient ._client .variation (agent_key , context , {})
877+ raw_instructions = raw_variation .get (
878+ "instructions" , agent_config .instructions
879+ )
880+ raw_tools = raw_variation .get ("tools" , [])
881+
850882 if not raw_instructions :
851883 raise ValueError (
852884 f"Agent '{ agent_key } ' has no instructions configured. "
853885 "Ensure the agent flag has instructions set before running an optimization."
854886 )
855887 self ._initial_instructions = raw_instructions
856888
857- raw_tools = raw_variation .get ("tools" , [])
858889 self ._initial_tool_keys = [
859890 t ["key" ]
860891 for t in raw_tools
@@ -888,9 +919,24 @@ async def optimize_from_options(
888919 raise ValueError (
889920 "auto_commit requires project_key to be set on OptimizationOptions"
890921 )
922+ if options .variation_key :
923+ if not self ._has_api_key :
924+ raise ValueError (
925+ "variation_key requires LAUNCHDARKLY_API_KEY to be set"
926+ )
927+ if not options .project_key :
928+ raise ValueError (
929+ "variation_key requires project_key to be set on OptimizationOptions"
930+ )
891931 self ._agent_key = agent_key
892932 context = random .choice (options .context_choices )
893- agent_config = await self ._get_agent_config (agent_key , context )
933+ agent_config = await self ._get_agent_config (
934+ agent_key ,
935+ context ,
936+ variation_key = options .variation_key ,
937+ project_key = options .project_key ,
938+ base_url = options .base_url ,
939+ )
894940 result = await self ._run_optimization (agent_config , options )
895941 if options .auto_commit and self ._last_run_succeeded and self ._last_succeeded_context :
896942 self ._commit_variation (
@@ -926,9 +972,24 @@ async def optimize_from_ground_truth_options(
926972 raise ValueError (
927973 "auto_commit requires project_key to be set on GroundTruthOptimizationOptions"
928974 )
975+ if options .variation_key :
976+ if not self ._has_api_key :
977+ raise ValueError (
978+ "variation_key requires LAUNCHDARKLY_API_KEY to be set"
979+ )
980+ if not options .project_key :
981+ raise ValueError (
982+ "variation_key requires project_key to be set on GroundTruthOptimizationOptions"
983+ )
929984 self ._agent_key = agent_key
930985 context = random .choice (options .context_choices )
931- agent_config = await self ._get_agent_config (agent_key , context )
986+ agent_config = await self ._get_agent_config (
987+ agent_key ,
988+ context ,
989+ variation_key = options .variation_key ,
990+ project_key = options .project_key ,
991+ base_url = options .base_url ,
992+ )
932993 result = await self ._run_ground_truth_optimization (agent_config , options )
933994 if options .auto_commit and self ._last_run_succeeded and self ._last_succeeded_context :
934995 self ._commit_variation (
@@ -1425,7 +1486,13 @@ async def optimize_from_config(
14251486 context = random .choice (options .context_choices )
14261487 # _get_agent_config calls _initialize_class_members_from_config internally;
14271488 # _run_optimization calls it again to reset history before the loop starts.
1428- agent_config = await self ._get_agent_config (self ._agent_key , context )
1489+ agent_config = await self ._get_agent_config (
1490+ self ._agent_key ,
1491+ context ,
1492+ variation_key = config .get ("variationKey" ),
1493+ project_key = options .project_key ,
1494+ api_client = api_client ,
1495+ )
14291496
14301497 optimization_options = self ._build_options_from_config (
14311498 config , options , api_client , optimization_key , run_id , model_configs
0 commit comments