1818
1919import re
2020from typing import AsyncGenerator
21- from typing import Generator
2221from typing import TYPE_CHECKING
2322
2423from typing_extensions import override
@@ -77,7 +76,52 @@ async def _populate_values(
7776 instruction_template : str ,
7877 context : InvocationContext ,
7978) -> str :
80- """Populates values in the instruction template, e.g. state, artifact, etc."""
79+ """Populates values in the instruction template, e.g. state, artifact, etc.
80+
81+ Supports nested dot-separated references like:
82+ - state.user.name
83+ - artifact.config.settings
84+ - user.profile.email
85+ - user?.profile?.name? (optional markers at any level)
86+ """
87+
88+ def _get_nested_value (
89+ obj , paths : list [str ], is_optional : bool = False
90+ ) -> str :
91+ """Gets a nested value from an object using a list of path segments.
92+
93+ Args:
94+ obj: The object to get the value from
95+ paths: List of path segments to traverse
96+ is_optional: Whether the entire path is optional
97+
98+ Returns:
99+ The value as a string
100+
101+ Raises:
102+ KeyError: If the path doesn't exist and the reference is not optional
103+ """
104+ if not paths :
105+ return str (obj )
106+
107+ # Get current part and remaining paths
108+ current_part = paths [0 ]
109+
110+ # Handle optional markers
111+ is_current_optional = current_part .endswith ('?' ) or is_optional
112+ clean_part = current_part .removesuffix ('?' )
113+
114+ # Get value for current part
115+ if isinstance (obj , dict ) and clean_part in obj :
116+ return _get_nested_value (obj [clean_part ], paths [1 :], is_current_optional )
117+ elif hasattr (obj , clean_part ):
118+ return _get_nested_value (
119+ getattr (obj , clean_part ), paths [1 :], is_current_optional
120+ )
121+ elif is_current_optional :
122+ return ''
123+ else :
124+ raise KeyError (f'Key not found: { clean_part } ' )
81125
82126 async def _async_sub (pattern , repl_async_fn , string ) -> str :
83127 result = []
@@ -96,29 +140,37 @@ async def _replace_match(match) -> str:
96140 if var_name .endswith ('?' ):
97141 optional = True
98142 var_name = var_name .removesuffix ('?' )
99- if var_name .startswith ('artifact.' ):
100- var_name = var_name .removeprefix ('artifact.' )
101- if context .artifact_service is None :
102- raise ValueError ('Artifact service is not initialized.' )
103- artifact = await context .artifact_service .load_artifact (
104- app_name = context .session .app_name ,
105- user_id = context .session .user_id ,
106- session_id = context .session .id ,
107- filename = var_name ,
108- )
109- if not var_name :
110- raise KeyError (f'Artifact { var_name } not found.' )
111- return str (artifact )
112- else :
113- if not _is_valid_state_name (var_name ):
114- return match .group ()
115- if var_name in context .session .state :
116- return str (context .session .state [var_name ])
143+
144+ try :
145+ if var_name .startswith ('artifact.' ):
146+ var_name = var_name .removeprefix ('artifact.' )
147+ if context .artifact_service is None :
148+ raise ValueError ('Artifact service is not initialized.' )
149+ artifact = await context .artifact_service .load_artifact (
150+ app_name = context .session .app_name ,
151+ user_id = context .session .user_id ,
152+ session_id = context .session .id ,
153+ filename = var_name ,
154+ )
155+ if not var_name :
156+ raise KeyError (f'Artifact { var_name } not found.' )
157+ return str (artifact )
117158 else :
118- if optional :
119- return ''
120- else :
159+ if not _is_valid_state_name (var_name .split ('.' )[0 ].removesuffix ('?' )):
160+ return match .group ()
161+ # Try to resolve nested path
162+ try :
163+ return _get_nested_value (
164+ context .session .state , var_name .split ('.' ), optional
165+ )
166+ except KeyError :
167+ if not _is_valid_state_name (var_name ):
168+ return match .group ()
121169 raise KeyError (f'Context variable not found: `{ var_name } `.' )
170+ except Exception as e :
171+ if optional :
172+ return ''
173+ raise e
122174
123175 return await _async_sub (r'{+[^{}]*}+' , _replace_match , instruction_template )
124176
0 commit comments