"""Pure, stateless Responses wire contract; never sends a request or loads credentials.""" import re from app.schemas.rag import ContextRead, ParsedResponse, ResponsesOptions, ResponseUsage from app.services.completions import ( INSTRUCTIONS, INSUFFICIENT, CompletionProtocolError, canonical_json, ) def request_body(query: str, evidence: list, options: ResponsesOptions): body = { "model": options.model, "instructions": INSTRUCTIONS, "input": [ { "role": "user", "content": [ { "type": "input_text", "text": canonical_json({"question": query, "evidence": evidence}), } ], } ], "max_output_tokens": options.output_tokens, "stream": False, } # DeepSeek is stateless and documents store as unsupported. Other profiles # explicitly disable response storage; no previous_response_id is assumed. if options.provider_profile != "deepseek": body["store"] = False if options.temperature is not None: body["temperature"] = options.temperature return body def parse_response(payload: dict, context: ContextRead) -> ParsedResponse: """Parse final text output, never treating reasoning or partial text as an answer.""" try: if ( not isinstance(context.completion, ResponsesOptions) or context.status != "prepared" or not context.evidence or payload["object"] != "response" ): raise ValueError("unexpected context or protocol") response_id, model = payload["id"], payload["model"] if not isinstance(response_id, str) or not response_id.strip() or len(response_id) > 256: raise ValueError("invalid id") if not isinstance(model, str) or not model.strip() or len(model) > 128: raise ValueError("invalid model") usage = ( ResponseUsage.model_validate(payload["usage"]) if payload.get("usage") is not None else None ) base = dict(response_id=response_id, model=model, usage=usage) output = payload["output"] if not isinstance(output, list): raise ValueError("invalid output") status = payload["status"] if status == "failed": error = payload["error"] if not isinstance(error, dict) or not isinstance(error.get("code"), str): raise ValueError("missing error") return ParsedResponse(**base, status="failed", answer=None, citations=[]) if payload.get("error") is not None: raise ValueError("conflicting error") if status == "incomplete": reason = payload["incomplete_details"]["reason"] if reason not in {"max_output_tokens", "max_messages", "content_filter", "steered"}: raise ValueError("unknown incomplete reason") return ParsedResponse( **base, status="refused" if reason == "content_filter" else "incomplete", answer=None, citations=[], ) if status != "completed" or payload.get("incomplete_details") is not None: raise ValueError("not a completed response") messages = [] for item in output: if item["type"] == "reasoning": continue if item["type"] != "message": raise ValueError("unsupported tool or output") if item["role"] != "assistant" or item["status"] != "completed": raise ValueError("incomplete assistant message") messages.append(item) if len(messages) != 1: raise ValueError("expected one assistant message") parts = messages[0]["content"] if not isinstance(parts, list) or not parts: raise ValueError("empty content") texts, refused = [], False for part in parts: if part["type"] == "refusal": if not isinstance(part["refusal"], str) or not part["refusal"].strip(): raise ValueError("invalid refusal") refused = True elif part["type"] == "output_text" and isinstance(part["text"], str): texts.append(part["text"]) else: raise ValueError("unsupported content") if refused: return ParsedResponse(**base, status="refused", answer=None, citations=[]) answer = "".join(texts).strip() if not answer or len(answer) > 32000: raise ValueError("invalid answer") if answer == INSUFFICIENT: return ParsedResponse( **base, status="insufficient_evidence", answer=answer, citations=[] ) markers = re.findall(r"\[[Ee][^\]\r\n]*\]", answer) allowed = {f"[{item.citation_id}]" for item in context.evidence} if not markers or any(marker not in allowed for marker in markers): raise ValueError("missing or unknown citations") return ParsedResponse( **base, status="answer_candidate", answer=answer, citations=list(dict.fromkeys(marker[1:-1] for marker in markers)), ) except (KeyError, TypeError, ValueError, AttributeError) as exc: raise CompletionProtocolError("模型响应格式或引用无效") from exc