"""Validate final Responses state and original tool JSON before consumption.""" import json from typing import Any class ResponseIntegrityError(ValueError): """An unfinished response must not become an action or a formal verdict.""" def __init__(self, reason: str, *, status: str | None = None): self.details = {"reason": reason} if status in {"completed", "incomplete", "failed", "cancelled", "in_progress", "queued"}: self.details["status"] = status super().__init__( f"模型响应未完整有效结束({reason});已丢弃本次输出,未执行本次动作或采纳结论。" ) def ensure_response_complete(response: Any) -> None: """Check the raw message even when structured parsing already succeeded. LangChain supplies ``model_provider=openai`` on Responses chunks, but may synthesize ``chunk_position=last`` at EOF or repair partial tool JSON. Neither is evidence of provider completion. Generic models without Responses metadata remain usable; their original tool argument strings still require strict JSON. """ raw = response.get("raw") if isinstance(response, dict) and "raw" in response else response metadata = getattr(raw, "response_metadata", None) metadata = metadata if isinstance(metadata, dict) else {} status = metadata.get("status") if metadata.get("model_provider") == "openai" or metadata.get("object") == "response": if status != "completed": raise ResponseIntegrityError( "missing_completion" if status is None else "response_not_completed", status=status ) invalid_calls = getattr(raw, "invalid_tool_calls", None) if isinstance(invalid_calls, list) and invalid_calls: raise ResponseIntegrityError("invalid_tool_arguments") chunks = getattr(raw, "tool_call_chunks", None) arguments = [tool.get("args") for tool in chunks] if isinstance(chunks, list) else [] content = getattr(raw, "content", None) if isinstance(content, list): arguments.extend( block.get("arguments") for block in content if isinstance(block, dict) and block.get("type") == "function_call" ) for value in arguments: if not isinstance(value, str): raise ResponseIntegrityError("invalid_tool_arguments") try: parsed = json.loads(value) except (ValueError, RecursionError) as error: raise ResponseIntegrityError("invalid_tool_arguments") from error if not isinstance(parsed, dict): raise ResponseIntegrityError("invalid_tool_arguments")