Download model.py from gaaaaaaaaaaa/MultimodalReasoning3B: direct link, hf CLI and curl.
- Browser
- Download file 57.1 kB
-
https://huggingface.co/gaaaaaaaaaaa/MultimodalReasoning3B/resolve/main/model.py
- Command line
-
hf download hf://gaaaaaaaaaaa/MultimodalReasoning3B/model.py
-
curl -L -o model.py https://huggingface.co/gaaaaaaaaaaa/MultimodalReasoning3B/resolve/main/model.py
57.1 kB
| import re | |
| import torch | |
| from queue import Empty | |
| from threading import Thread | |
| from typing import Callable, Generator, List, Dict, Optional | |
| from transformers import ( | |
| AutoTokenizer, | |
| AutoModelForCausalLM, | |
| TextIteratorStreamer, | |
| StoppingCriteria, | |
| StoppingCriteriaList, | |
| ) | |
| from prompts import REASONING_SYSTEM_PROMPT | |
| from tokens_control import protocol as token_protocol | |
| CHAT_CONTROL_TOKENS = token_protocol.CHAT_CONTROL_TOKENS | |
| def strip_chat_control_tokens(text: str | None, trim: bool = True) -> str: | |
| if text is None: | |
| return "" | |
| cleaned = str(text) | |
| for token in CHAT_CONTROL_TOKENS: | |
| cleaned = cleaned.replace(token, "") | |
| return cleaned.strip() if trim else cleaned | |
| class StopOnSubstrings(StoppingCriteria): | |
| """ | |
| Stops generation once any stop string appears in the decoded tail. | |
| """ | |
| def __init__( | |
| self, | |
| tokenizer, | |
| stop_strings: List[str], | |
| prompt_length: int = 0, | |
| max_check_tokens: int = 80, | |
| ): | |
| self.tokenizer = tokenizer | |
| self.stop_strings = stop_strings | |
| self.prompt_length = prompt_length | |
| self.max_check_tokens = max_check_tokens | |
| def __call__(self, input_ids, scores, **kwargs) -> bool: | |
| generated_ids = input_ids[0][self.prompt_length:] | |
| if generated_ids.numel() == 0: | |
| return False | |
| tail_ids = generated_ids[-self.max_check_tokens:] | |
| tail = self.tokenizer.decode(tail_ids, skip_special_tokens=False) | |
| return any(stop in tail for stop in self.stop_strings) | |
| class StopOnThinkingTokenLimit(StoppingCriteria): | |
| """ | |
| Stops generation when the currently open <think> block reaches a token cap. | |
| """ | |
| def __init__( | |
| self, | |
| tokenizer, | |
| token_limit: int, | |
| prompt_length: int = 0, | |
| initial_in_think: bool = False, | |
| ): | |
| self.tokenizer = tokenizer | |
| self.token_limit = max(0, int(token_limit)) | |
| self.prompt_length = prompt_length | |
| self.initial_in_think = initial_in_think | |
| self.triggered = False | |
| def __call__(self, input_ids, scores, **kwargs) -> bool: | |
| generated_ids = input_ids[0][self.prompt_length:] | |
| if generated_ids.numel() == 0: | |
| return False | |
| generated = self.tokenizer.decode(generated_ids, skip_special_tokens=False) | |
| think_start = generated.rfind("<think>") | |
| think_end = generated.rfind("</think>") | |
| if think_start >= 0 and think_end < think_start: | |
| content = generated[think_start + len("<think>"):] | |
| elif self.initial_in_think and think_end < 0: | |
| content = generated | |
| else: | |
| return False | |
| count = len( | |
| self.tokenizer( | |
| content, | |
| add_special_tokens=False, | |
| )["input_ids"] | |
| ) | |
| if count >= self.token_limit: | |
| self.triggered = True | |
| return True | |
| return False | |
| class ResultTagSuppressor: | |
| """ | |
| Suppresses model-generated <result> blocks from the visible stream. | |
| Runtime-injected <result> blocks are handled separately. | |
| """ | |
| START = "<result>" | |
| END = "</result>" | |
| TAIL = max(len(START), len(END)) - 1 | |
| def __init__(self): | |
| self.buffer = "" | |
| self.in_result = False | |
| self.suppressed = False | |
| def feed(self, chunk: str) -> str: | |
| self.buffer += chunk | |
| output = [] | |
| while self.buffer: | |
| if self.in_result: | |
| end_index = self.buffer.find(self.END) | |
| if end_index < 0: | |
| self.buffer = self.buffer[-self.TAIL:] | |
| break | |
| self.buffer = self.buffer[end_index + len(self.END):] | |
| self.in_result = False | |
| continue | |
| start_index = self.buffer.find(self.START) | |
| if start_index >= 0: | |
| output.append(self.buffer[:start_index]) | |
| self.buffer = self.buffer[start_index + len(self.START):] | |
| self.in_result = True | |
| self.suppressed = True | |
| continue | |
| if len(self.buffer) <= self.TAIL: | |
| break | |
| emit_upto = len(self.buffer) - self.TAIL | |
| output.append(self.buffer[:emit_upto]) | |
| self.buffer = self.buffer[emit_upto:] | |
| break | |
| return "".join(output) | |
| def flush(self) -> str: | |
| if self.in_result: | |
| self.buffer = "" | |
| self.in_result = False | |
| return "" | |
| output = self.buffer | |
| self.buffer = "" | |
| return output | |
| class EarlyAnswerSuppressor: | |
| """ | |
| While retrieval has started but min_tool_rounds has not been reached, | |
| hides early <answer> attempts from the UI. | |
| """ | |
| START = "<answer>" | |
| TAIL = len(START) - 1 | |
| def __init__(self): | |
| self.buffer = "" | |
| self.blocked = False | |
| def feed(self, chunk: str) -> str: | |
| if self.blocked: | |
| return "" | |
| self.buffer += chunk | |
| start_index = self.buffer.find(self.START) | |
| if start_index >= 0: | |
| output = self.buffer[:start_index] | |
| self.buffer = "" | |
| self.blocked = True | |
| return output | |
| if len(self.buffer) <= self.TAIL: | |
| return "" | |
| emit_upto = len(self.buffer) - self.TAIL | |
| output = self.buffer[:emit_upto] | |
| self.buffer = self.buffer[emit_upto:] | |
| return output | |
| def flush(self) -> str: | |
| if self.blocked: | |
| self.buffer = "" | |
| return "" | |
| output = self.buffer | |
| self.buffer = "" | |
| return output | |
| class StartTagSuppressor: | |
| """ | |
| Suppresses a forbidden opening tag and everything generated after it. | |
| Used after retrieval is exhausted so leftover <query> spam never reaches UI. | |
| """ | |
| def __init__(self, start_tags: List[str]): | |
| self.start_tags = start_tags | |
| self.tail = max((len(tag) for tag in start_tags), default=1) - 1 | |
| self.buffer = "" | |
| self.blocked = False | |
| def feed(self, chunk: str) -> str: | |
| if self.blocked: | |
| return "" | |
| self.buffer += chunk | |
| starts = [ | |
| index | |
| for tag in self.start_tags | |
| for index in [self.buffer.find(tag)] | |
| if index >= 0 | |
| ] | |
| if starts: | |
| start_index = min(starts) | |
| output = self.buffer[:start_index] | |
| self.buffer = "" | |
| self.blocked = True | |
| return output | |
| if len(self.buffer) <= self.tail: | |
| return "" | |
| emit_upto = len(self.buffer) - self.tail | |
| output = self.buffer[:emit_upto] | |
| self.buffer = self.buffer[emit_upto:] | |
| return output | |
| def flush(self) -> str: | |
| if self.blocked: | |
| self.buffer = "" | |
| return "" | |
| output = self.buffer | |
| self.buffer = "" | |
| return output | |
| class MultimodalQwen: | |
| QUERY_RE = token_protocol.QUERY_RE | |
| QUERY_FORBIDDEN_RE = token_protocol.QUERY_FORBIDDEN_RE | |
| QUERY_PREFIX_JUNK_RE = token_protocol.QUERY_PREFIX_JUNK_RE | |
| QUERY_LEAK_STOP_STRINGS = token_protocol.QUERY_LEAK_STOP_STRINGS | |
| RESULT_BLOCK_RE = token_protocol.RESULT_BLOCK_RE | |
| PLAIN_FINAL_RE = re.compile( | |
| r"(?is)(?:^|\n)\s*(?:final answer|answer)\s*:\s*(.+)$" | |
| ) | |
| GENERATED_RESULT_CLAIM_RE = re.compile( | |
| r"""(?is) | |
| ( | |
| \b(?:the\s+)?retrieved\s+result\s+(?:is|says|shows|states)\s*: | |
| | | |
| \b(?:the\s+)?retrieval\s+result\s+(?:is|says|shows|states)\s*: | |
| | | |
| \b(?:the\s+)?document\s+(?:says|states|shows|mentions)\s*: | |
| | | |
| \b(?:the\s+)?source\s+(?:says|states|shows|mentions)\s*: | |
| | | |
| \bresult\s*: | |
| | | |
| \bretrieved\s+result\s*: | |
| | | |
| \bdocument\s*: | |
| ) | |
| """, | |
| re.VERBOSE, | |
| ) | |
| FAKE_RETRIEVAL_CLAIM_RE = re.compile( | |
| r"""(?is) | |
| \b( | |
| retrieved\s+(?:result|text|document|evidence) | |
| | | |
| retrieval\s+(?:result|text|document|evidence) | |
| | | |
| document | |
| | | |
| source | |
| | | |
| evidence | |
| )\s+ | |
| (?:is|says|states|shows|mentions|contains|indicates)\b | |
| """, | |
| re.VERBOSE, | |
| ) | |
| BAD_META_QUERY_RE = re.compile( | |
| r"""(?is)^\s* | |
| ( | |
| block\s+to\s+request\s+retrieval | |
| |query\s+block | |
| |request\s+retrieval | |
| |emit\s+(?:a\s+)?query | |
| |retrieve\s+it | |
| |look\s+it\s+up | |
| ) | |
| [\s.]*$ | |
| """, | |
| re.VERBOSE, | |
| ) | |
| BAD_META_ANSWER_RE = re.compile( | |
| r"""(?is) | |
| <answer>\s* | |
| ( | |
| .*?\b(?:should|need|must)\s+(?:retrieve|query|search|look\s+up)\b.* | |
| | | |
| .*?\bno\s+(?:document|context|result|retrieved\s+evidence)\s+(?:is|was)\s+present\b.* | |
| | | |
| .*?\bno\s+retrieved\s+document\s+evidence\s+was\s+found\b.* | |
| | | |
| this\s+is\s+a\s+(?:fact|factual|document|external|fictional).{0,240} | |
| ) | |
| """, | |
| re.VERBOSE, | |
| ) | |
| OFF_TOPIC_EVIDENCE_RE = re.compile( | |
| r"""(?is) | |
| ( | |
| retrieved\s+(?:result|evidence|document).{0,160} | |
| (?:not\s+directly\s+applicable|not\s+directly\s+address|does\s+not\s+address|irrelevant|off-topic) | |
| | | |
| evidence\s+is\s+incomplete | |
| | | |
| retrieved\s+evidence\s+is\s+insufficient | |
| ) | |
| """, | |
| re.VERBOSE, | |
| ) | |
| RETRIEVAL_INTENT_RE = re.compile( | |
| r"""(?is) | |
| ( | |
| \b(?:require|requires|required|need|needs|needed|must|should|have\s+to)\b | |
| .{0,160}? | |
| \b(?:retrieval|retrieve|query|lookup|search|document|documents|passage|passages|source|sources|evidence)\b | |
| ) | |
| | | |
| \bno\s+(?:retrieval\s+)?result\s+is\s+present\b | |
| | | |
| \bno\s+direct\s+evidence\s+is\s+present\b | |
| | | |
| \bmust\s+create\s+(?:one|a\s+(?:query|retrieval|lookup))\b | |
| | | |
| \bneed\s+to\s+check\s+(?:if\s+)?(?:the\s+)?documents?\b | |
| """, | |
| re.VERBOSE, | |
| ) | |
| NO_RETRIEVAL_INTENT_RE = re.compile( | |
| r"""(?is) | |
| \b(?:do\s+not|don't|does\s+not|doesn't|no|not)\s+ | |
| (?:need|require|requires|required)\s+ | |
| .{0,80}? | |
| \b(?:retrieval|retrieve|query|lookup|search)\b | |
| """, | |
| re.VERBOSE, | |
| ) | |
| TOOL_PROMISE_RE = re.compile( | |
| r"""(?is) | |
| \b( | |
| i\s+will | |
| |i'll | |
| |i\s+am\s+going\s+to | |
| |i'm\s+going\s+to | |
| |let\s+me | |
| |i\s+should | |
| |i\s+need\s+to | |
| |i\s+have\s+to | |
| )\s+ | |
| (?:now\s+|first\s+|try\s+to\s+)? | |
| (search|retrieve|query|look\s+up|lookup|check\s+(?:the\s+)?documents?|find\s+(?:evidence|sources?)) | |
| \b | |
| """, | |
| re.VERBOSE, | |
| ) | |
| PLAIN_ANSWER_ATTEMPT_RE = re.compile( | |
| r"(?is)(?:^|\n)\s*(?:final\s+answer|answer)\s*:\s*[\s\S]*$" | |
| ) | |
| def __init__( | |
| self, | |
| model_name: str = "Qwen/Qwen2.5-3B-Instruct", | |
| device: Optional[str] = None, | |
| torch_dtype="auto", | |
| ): | |
| self.model_name = model_name | |
| self.tokenizer = AutoTokenizer.from_pretrained( | |
| model_name, | |
| trust_remote_code=True, | |
| ) | |
| if self.tokenizer.pad_token_id is None: | |
| self.tokenizer.pad_token = self.tokenizer.eos_token | |
| self.device = device or ("cuda" if torch.cuda.is_available() else "cpu") | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| model_name, | |
| torch_dtype=torch_dtype, | |
| trust_remote_code=True, | |
| ).to(self.device) | |
| self.model.eval() | |
| self.prompt = REASONING_SYSTEM_PROMPT | |
| # --------------------------------------------------------------------- | |
| # Basic helpers | |
| # --------------------------------------------------------------------- | |
| def _stop_strings(self, stop_strings: Optional[List[str]] = None) -> List[str]: | |
| return list(stop_strings or []) | |
| def _eos_token_ids(self): | |
| return self.tokenizer.eos_token_id | |
| def coerce_plain_final_answer(self, text: str) -> tuple[str, bool]: | |
| """ | |
| Do not fabricate <answer> in Python. | |
| The model must emit protocol tags itself. | |
| """ | |
| return text, False | |
| def has_open_tag(text: str, tag: str) -> bool: | |
| return text.rfind(f"<{tag}>") > text.rfind(f"</{tag}>") | |
| def first_terminal_tag(text: str) -> tuple[str | None, int]: | |
| """ | |
| Returns the first completed terminal protocol block. | |
| If model emits: | |
| <query>real query</query><query><answer>junk</answer> | |
| runtime keeps only: | |
| <query>real query</query> | |
| and starts retrieval immediately. | |
| """ | |
| text = text or "" | |
| candidates: list[tuple[int, str]] = [] | |
| for close_tag, kind in ( | |
| ("</query>", "query"), | |
| ("</answer>", "answer"), | |
| ): | |
| index = text.find(close_tag) | |
| if index >= 0: | |
| candidates.append((index + len(close_tag), kind)) | |
| if not candidates: | |
| return None, -1 | |
| end_index, kind = min(candidates, key=lambda item: item[0]) | |
| return kind, end_index | |
| def _slice_to_first_terminal(self, text: str) -> tuple[str, str | None]: | |
| terminal_kind, terminal_end = self.first_terminal_tag(text) | |
| if terminal_kind is None: | |
| return text, None | |
| return text[:terminal_end], terminal_kind | |
| # --------------------------------------------------------------------- | |
| # Prompt formatting | |
| # --------------------------------------------------------------------- | |
| def _messages( | |
| self, | |
| user_prompt: str, | |
| system_prompt: Optional[str] = None, | |
| ) -> List[Dict[str, str]]: | |
| active_prompt = str(system_prompt).strip() if system_prompt is not None else "" | |
| active_prompt = active_prompt or self.prompt | |
| return [ | |
| {"role": "system", "content": active_prompt}, | |
| {"role": "user", "content": strip_chat_control_tokens(user_prompt)}, | |
| ] | |
| def _apply_chat_template(self, messages: List[Dict[str, str]]): | |
| inputs = self.tokenizer.apply_chat_template( | |
| messages, | |
| add_generation_prompt=True, | |
| tokenize=True, | |
| return_dict=True, | |
| return_tensors="pt", | |
| ) | |
| return {k: v.to(self.device) for k, v in inputs.items()} | |
| def _build_raw_prompt( | |
| self, | |
| user_prompt: str, | |
| system_prompt: Optional[str] = None, | |
| ) -> str: | |
| return self.tokenizer.apply_chat_template( | |
| self._messages(user_prompt, system_prompt=system_prompt), | |
| add_generation_prompt=True, | |
| tokenize=False, | |
| ) | |
| def _tokenize_raw(self, raw_prompt: str): | |
| inputs = self.tokenizer( | |
| raw_prompt, | |
| return_tensors="pt", | |
| add_special_tokens=False, | |
| ) | |
| return {k: v.to(self.device) for k, v in inputs.items()} | |
| # --------------------------------------------------------------------- | |
| # Basic generation | |
| # --------------------------------------------------------------------- | |
| def generate( | |
| self, | |
| prompt: str, | |
| max_new_tokens: int = 512, | |
| temperature: float = 0.2, | |
| top_p: float = 0.9, | |
| system_prompt: Optional[str] = None, | |
| ) -> str: | |
| messages = self._messages(prompt, system_prompt=system_prompt) | |
| inputs = self._apply_chat_template(messages) | |
| do_sample = temperature > 0 | |
| output_ids = self.model.generate( | |
| **inputs, | |
| max_new_tokens=max_new_tokens, | |
| do_sample=do_sample, | |
| temperature=temperature if do_sample else None, | |
| top_p=top_p if do_sample else None, | |
| pad_token_id=self.tokenizer.pad_token_id, | |
| eos_token_id=self._eos_token_ids(), | |
| ) | |
| new_ids = output_ids[0][inputs["input_ids"].shape[-1]:] | |
| return strip_chat_control_tokens( | |
| self.tokenizer.decode(new_ids, skip_special_tokens=True) | |
| ) | |
| def _generate_raw( | |
| self, | |
| raw_prompt: str, | |
| max_new_tokens: int = 512, | |
| temperature: float = 0.2, | |
| top_p: float = 0.9, | |
| stop_strings: Optional[List[str]] = None, | |
| ) -> str: | |
| inputs = self._tokenize_raw(raw_prompt) | |
| do_sample = temperature > 0 | |
| stop_strings = self._stop_strings(stop_strings) | |
| stopping_criteria = None | |
| if stop_strings: | |
| stopping_criteria = StoppingCriteriaList([ | |
| StopOnSubstrings( | |
| self.tokenizer, | |
| stop_strings, | |
| prompt_length=inputs["input_ids"].shape[-1], | |
| ) | |
| ]) | |
| output_ids = self.model.generate( | |
| **inputs, | |
| max_new_tokens=max_new_tokens, | |
| do_sample=do_sample, | |
| temperature=temperature if do_sample else None, | |
| top_p=top_p if do_sample else None, | |
| pad_token_id=self.tokenizer.pad_token_id, | |
| eos_token_id=self._eos_token_ids(), | |
| stopping_criteria=stopping_criteria, | |
| ) | |
| new_ids = output_ids[0][inputs["input_ids"].shape[-1]:] | |
| return strip_chat_control_tokens( | |
| self.tokenizer.decode(new_ids, skip_special_tokens=True) | |
| ) | |
| def stream_raw( | |
| self, | |
| raw_prompt: str, | |
| max_new_tokens: int = 512, | |
| temperature: float = 0.2, | |
| top_p: float = 0.9, | |
| stop_strings: Optional[List[str]] = None, | |
| thinking_token_limit: Optional[int] = None, | |
| thinking_limit_state: Optional[dict] = None, | |
| ) -> Generator[str, None, None]: | |
| inputs = self._tokenize_raw(raw_prompt) | |
| streamer = TextIteratorStreamer( | |
| self.tokenizer, | |
| skip_prompt=True, | |
| skip_special_tokens=True, | |
| timeout=1.0, | |
| ) | |
| do_sample = temperature > 0 | |
| stop_strings = self._stop_strings(stop_strings) | |
| stopping_items = [] | |
| if stop_strings: | |
| stopping_items.append( | |
| StopOnSubstrings( | |
| self.tokenizer, | |
| stop_strings, | |
| prompt_length=inputs["input_ids"].shape[-1], | |
| ) | |
| ) | |
| thinking_stopper = None | |
| if thinking_token_limit is not None: | |
| thinking_stopper = StopOnThinkingTokenLimit( | |
| self.tokenizer, | |
| thinking_token_limit, | |
| prompt_length=inputs["input_ids"].shape[-1], | |
| initial_in_think=self.has_open_tag(raw_prompt, "think"), | |
| ) | |
| stopping_items.append(thinking_stopper) | |
| stopping_criteria = StoppingCriteriaList(stopping_items) if stopping_items else None | |
| kwargs = dict( | |
| **inputs, | |
| streamer=streamer, | |
| max_new_tokens=max_new_tokens, | |
| do_sample=do_sample, | |
| temperature=temperature if do_sample else None, | |
| top_p=top_p if do_sample else None, | |
| pad_token_id=self.tokenizer.pad_token_id, | |
| eos_token_id=self._eos_token_ids(), | |
| stopping_criteria=stopping_criteria, | |
| ) | |
| generation_error = [] | |
| def run_generation(): | |
| try: | |
| with torch.inference_mode(): | |
| self.model.generate(**kwargs) | |
| except BaseException as exc: | |
| generation_error.append(exc) | |
| try: | |
| streamer.on_finalized_text("", stream_end=True) | |
| except Exception: | |
| pass | |
| thread = Thread(target=run_generation, daemon=True) | |
| thread.start() | |
| while True: | |
| try: | |
| chunk = next(streamer) | |
| except StopIteration: | |
| break | |
| except Empty: | |
| if not thread.is_alive(): | |
| break | |
| continue | |
| chunk = strip_chat_control_tokens(chunk, trim=False) | |
| if chunk: | |
| yield chunk | |
| thread.join() | |
| if thinking_limit_state is not None: | |
| thinking_limit_state["triggered"] = bool( | |
| thinking_stopper and thinking_stopper.triggered | |
| ) | |
| if generation_error: | |
| raise RuntimeError("Model generation failed") from generation_error[0] | |
| def stream( | |
| self, | |
| prompt: str, | |
| max_new_tokens: int = 512, | |
| temperature: float = 0.2, | |
| top_p: float = 0.9, | |
| thinking_token_limit: Optional[int] = None, | |
| system_prompt: Optional[str] = None, | |
| ) -> Generator[str, None, None]: | |
| raw_prompt = self._build_raw_prompt(prompt, system_prompt=system_prompt) | |
| thinking_state: dict = {} | |
| generated = "" | |
| for chunk in self.stream_raw( | |
| raw_prompt=raw_prompt, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| thinking_token_limit=thinking_token_limit, | |
| thinking_limit_state=thinking_state, | |
| ): | |
| generated += chunk | |
| yield chunk | |
| raw_prompt += generated | |
| if thinking_state.get("triggered") and self.has_open_tag(raw_prompt, "think"): | |
| close_think = "\n</think>\n" | |
| raw_prompt += close_think | |
| yield close_think | |
| forced = ( | |
| "[Runtime instruction: Thinking budget reached. " | |
| "Produce the final <answer>...</answer> now.]\n" | |
| ) | |
| raw_prompt += forced | |
| yield forced | |
| for chunk in self.stream_raw( | |
| raw_prompt=raw_prompt, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stop_strings=["</answer>", "<result>", "</result>", "</query>", "<query>", "<think>"], | |
| ): | |
| yield chunk | |
| # --------------------------------------------------------------------- | |
| # Protocol parsing / sanitizing | |
| # --------------------------------------------------------------------- | |
| def extract_query(self, text: str) -> Optional[str]: | |
| return token_protocol.extract_query(text) | |
| def extract_open_query(self, text: str) -> Optional[str]: | |
| """ | |
| Only catches a truly unclosed final <query> block. | |
| """ | |
| text = text or "" | |
| lower = text.lower() | |
| last_open = lower.rfind("<query>") | |
| last_close = lower.rfind("</query>") | |
| if last_open < 0: | |
| return None | |
| if last_close > last_open: | |
| return None | |
| return text[last_open + len("<query>"):].strip() | |
| def sanitize_query_text(self, query: str) -> str: | |
| query = str(query or "").strip() | |
| # Remove repeated/nested query openings. | |
| query = re.sub(r"(?is)(?:<query>\s*)+", "", query).strip() | |
| query = re.split( | |
| r"(?is)</?think>|</?answer>|</?result>|<query>|</query>|final\s+answer\s*:|answer\s*:", | |
| query, | |
| maxsplit=1, | |
| )[0].strip() | |
| query = re.sub( | |
| r"(?is)\b(?:retrieved\s+evidence\s+only|evidence\s+only|use\s+retrieved\s+evidence\s+only)\b\.?", | |
| "", | |
| query, | |
| ).strip() | |
| query = self.QUERY_PREFIX_JUNK_RE.sub("", query).strip() | |
| query = re.sub(r"(?is)^(query|search|lookup)\s*:\s*", "", query).strip() | |
| return query | |
| def has_malformed_query(self, text: str) -> bool: | |
| open_query = self.extract_open_query(text) | |
| if open_query is None: | |
| return False | |
| clean_query = self.sanitize_query_text(open_query) | |
| if not clean_query: | |
| return True | |
| if self.QUERY_FORBIDDEN_RE.search(open_query): | |
| return True | |
| return False | |
| def malformed_query_correction() -> str: | |
| return token_protocol.malformed_query_correction() | |
| def strip_generated_result_blocks(self, text: str) -> tuple[str, bool]: | |
| cleaned, count = self.RESULT_BLOCK_RE.subn("", text) | |
| return cleaned, count > 0 | |
| def sanitize_result(self, result: str) -> str: | |
| if result is None: | |
| result = "" | |
| result = str(result) | |
| result = strip_chat_control_tokens(result) | |
| replacements = { | |
| "<think>": "[think]", | |
| "</think>": "[/think]", | |
| "<query>": "[query]", | |
| "</query>": "[/query]", | |
| "<result>": "[result]", | |
| "</result>": "[/result]", | |
| "<answer>": "[answer]", | |
| "</answer>": "[/answer]", | |
| } | |
| for old, new in replacements.items(): | |
| result = result.replace(old, new) | |
| return result.strip() | |
| def format_query_result(self, query: str, result: str) -> str: | |
| safe_query = self.sanitize_result(query) | |
| safe_result = self.sanitize_result(result) | |
| if not safe_result: | |
| safe_result = "No retrieval results found." | |
| return ( | |
| "Query:\n" | |
| f"{safe_query}\n\n" | |
| "Retrieved Result:\n" | |
| f"{safe_result}" | |
| ) | |
| def retrieval_limit_result(self, max_tool_rounds: int) -> str: | |
| return self.format_query_result( | |
| query="retrieval limit", | |
| result=( | |
| f"Retrieval limit reached ({max_tool_rounds}). " | |
| "No more retrieval documents will be provided for this answer. " | |
| "Use the evidence already shown, say when it is insufficient, " | |
| "and finish with answer." | |
| ), | |
| ) | |
| def is_bad_meta_query(self, query: str) -> bool: | |
| return self.BAD_META_QUERY_RE.search(query or "") is not None | |
| def has_bad_meta_answer(self, text: str) -> bool: | |
| return self.BAD_META_ANSWER_RE.search(text or "") is not None | |
| def says_evidence_off_topic_or_incomplete(self, text: str) -> bool: | |
| return self.OFF_TOPIC_EVIDENCE_RE.search(text or "") is not None | |
| def has_generated_result_claim(self, text: str, tool_rounds: int) -> bool: | |
| if not text: | |
| return False | |
| if tool_rounds > 0: | |
| return False | |
| if "<result>" in text or "</result>" in text: | |
| return False | |
| return self.GENERATED_RESULT_CLAIM_RE.search(text) is not None | |
| def strip_generated_result_claim(self, text: str) -> str: | |
| match = self.GENERATED_RESULT_CLAIM_RE.search(text or "") | |
| if not match: | |
| return text | |
| return text[:match.start()].rstrip() | |
| def has_fake_retrieval_claim(self, text: str, tool_rounds: int) -> bool: | |
| if tool_rounds > 0: | |
| return False | |
| if "<result>" in text or "</result>" in text: | |
| return False | |
| return self.FAKE_RETRIEVAL_CLAIM_RE.search(text or "") is not None | |
| def strip_fake_retrieval_claim(self, text: str) -> str: | |
| match = self.FAKE_RETRIEVAL_CLAIM_RE.search(text or "") | |
| if not match: | |
| return text | |
| return text[:match.start()].rstrip() | |
| def retrieval_exhausted_answer_instruction(max_tool_rounds: int) -> str: | |
| return ( | |
| "\n[Runtime instruction: Retrieval budget is exhausted. " | |
| f"Maximum retrieval results already reached: {max_tool_rounds}. " | |
| "Do not emit <query> again. Do not say you will search, retrieve, look up, " | |
| "check documents, or request more evidence. " | |
| "Produce exactly one final <answer>...</answer> now, using only the existing " | |
| "<result> evidence. If the evidence is insufficient, say it is insufficient.]\n" | |
| ) | |
| def protocol_correction(force_answer: bool = False) -> str: | |
| if force_answer: | |
| return ( | |
| "\n[Runtime instruction: No more retrieval results are available. " | |
| "Answer from the evidence already shown.]\n" | |
| ) | |
| return ( | |
| "\n[Runtime instruction: Do not create retrieval results yourself. " | |
| "Either request one focused lookup or answer directly.]\n" | |
| ) | |
| def fake_retrieval_claim_correction() -> str: | |
| return ( | |
| "\n[Runtime instruction: You claimed retrieval/document evidence exists, " | |
| "but no <result> block has been provided by runtime. Do not invent retrieved content. " | |
| "Emit exactly one focused <query>...</query> next.]\n" | |
| ) | |
| def generated_result_claim_correction() -> str: | |
| return ( | |
| "\n[Runtime instruction: You cannot create or describe retrieved results yourself. " | |
| "Only the runtime may provide retrieval results inside <result>...</result>. " | |
| "If evidence is needed, emit exactly one focused <query>...</query>. " | |
| "If evidence is not needed, answer directly without mentioning retrieved results.]\n" | |
| ) | |
| def force_query_correction() -> str: | |
| return ( | |
| "\n[Runtime instruction: You promised or stated that retrieval/search is needed, " | |
| "but you did not emit a valid <query> block. Natural-language promises like " | |
| "'I will search' do not execute tools. Emit exactly one concrete " | |
| "<query>...</query> next.]\n" | |
| ) | |
| def minimum_retrieval_correction(self, tool_rounds: int, min_tool_rounds: int) -> str: | |
| remaining = max(0, min_tool_rounds - tool_rounds) | |
| return ( | |
| "\n[Runtime instruction: Retrieval has started, but not enough retrieval results " | |
| "have been collected yet. " | |
| f"Current retrieval results: {tool_rounds}. " | |
| f"Minimum required retrieval results: {min_tool_rounds}. " | |
| f"Remaining retrieval result(s): {remaining}. " | |
| "Request another focused retrieval query next.]\n" | |
| ) | |
| def declares_retrieval_intent(self, text: str) -> bool: | |
| if not text: | |
| return False | |
| if self.extract_query(text) is not None: | |
| return False | |
| if self.NO_RETRIEVAL_INTENT_RE.search(text): | |
| return False | |
| return ( | |
| self.RETRIEVAL_INTENT_RE.search(text) is not None | |
| or self.TOOL_PROMISE_RE.search(text) is not None | |
| ) | |
| def strip_answer_attempts(self, text: str) -> str: | |
| text = re.sub(r"(?is)<answer>[\s\S]*?(?:</answer>|$)", "", text) | |
| text = self.PLAIN_ANSWER_ATTEMPT_RE.sub("", text) | |
| return text.rstrip() | |
| # --------------------------------------------------------------------- | |
| # Non-streaming query loop | |
| # --------------------------------------------------------------------- | |
| def generate_with_query( | |
| self, | |
| prompt: str, | |
| query_fn: Callable[[str], str], | |
| max_tool_rounds: int = 4, | |
| min_tool_rounds: int = 2, | |
| max_new_tokens: int = 512, | |
| temperature: float = 0.2, | |
| top_p: float = 0.9, | |
| system_prompt: Optional[str] = None, | |
| ) -> str: | |
| raw_prompt = self._build_raw_prompt(prompt, system_prompt=system_prompt) | |
| transcript = "" | |
| tool_rounds = 0 | |
| retrieval_started = False | |
| force_answer = False | |
| illegal_result_count = 0 | |
| min_tool_rounds = max(0, int(min_tool_rounds)) | |
| max_tool_rounds = max(min_tool_rounds, int(max_tool_rounds)) | |
| max_iterations = max_tool_rounds + 8 | |
| def inject_query_result(query: str) -> str: | |
| nonlocal tool_rounds, retrieval_started | |
| tool_rounds += 1 | |
| retrieval_started = True | |
| result = self.format_query_result(query, query_fn(query)) | |
| return ( | |
| "\n" | |
| f"<result>{result}</result>\n" | |
| "<think>\n" | |
| ) | |
| for _ in range(max_iterations): | |
| need_more_retrieval = retrieval_started and tool_rounds < min_tool_rounds | |
| stop_strings = token_protocol.query_generation_stop_strings( | |
| need_more_retrieval=need_more_retrieval, | |
| ) | |
| if self.has_open_tag(raw_prompt, "think"): | |
| stop_strings = list(dict.fromkeys([ | |
| "</think>", | |
| "<answer>", | |
| "<result>", | |
| "</result>", | |
| *stop_strings, | |
| ])) | |
| generated = self._generate_raw( | |
| raw_prompt=raw_prompt, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stop_strings=stop_strings, | |
| ) | |
| generated, had_illegal_result = self.strip_generated_result_blocks(generated) | |
| generated, terminal_kind = self._slice_to_first_terminal(generated) | |
| # Query wins immediately after </query>. | |
| if terminal_kind == "query": | |
| query = self.extract_query(generated) or "" | |
| clean_query = self.sanitize_query_text(query) | |
| if not clean_query or self.is_bad_meta_query(clean_query): | |
| cleaned = generated.split("<query>", 1)[0].rstrip() | |
| raw_prompt += cleaned | |
| transcript += cleaned | |
| correction = ( | |
| "\n[Runtime instruction: The query was malformed or not a real lookup. " | |
| "Emit exactly one concrete <query>...</query> containing the subject and missing fact.]\n" | |
| ) | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| if force_answer or tool_rounds >= max_tool_rounds: | |
| cleaned = generated.split("<query>", 1)[0].rstrip() | |
| raw_prompt += cleaned | |
| transcript += cleaned | |
| injected = self.retrieval_exhausted_answer_instruction(max_tool_rounds) | |
| raw_prompt += injected | |
| transcript += injected | |
| force_answer = True | |
| continue | |
| raw_prompt += generated | |
| transcript += generated | |
| injected = inject_query_result(clean_query) | |
| raw_prompt += injected | |
| transcript += injected | |
| continue | |
| # Completed answer. | |
| if terminal_kind == "answer": | |
| if self.has_bad_meta_answer(generated) or self.declares_retrieval_intent(generated): | |
| cleaned = self.strip_answer_attempts(generated) | |
| raw_prompt += cleaned | |
| transcript += cleaned | |
| correction = self.force_query_correction() | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| if retrieval_started and tool_rounds < min_tool_rounds: | |
| cleaned = self.strip_answer_attempts(generated) | |
| raw_prompt += cleaned | |
| transcript += cleaned | |
| correction = self.minimum_retrieval_correction( | |
| tool_rounds=tool_rounds, | |
| min_tool_rounds=min_tool_rounds, | |
| ) | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| raw_prompt += generated | |
| transcript += generated | |
| break | |
| # Open query / malformed query. | |
| if self.has_malformed_query(generated): | |
| raw_query = self.extract_open_query(generated) or "" | |
| clean_query = self.sanitize_query_text(raw_query) | |
| raw_prompt += re.sub(r"(?is)<query>[\s\S]*$", "", generated).rstrip() | |
| transcript += re.sub(r"(?is)<query>[\s\S]*$", "", generated).rstrip() | |
| if clean_query and not self.is_bad_meta_query(clean_query): | |
| normalized = f"<query>{clean_query}</query>" | |
| raw_prompt += normalized | |
| transcript += normalized | |
| if not force_answer and tool_rounds < max_tool_rounds: | |
| injected = inject_query_result(clean_query) | |
| raw_prompt += injected | |
| transcript += injected | |
| continue | |
| correction = self.malformed_query_correction() | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| if self.has_fake_retrieval_claim(generated, tool_rounds): | |
| generated = self.strip_fake_retrieval_claim(generated) | |
| raw_prompt += generated | |
| transcript += generated | |
| correction = self.fake_retrieval_claim_correction() | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| if self.has_generated_result_claim(generated, tool_rounds): | |
| generated = self.strip_generated_result_claim(generated) | |
| raw_prompt += generated | |
| transcript += generated | |
| correction = self.generated_result_claim_correction() | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| if self.declares_retrieval_intent(generated): | |
| generated = self.strip_answer_attempts(generated) | |
| raw_prompt += generated | |
| transcript += generated | |
| correction = self.force_query_correction() | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| if self.has_open_tag(raw_prompt, "think") and "<answer>" in generated: | |
| before_answer = generated.split("<answer>", 1)[0].rstrip() | |
| raw_prompt += before_answer | |
| transcript += before_answer | |
| raw_prompt += "\n</think>\n" | |
| transcript += "\n</think>\n" | |
| raw_prompt += self.protocol_correction(force_answer=True) | |
| transcript += self.protocol_correction(force_answer=True) | |
| continue | |
| if had_illegal_result: | |
| illegal_result_count += 1 | |
| force_answer = force_answer or illegal_result_count >= 2 | |
| correction = self.protocol_correction(force_answer=force_answer) | |
| raw_prompt += correction | |
| transcript += correction | |
| continue | |
| raw_prompt += generated | |
| transcript += generated | |
| if generated.strip().endswith("</think>"): | |
| if retrieval_started and tool_rounds >= min_tool_rounds: | |
| instruction = ( | |
| "\n[Runtime instruction: Retrieved evidence is available. " | |
| "Produce the final <answer>...</answer> now.]\n" | |
| ) | |
| raw_prompt += instruction | |
| transcript += instruction | |
| continue | |
| continue | |
| if retrieval_started and tool_rounds >= min_tool_rounds: | |
| instruction = ( | |
| "\n[Runtime instruction: Retrieved evidence is available. " | |
| "Produce the final <answer>...</answer> now.]\n" | |
| ) | |
| raw_prompt += instruction | |
| transcript += instruction | |
| continue | |
| if not retrieval_started: | |
| break | |
| else: | |
| correction = ( | |
| "\n[Runtime instruction: The retrieval loop reached its safety limit. " | |
| "Do not request more retrieval. Emit a final <answer>...</answer> now, " | |
| "using only the evidence already shown.]\n" | |
| ) | |
| raw_prompt += correction | |
| transcript += correction | |
| generated = self._generate_raw( | |
| raw_prompt=raw_prompt, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stop_strings=["</answer>", "<result>", "</result>", "</query>", "<query>"], | |
| ) | |
| generated, _ = self._slice_to_first_terminal(generated) | |
| generated = generated.split("<query>", 1)[0] | |
| generated = generated.split("<result>", 1)[0] | |
| raw_prompt += generated | |
| transcript += generated | |
| return transcript | |
| # --------------------------------------------------------------------- | |
| # Streaming query loop | |
| # --------------------------------------------------------------------- | |
| def stream_with_query( | |
| self, | |
| prompt: str, | |
| query_fn: Callable[[str], str], | |
| max_tool_rounds: int = 3, | |
| min_tool_rounds: int = 2, | |
| max_new_tokens: int = 512, | |
| temperature: float = 0.2, | |
| top_p: float = 0.9, | |
| show_injected_result: bool = True, | |
| thinking_token_limit: Optional[int] = None, | |
| system_prompt: Optional[str] = None, | |
| ) -> Generator[str, None, None]: | |
| raw_prompt = self._build_raw_prompt(prompt, system_prompt=system_prompt) | |
| min_tool_rounds = max(0, int(min_tool_rounds)) | |
| max_tool_rounds = max(min_tool_rounds, int(max_tool_rounds)) | |
| tool_rounds = 0 | |
| retrieval_started = False | |
| force_answer = False | |
| final_answer_requested = False | |
| illegal_result_count = 0 | |
| last_query: str | None = None | |
| max_iterations = max_tool_rounds + 8 | |
| def close_open_think_if_needed(text: str) -> str: | |
| if self.has_open_tag(text, "think"): | |
| return "\n</think>\n" | |
| if not text.endswith("\n"): | |
| return "\n" | |
| return "" | |
| def visible_result_block(result: str) -> str: | |
| return f"<result>{result}</result>\n" | |
| def inject_results_for_query(query: str, minimum_fill: bool) -> tuple[str, str]: | |
| nonlocal tool_rounds | |
| prompt_parts = [] | |
| visible_parts = [] | |
| needed = max(1, min_tool_rounds - tool_rounds) if minimum_fill else 1 | |
| can_fetch = max(0, max_tool_rounds - tool_rounds) | |
| fetch_count = min(needed, can_fetch) | |
| for _ in range(fetch_count): | |
| tool_rounds += 1 | |
| result = self.format_query_result(query, query_fn(query)) | |
| block = visible_result_block(result) | |
| prompt_parts.append(block) | |
| visible_parts.append(block) | |
| return "".join(prompt_parts), "".join(visible_parts) | |
| def request_final_answer_instruction() -> str: | |
| return ( | |
| "\n[Runtime instruction: Retrieved evidence is available. " | |
| "Do not request more retrieval unless a genuinely different lookup is required. " | |
| "Continue with a normal reasoning block if needed, then produce the final " | |
| "<answer>...</answer>.]\n" | |
| ) | |
| for _ in range(max_iterations): | |
| generated = "" | |
| thinking_state: dict = {} | |
| suppressor = ResultTagSuppressor() | |
| query_guard = StartTagSuppressor(["<query>"]) if force_answer else None | |
| block_early_answer = retrieval_started and tool_rounds < min_tool_rounds | |
| answer_guard = EarlyAnswerSuppressor() if block_early_answer else None | |
| need_more_retrieval = retrieval_started and tool_rounds < min_tool_rounds | |
| if force_answer: | |
| # Answer-only mode. Stop as soon as the model tries to open another query. | |
| stop_strings = ["</answer>", "<query>", "<result>", "</result>"] | |
| else: | |
| stop_strings = token_protocol.query_generation_stop_strings( | |
| need_more_retrieval=need_more_retrieval, | |
| ) | |
| if self.has_open_tag(raw_prompt, "think"): | |
| stop_strings = list(dict.fromkeys([ | |
| "</think>", | |
| "<answer>", | |
| "<result>", | |
| "</result>", | |
| *stop_strings, | |
| ])) | |
| for chunk in self.stream_raw( | |
| raw_prompt=raw_prompt, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stop_strings=stop_strings, | |
| thinking_token_limit=thinking_token_limit, | |
| thinking_limit_state=thinking_state, | |
| ): | |
| filtered_chunk = suppressor.feed(chunk) | |
| if not filtered_chunk: | |
| continue | |
| if query_guard is not None: | |
| filtered_chunk = query_guard.feed(filtered_chunk) | |
| if not filtered_chunk: | |
| continue | |
| if answer_guard is not None: | |
| visible_chunk = answer_guard.feed(filtered_chunk) | |
| else: | |
| visible_chunk = filtered_chunk | |
| if visible_chunk: | |
| generated += visible_chunk | |
| yield visible_chunk | |
| filtered_tail = suppressor.flush() | |
| if filtered_tail: | |
| if query_guard is not None: | |
| filtered_tail = query_guard.feed(filtered_tail) | |
| if answer_guard is not None: | |
| visible_tail = answer_guard.feed(filtered_tail) | |
| else: | |
| visible_tail = filtered_tail | |
| if visible_tail: | |
| generated += visible_tail | |
| yield visible_tail | |
| if query_guard is not None: | |
| visible_tail = query_guard.flush() | |
| if visible_tail: | |
| generated += visible_tail | |
| yield visible_tail | |
| if answer_guard is not None: | |
| visible_tail = answer_guard.flush() | |
| if visible_tail: | |
| generated += visible_tail | |
| yield visible_tail | |
| think_limit_hit = bool(thinking_state.get("triggered")) | |
| if think_limit_hit and self.has_open_tag(raw_prompt + generated, "think"): | |
| close_think = "\n</think>\n" | |
| generated += close_think | |
| yield close_think | |
| generated, had_illegal_result = self.strip_generated_result_blocks(generated) | |
| generated, terminal_kind = self._slice_to_first_terminal(generated) | |
| if force_answer and (("<query>" in generated) or (query_guard is not None and query_guard.blocked)): | |
| before_query = generated.split("<query>", 1)[0].rstrip() | |
| if before_query: | |
| raw_prompt += before_query | |
| raw_prompt += close_open_think_if_needed(raw_prompt) | |
| raw_prompt += self.retrieval_exhausted_answer_instruction(max_tool_rounds) | |
| final_answer_requested = True | |
| continue | |
| # ------------------------------------------------------------- | |
| # CRITICAL FIX: | |
| # If </query> is the first completed terminal block, | |
| # start retrieval immediately. Do not inspect later <answer> junk. | |
| # ------------------------------------------------------------- | |
| if terminal_kind == "query": | |
| query = self.extract_query(generated) or "" | |
| clean_query = self.sanitize_query_text(query) | |
| if force_answer or tool_rounds >= max_tool_rounds: | |
| before_query = generated.split("<query>", 1)[0].rstrip() | |
| if before_query: | |
| raw_prompt += before_query | |
| raw_prompt += close_open_think_if_needed(raw_prompt) | |
| raw_prompt += self.retrieval_exhausted_answer_instruction(max_tool_rounds) | |
| force_answer = True | |
| final_answer_requested = True | |
| continue | |
| raw_prompt += generated | |
| if not clean_query or self.is_bad_meta_query(clean_query): | |
| raw_prompt += ( | |
| "\n[Runtime instruction: The query was malformed or not a real lookup. " | |
| "Emit exactly one concrete <query>...</query> containing the subject and missing fact.]\n" | |
| ) | |
| continue | |
| retrieval_started = True | |
| last_query = clean_query | |
| close_think = close_open_think_if_needed(raw_prompt) | |
| prompt_injected, visible_injected = inject_results_for_query( | |
| clean_query, | |
| minimum_fill=(tool_rounds < min_tool_rounds), | |
| ) | |
| prompt_injected = close_think + prompt_injected + "<think>\n" | |
| visible_injected = close_think + visible_injected + "<think>\n" | |
| raw_prompt += prompt_injected | |
| if show_injected_result and visible_injected.strip(): | |
| yield visible_injected | |
| continue | |
| # Completed answer. | |
| if terminal_kind == "answer": | |
| if self.has_bad_meta_answer(generated) or self.declares_retrieval_intent(generated): | |
| cleaned = self.strip_answer_attempts(generated) | |
| raw_prompt += cleaned | |
| raw_prompt += self.force_query_correction() | |
| continue | |
| if retrieval_started and tool_rounds < min_tool_rounds: | |
| cleaned = self.strip_answer_attempts(generated) | |
| raw_prompt += cleaned | |
| if last_query and tool_rounds < max_tool_rounds: | |
| prompt_injected, visible_injected = inject_results_for_query( | |
| last_query, | |
| minimum_fill=True, | |
| ) | |
| raw_prompt += prompt_injected | |
| if show_injected_result and visible_injected: | |
| yield visible_injected | |
| continue | |
| raw_prompt += self.minimum_retrieval_correction( | |
| tool_rounds=tool_rounds, | |
| min_tool_rounds=min_tool_rounds, | |
| ) | |
| continue | |
| raw_prompt += generated | |
| break | |
| # Open query / malformed query. | |
| if self.has_malformed_query(generated): | |
| raw_query = self.extract_open_query(generated) or "" | |
| clean_query = self.sanitize_query_text(raw_query) | |
| generated_without_open_query = re.sub( | |
| r"(?is)<query>[\s\S]*$", | |
| "", | |
| generated, | |
| ).rstrip() | |
| raw_prompt += generated_without_open_query | |
| if clean_query and not self.is_bad_meta_query(clean_query): | |
| normalized = f"<query>{clean_query}</query>" | |
| raw_prompt += normalized | |
| yield normalized | |
| if not force_answer and tool_rounds < max_tool_rounds: | |
| retrieval_started = True | |
| last_query = clean_query | |
| close_think = close_open_think_if_needed(raw_prompt) | |
| prompt_injected, visible_injected = inject_results_for_query( | |
| clean_query, | |
| minimum_fill=(tool_rounds < min_tool_rounds), | |
| ) | |
| prompt_injected = close_think + prompt_injected + "<think>\n" | |
| visible_injected = close_think + visible_injected + "<think>\n" | |
| raw_prompt += prompt_injected | |
| if show_injected_result and visible_injected.strip(): | |
| yield visible_injected | |
| continue | |
| raw_prompt += self.malformed_query_correction() | |
| continue | |
| # Fake retrieval claims before runtime provided results. | |
| if self.has_fake_retrieval_claim(generated, tool_rounds): | |
| generated = self.strip_fake_retrieval_claim(generated) | |
| raw_prompt += generated | |
| raw_prompt += self.fake_retrieval_claim_correction() | |
| continue | |
| if self.has_generated_result_claim(generated, tool_rounds): | |
| generated = self.strip_generated_result_claim(generated) | |
| raw_prompt += generated | |
| raw_prompt += self.generated_result_claim_correction() | |
| continue | |
| # Retrieval intent without a valid query. | |
| if self.declares_retrieval_intent(generated): | |
| cleaned = self.strip_answer_attempts(generated) | |
| raw_prompt += cleaned | |
| raw_prompt += self.force_query_correction() | |
| continue | |
| # If answer leaked inside a runtime-opened think block, repair it. | |
| if self.has_open_tag(raw_prompt, "think") and "<answer>" in generated: | |
| before_answer = generated.split("<answer>", 1)[0].rstrip() | |
| raw_prompt += before_answer | |
| raw_prompt += "\n</think>\n" | |
| raw_prompt += self.retrieval_exhausted_answer_instruction(max_tool_rounds) | |
| final_answer_requested = True | |
| continue | |
| if had_illegal_result or suppressor.suppressed: | |
| illegal_result_count += 1 | |
| force_answer = force_answer or illegal_result_count >= 2 | |
| raw_prompt += self.protocol_correction(force_answer=force_answer) | |
| continue | |
| if think_limit_hit: | |
| raw_prompt += generated | |
| raw_prompt += ( | |
| "\n[Runtime instruction: Thinking budget reached. " | |
| "Produce the final <answer>...</answer> now.]\n" | |
| ) | |
| final_answer_requested = True | |
| continue | |
| raw_prompt += generated | |
| if generated.strip().endswith("</think>"): | |
| if retrieval_started and tool_rounds >= min_tool_rounds: | |
| raw_prompt += request_final_answer_instruction() | |
| final_answer_requested = True | |
| continue | |
| # Before retrieval starts, do not force answer. | |
| continue | |
| if retrieval_started and tool_rounds >= min_tool_rounds and not final_answer_requested: | |
| if force_answer or tool_rounds >= max_tool_rounds: | |
| raw_prompt += self.retrieval_exhausted_answer_instruction(max_tool_rounds) | |
| else: | |
| raw_prompt += request_final_answer_instruction() | |
| final_answer_requested = True | |
| continue | |
| if not retrieval_started: | |
| break | |
| if not final_answer_requested: | |
| raw_prompt += request_final_answer_instruction() | |
| final_answer_requested = True | |
| continue | |
| # Final fallback pass. | |
| fallback_guard = StartTagSuppressor(["<query>", "<result>"]) | |
| for chunk in self.stream_raw( | |
| raw_prompt=raw_prompt, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stop_strings=["</answer>", "<query>", "<result>", "</result>"], | |
| ): | |
| visible_chunk = fallback_guard.feed(chunk) | |
| if visible_chunk: | |
| yield visible_chunk | |
| visible_tail = fallback_guard.flush() | |
| if visible_tail: | |
| yield visible_tail | |
| break | |
| else: | |
| raw_prompt += request_final_answer_instruction() | |
| fallback_guard = StartTagSuppressor(["<query>", "<result>"]) | |
| for chunk in self.stream_raw( | |
| raw_prompt=raw_prompt, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stop_strings=["</answer>", "<query>", "<result>", "</result>", "</query>"], | |
| ): | |
| visible_chunk = fallback_guard.feed(chunk) | |
| if visible_chunk: | |
| yield visible_chunk | |
| visible_tail = fallback_guard.flush() | |
| if visible_tail: | |
| yield visible_tail | |