88from dataclasses import dataclass
99from typing import Any
1010
11+ from gooddata_sdk import GoodDataSdk
12+
1113from gooddata_eval .core .chat .sse_client import ChatClient
1214from gooddata_eval .core .models import ToolCallEvent
1315
@@ -127,6 +129,41 @@ def _extract_metric_result(tool_call_events: list[ToolCallEvent]) -> dict | None
127129 return None
128130
129131
132+ def _extract_created_metric_ids (tool_call_events : list [ToolCallEvent ]) -> list [str ]:
133+ """Ids of every metric created by ``create_metric`` calls (a turn may create more than one).
134+
135+ Used for cleanup so no created metric leaks — unlike ``_extract_metric_result``, which
136+ returns only the first result for MAQL evaluation. Shared with conversation evaluation.
137+ """
138+ metric_ids : list [str ] = []
139+ for tc in tool_call_events :
140+ if tc .function_name != "create_metric" or not tc .result :
141+ continue
142+ result_data = tc .parsed_result ()
143+ if not result_data :
144+ continue
145+ data = result_data .get ("data" , result_data )
146+ metric_id = data .get ("metric_id" ) if isinstance (data , dict ) else None
147+ if metric_id and metric_id not in metric_ids :
148+ metric_ids .append (metric_id )
149+ return metric_ids
150+
151+
152+ def _delete_metric (sdk : GoodDataSdk , workspace_id : str , metric_id : str ) -> None :
153+ """Delete a metric created during evaluation.
154+
155+ Eval runs share a persistent workspace, so a metric left behind is picked up by
156+ a later test — the agent reuses it (returning ``SELECT {id}`` instead of full
157+ MAQL) and the assertion fails. Deleting the created metric on the way out keeps
158+ the workspace clean for the next run. Best-effort: failures are logged, not raised.
159+ Mirrors ``alert_skill._delete_alert``.
160+ """
161+ try :
162+ sdk ._client .entities_api .delete_entity_metrics (workspace_id , metric_id )
163+ except Exception as exc :
164+ print (f"[CLEANUP] Failed to delete metric { metric_id } : { exc } " )
165+
166+
130167def _is_asking_clarification (text : str ) -> bool :
131168 if not text :
132169 return False
@@ -136,41 +173,54 @@ def _is_asking_clarification(text: str) -> bool:
136173
137174def _execute_single_metric_run (
138175 client : ChatClient ,
176+ sdk : GoodDataSdk ,
177+ workspace_id : str ,
139178 conversation_id : str ,
140179 question : str ,
141180 expected_outputs : list [dict ],
142181 max_iterations : int ,
143182) -> MetricRunResult :
144- """Drive one full multi-turn metric-skill conversation and evaluate the result."""
183+ """Drive one full multi-turn metric-skill conversation and evaluate the result.
184+
185+ Any metric the agent creates during this run is deleted on the way out (see
186+ ``_delete_metric``) so it cannot leak into — and be reused by — a later test
187+ sharing the workspace.
188+ """
145189 primary_expected = expected_outputs [0 ] if expected_outputs else {}
146190 metric_result : dict | None = None
191+ metric_id_to_delete : str | None = None
147192 turns = 0
148193 current_question = question
149194
150- for _iteration in range (max_iterations ):
151- turns += 1
152- chat_result = client .send_message (conversation_id , current_question )
153- candidate = _extract_metric_result (chat_result .tool_call_events or [])
154- if candidate is not None :
155- metric_result = candidate
156- break
157- response_text = (chat_result .text_response or "" ).strip ()
158- if _is_asking_clarification (response_text ):
159- current_question = generate_simulated_response (response_text , primary_expected )
160- else :
161- break
162-
163- actual_maql = (metric_result or {}).get ("maql" , "" )
164- metric_created = metric_result is not None
165- maql_correct , _ = _best_maql_match (actual_maql , expected_outputs ) if metric_created else (False , "" )
166- return MetricRunResult (
167- conversation_id = conversation_id ,
168- metric_result = metric_result ,
169- metric_created = metric_created ,
170- actual_maql = actual_maql ,
171- maql_correct = maql_correct ,
172- total_turns = float (turns ),
173- )
195+ try :
196+ for _iteration in range (max_iterations ):
197+ turns += 1
198+ chat_result = client .send_message (conversation_id , current_question )
199+ candidate = _extract_metric_result (chat_result .tool_call_events or [])
200+ if candidate is not None :
201+ metric_result = candidate
202+ metric_id_to_delete = candidate .get ("metric_id" )
203+ break
204+ response_text = (chat_result .text_response or "" ).strip ()
205+ if _is_asking_clarification (response_text ):
206+ current_question = generate_simulated_response (response_text , primary_expected )
207+ else :
208+ break
209+
210+ actual_maql = (metric_result or {}).get ("maql" , "" )
211+ metric_created = metric_result is not None
212+ maql_correct , _ = _best_maql_match (actual_maql , expected_outputs ) if metric_created else (False , "" )
213+ return MetricRunResult (
214+ conversation_id = conversation_id ,
215+ metric_result = metric_result ,
216+ metric_created = metric_created ,
217+ actual_maql = actual_maql ,
218+ maql_correct = maql_correct ,
219+ total_turns = float (turns ),
220+ )
221+ finally :
222+ if metric_id_to_delete :
223+ _delete_metric (sdk , workspace_id , metric_id_to_delete )
174224
175225
176226def run_agentic_metric_skill (
@@ -191,12 +241,15 @@ def run_agentic_metric_skill(
191241 expected_outputs : list [dict ] = expected_output if isinstance (expected_output , list ) else [expected_output ]
192242 run_results : list [MetricRunResult ] = []
193243 client = ChatClient (host = host , token = token , workspace_id = workspace_id )
244+ sdk = GoodDataSdk .create (host , token )
194245
195246 try :
196247 conv_id_0 = initial_conversation_id if initial_conversation_id is not None else client .create_conversation ()
197248 try :
198249 run_results .append (
199- _execute_single_metric_run (client , conv_id_0 , question , expected_outputs , max_iterations )
250+ _execute_single_metric_run (
251+ client , sdk , workspace_id , conv_id_0 , question , expected_outputs , max_iterations
252+ )
200253 )
201254 finally :
202255 if initial_conversation_id is None : # only delete conversations we created
@@ -206,7 +259,9 @@ def run_agentic_metric_skill(
206259 conv_id = client .create_conversation ()
207260 try :
208261 run_results .append (
209- _execute_single_metric_run (client , conv_id , question , expected_outputs , max_iterations )
262+ _execute_single_metric_run (
263+ client , sdk , workspace_id , conv_id , question , expected_outputs , max_iterations
264+ )
210265 )
211266 finally :
212267 client .delete_conversation (conv_id )
0 commit comments