""" title: Quantitative Question Nudge author: owuitools version: 0.2.0 description: Raises tool-use salience on measurement-bearing questions that are phrased casually. """ # WHY THIS EXISTS # # The system prompt already says that reasoning tasks need tools and that # ruling a cause out is itself a claim. It does not fire on questions that # are phrased conversationally. Measured case: # # "im logging vibration off a motor at 500Hz, motor runs 3600 rpm but # the biggest fft peak is at 40Hz. is my sensor broken?" # # Zero tool calls, and the answer stated "aliasing cannot move a 60 Hz peak # to 40 Hz - so 40 Hz is a REAL vibration". True of the fundamental and # wrong as an elimination: the 9th harmonic at 540 Hz folds to exactly # 40.0 Hz at that sample rate. That sends someone hunting a phantom # resonance instead of fitting an anti-alias filter. Adding a paragraph to # the system prompt did not change it - which is this suite's most repeated # finding: static prompt text loses to the shape of the conversation. # # An inlet note is a different lever. It arrives as the most recent thing # in the conversation rather than as line 40 of a long preamble, so it is # read as part of the current task. It is invisible to the user: inlet # rewrites what goes to the model, not what is displayed. # # Deliberately narrow. It fires only when a message carries an actual # measurement - a number with a unit - because that is what distinguishes # "is my sensor broken" from "is Postgres a good choice". A nudge on every # message would be one more paragraph the model learns to skim. # # WHY IT NAMES THE TOOLS # # The first version said "check it with a tool" and left the choice open. # On a compound-interest question the model duly reached for a tool - and # reached for web_search, to look up a formula it already knew, then did # the actual arithmetic in its head and got it wrong. Tool-seeking without # tool-selection. The note now says which tool does what, and says out loud # that searching for a formula you know is not the point. import re from pydantic import BaseModel, Field # A number bound to a unit. Bare numbers are excluded on purpose: "give me # 3 options" is not a measurement. Currency and durations are in because # "$4,000 at 5% for 10 years" is as quantitative as anything in volts, and # the arithmetic is exactly where in-head answers go wrong. _MEASUREMENT = re.compile( r"[$£€]\s?\d" r"|\b\d+(?:\.\d+)?\s?" r"(?:hz|khz|mhz|ghz|rpm|v|kv|mv|a|ma|w|kw|mw|va|kva|ohm|ohms|" r"db|dbm|s|ms|us|ns|m|mm|cm|km|kg|g|n|nm|pa|kpa|bar|psi|" r"c|f|k|%|gb|mb|kb|tb|bps|kbps|mbps|gbps|awg|bit|bits|" r"years?|yrs?|months?|weeks?|days?)\b", re.I, ) _NOTE = ( "Reminder for this question: it contains measurements, so it is a technical question " "however casually it is worded. Before asserting any number, mechanism or standard, check " "it with a tool: the calculators (ohms_law_power, component_calc, wiring_calc, pcb_calc, " "subnet_calc) for what they cover, matlab_run for any other arithmetic - compounding, unit " "conversions, statistics, counting - and web_search then fetch_page for facts and " "standards, not for formulas you already know. In particular, if you are about to rule a " "cause OUT, enumerate the variants of that cause and test the ones you have not considered " "- a correct elimination of the obvious version and a missed elimination of a subtler " "version look identical in the answer, and send the user to fix the wrong thing." ) class Filter: class Valves(BaseModel): enabled: bool = Field( default=True, description="Turn the nudge off without deleting the filter.", ) min_chars: int = Field( default=40, description="Skip very short messages. 'what is 3 dB' needs no reminder.", ) def __init__(self): self.valves = self.Valves() async def inlet(self, body: dict, __user__=None) -> dict: if not self.valves.enabled: return body try: messages = body.get("messages") or [] if not messages: return body last_user = None for message in reversed(messages): if message.get("role") == "user": last_user = message break if last_user is None: return body text = last_user.get("content") if not isinstance(text, str) or len(text) < self.valves.min_chars: return body if not _MEASUREMENT.search(text): return body # Appended AFTER the user's turn, not before it. Inserted # before, this was read as more preamble and changed nothing; # the last message in the list is the one that gets weight. messages.append({"role": "system", "content": _NOTE}) body["messages"] = messages except Exception: return body return body