77"""
88
99import collections .abc
10- from typing import cast
1110
1211from ....backends import model_ids
1312from ....backends .adapters import AdapterMixin
1413from ...components import Document
1514from ...context import ChatContext
1615from ..chat import Message
1716from ..docs .document import _coerce_to_documents
18- from ._util import call_intrinsic
17+ from ._util import _resolve_response , call_intrinsic
1918
2019
2120def policy_guardrails (
@@ -153,6 +152,8 @@ def guardian_check(
153152 backend : AdapterMixin ,
154153 criteria : str ,
155154 target_role : str = "assistant" ,
155+ * ,
156+ documents : collections .abc .Iterable [str | Document ] | None = None ,
156157 model_options : dict | None = None ,
157158) -> float :
158159 """Check whether text meets specified safety/quality criteria.
@@ -168,12 +169,20 @@ def guardian_check(
168169 criteria string.
169170 target_role: Role whose last message is being evaluated
170171 (``"user"`` or ``"assistant"``).
172+ documents: Optional document snippets to attach to the target message.
173+ Primarily used for the ``"groundedness"`` criterion, to provide
174+ reference context for grounding checks. Each element may be a
175+ ``Document`` or a plain string (automatically wrapped in ``Document``).
176+ Keyword-only.
171177 model_options: Optional model options to pass to the backend (e.g.,
172178 temperature, max_tokens). Defaults to ``{ModelOption.TEMPERATURE: 0.0}``.
173179
174180 Returns:
175181 Risk score as a float between 0.0 (no risk) and 1.0 (risk detected).
176182 """
183+ if documents is not None and target_role == "assistant" :
184+ context = _reattach_documents (context , documents )
185+
177186 criteria_text = CRITERIA_BANK .get (criteria , criteria )
178187
179188 scoring = (
@@ -209,61 +218,57 @@ def _reattach_documents(
209218 New context with documents attached to the last assistant message.
210219
211220 Raises:
212- ValueError: If context cannot be rewound or assistant content cannot be extracted.
221+ ValueError: If context cannot be rewound or content cannot be extracted.
213222 """
214- last_turn = context .last_turn ()
215- if last_turn is None :
216- raise ValueError ("Cannot reattach documents: context has no last turn" )
217-
218- # Extract assistant content, preferring generated output over input
219- if last_turn .output is not None and last_turn .output .value is not None :
220- assistant_content = last_turn .output .value
221- elif last_turn .output is not None and last_turn .output .value is None :
222- # Uncomputed thunk — avoid silent fallthrough to model_input
223- raise ValueError (
224- "Cannot reattach documents: last turn output is uncomputed (thunk with no value)"
225- )
226- elif last_turn .model_input is not None and isinstance (
227- last_turn .model_input , Message
223+ turn = context .last_turn ()
224+ if turn is None :
225+ raise ValueError ("Cannot extract response from empty context" )
226+
227+ # Try to get response from output first (generated), then from message content (manual)
228+ response_text = None
229+ rewound = context .previous_node
230+
231+ if turn .output is not None and turn .output .value is not None :
232+ # Response is from a generated output
233+ response_text = turn .output .value
234+ elif (
235+ turn .model_input is not None
236+ and isinstance (turn .model_input , Message )
237+ and turn .model_input .role == "assistant"
228238 ):
229- assistant_content = last_turn .model_input .content
239+ # Response is from a manually added assistant Message
240+ response_text = turn .model_input .content
230241 else :
231242 raise ValueError (
232- "Cannot reattach documents: cannot extract assistant content from last turn "
243+ "Cannot extract response: turn has neither output nor assistant message "
233244 )
234245
235- # Rewind and re-add with documents
236- rewound = context .previous_node
237246 if rewound is None :
238247 raise ValueError ("Cannot rewind context past the root node" )
239248
240- return cast (
241- ChatContext ,
242- rewound .add (
243- Message (
244- "assistant" ,
245- assistant_content ,
246- documents = _coerce_to_documents (documents ),
247- )
248- ),
249+ return rewound .add ( # type: ignore[return-value]
250+ Message ("assistant" , response_text , documents = _coerce_to_documents (documents ))
249251 )
250252
251253
252254def factuality_detection (
255+ response : str | None ,
253256 context : ChatContext ,
254257 backend : AdapterMixin ,
255258 * ,
256259 documents : collections .abc .Iterable [str | Document ] | None = None ,
257260 model_options : dict | None = None ,
258261) -> str :
259- """Determine if the last response is factually incorrect.
262+ """Determine if a response is factually incorrect.
260263
261- Intrinsic function that evaluates the factuality of the
262- assistant's response to a user's question. The context should end with
263- a user question followed by an assistant answer.
264+ Intrinsic function that evaluates the factuality of an assistant's response
265+ to a user's question. The context should typically end with a user question
266+ followed by an assistant answer.
264267
265268 Args:
266- context: Chat context containing user question and assistant answer.
269+ response: The assistant's response text to evaluate. When ``None``, the
270+ response is extracted from the last assistant output in ``context``.
271+ context: Chat context containing user question and conversation history.
267272 backend: Backend instance that supports LoRA/aLoRA adapters.
268273 documents: Document snippets that provide factual context for evaluation.
269274 Each element may be a ``Document`` or a plain string (automatically
@@ -283,8 +288,24 @@ def factuality_detection(
283288### Scoring Schema: If the last assistant's text meets the criteria, return 'yes'; otherwise, return 'no'.
284289"""
285290
291+ if response is None :
292+ response , context = _resolve_response (None , context )
293+
286294 if documents is not None :
287- context = _reattach_documents (context , documents )
295+ if response is not None :
296+ # Response was explicitly provided, add it with documents
297+ context = context .add (
298+ Message (
299+ "assistant" , response , documents = _coerce_to_documents (documents )
300+ )
301+ )
302+ else :
303+ # Response came from context output, reattach documents
304+ context = _reattach_documents (context , documents )
305+ else :
306+ # No documents provided, add response to context if it was explicitly provided
307+ if response is not None :
308+ context = context .add (Message ("assistant" , response ))
288309
289310 context = context .add (Message ("user" , detector_message ))
290311 result_json = call_intrinsic (
@@ -294,19 +315,22 @@ def factuality_detection(
294315
295316
296317def factuality_correction (
318+ response : str | None ,
297319 context : ChatContext ,
298320 backend : AdapterMixin ,
299321 * ,
300322 documents : collections .abc .Iterable [str | Document ] | None = None ,
301323 model_options : dict | None = None ,
302324) -> str :
303- """Corrects the last response so that it is factually correct .
325+ """Correct a response to be factually accurate .
304326
305327 Intrinsic function that corrects the assistant's response to a user's
306328 question relative to the given contextual information.
307329
308330 Args:
309- context: Chat context containing user question and assistant answer.
331+ response: The assistant's response text to correct. When ``None``, the
332+ response is extracted from the last assistant output in ``context``.
333+ context: Chat context containing user question and conversation history.
310334 backend: Backend instance that supports LoRA/aLoRA adapters.
311335 documents: Document snippets that provide factual context for correction.
312336 Each element may be a ``Document`` or a plain string (automatically
@@ -326,8 +350,24 @@ def factuality_correction(
326350### Scoring Schema: If the last assistant's text meets the criteria, return a corrected version of the assistant's message based on the given context; otherwise, return 'none'.
327351"""
328352
353+ if response is None :
354+ response , context = _resolve_response (None , context )
355+
329356 if documents is not None :
330- context = _reattach_documents (context , documents )
357+ if response is not None :
358+ # Response was explicitly provided, add it with documents
359+ context = context .add (
360+ Message (
361+ "assistant" , response , documents = _coerce_to_documents (documents )
362+ )
363+ )
364+ else :
365+ # Response came from context output, reattach documents
366+ context = _reattach_documents (context , documents )
367+ else :
368+ # No documents provided, add response to context if it was explicitly provided
369+ if response is not None :
370+ context = context .add (Message ("assistant" , response ))
331371
332372 context = context .add (Message ("user" , corrector_message ))
333373 result_json = call_intrinsic (
0 commit comments