-
Notifications
You must be signed in to change notification settings - Fork 1
perf: harden non-inferable principle implementation #195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
2e46051
8943bef
230b8a4
c6801d0
fd6db3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| """Org policy quality validation heuristics. | ||
|
|
||
| Applies lightweight checks to detect policies that likely violate the | ||
| non-inferable principle — e.g. policies that describe codebase structure | ||
| (inferable by reading the repo) rather than actionable constraints. | ||
|
|
||
| Examples of **good** policies (non-inferable, actionable): | ||
|
|
||
| - ``"All API responses must include a correlation_id header"`` | ||
| - ``"Never store PII in memory without encryption"`` | ||
| - ``"Escalate budget overruns above $5 to the CFO"`` | ||
|
|
||
| Examples of **bad** policies (inferable or non-actionable): | ||
|
|
||
| - ``"The project uses Python 3.14"`` — discoverable from pyproject.toml | ||
| - ``"src/api/ contains REST controllers"`` — discoverable by reading code | ||
| - ``"x"`` — too short to be actionable | ||
| """ | ||
|
|
||
| import re | ||
| from typing import Final, Literal | ||
|
|
||
| from pydantic import BaseModel, ConfigDict, Field | ||
|
|
||
| from ai_company.observability import get_logger | ||
| from ai_company.observability.events.prompt import PROMPT_POLICY_QUALITY_ISSUE | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
||
| _MIN_POLICY_LENGTH: Final[int] = 10 | ||
| _MAX_POLICY_LENGTH: Final[int] = 500 | ||
|
|
||
| # Patterns that suggest inferable codebase context rather than a policy. | ||
| _CODE_PATTERNS: Final[tuple[re.Pattern[str], ...]] = ( | ||
| re.compile(r"(?:src|tests|lib|app)/[\w/]+\.py"), # file paths | ||
| re.compile(r"\bfrom\s+\w+\s+import\b"), # Python imports | ||
| re.compile(r"\bimport\s+\w+"), # bare imports | ||
| re.compile(r"\bdef\s+\w+\s*\("), # function definitions | ||
| re.compile(r"\bclass\s+\w+[\s:(]"), # class definitions | ||
| ) | ||
|
Comment on lines
+38
to
+44
|
||
|
|
||
| # Action verbs that signal an actionable constraint. | ||
| _ACTION_VERBS: Final[frozenset[str]] = frozenset( | ||
| { | ||
| "must", | ||
| "should", | ||
| "always", | ||
| "never", | ||
| "require", | ||
| "ensure", | ||
| "prohibit", | ||
| "enforce", | ||
| "restrict", | ||
| "mandate", | ||
| "avoid", | ||
| "prefer", | ||
| "escalate", | ||
| "approve", | ||
| "deny", | ||
| "reject", | ||
| "validate", | ||
| "verify", | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| class PolicyQualityIssue(BaseModel): | ||
| """A quality issue found in an org policy. | ||
|
|
||
| Attributes: | ||
| policy: The policy text that triggered the issue. | ||
| issue: Human-readable description of the problem. | ||
| severity: ``"warning"`` for advisory, ``"error"`` for likely invalid. | ||
| """ | ||
|
|
||
| model_config = ConfigDict(frozen=True) | ||
|
|
||
| policy: str = Field(description="The policy text that triggered the issue") | ||
| issue: str = Field(description="Human-readable description of the problem") | ||
| severity: Literal["warning", "error"] = Field( | ||
| description="Issue severity (``'error'`` reserved for future stricter checks)", | ||
| ) | ||
|
|
||
|
|
||
| def validate_policy_quality( | ||
| policies: tuple[str, ...], | ||
| ) -> tuple[PolicyQualityIssue, ...]: | ||
| """Check org policies for non-inferable principle violations. | ||
|
|
||
| Applies heuristic checks — results are advisory and never block | ||
| prompt construction. | ||
|
|
||
| Args: | ||
| policies: Org policy texts to validate. | ||
|
|
||
| Returns: | ||
| Tuple of quality issues found (empty if all policies pass). | ||
| """ | ||
| logger.debug( | ||
| PROMPT_POLICY_QUALITY_ISSUE, | ||
| phase="start", | ||
| policy_count=len(policies), | ||
| ) | ||
|
Comment on lines
+107
to
+110
|
||
| issues: list[PolicyQualityIssue] = [] | ||
| for policy in policies: | ||
| issues.extend(_check_single_policy(policy)) | ||
|
|
||
| for issue in issues: | ||
| logger.warning( | ||
| PROMPT_POLICY_QUALITY_ISSUE, | ||
| policy=issue.policy[:80], | ||
| issue=issue.issue, | ||
| severity=issue.severity, | ||
| ) | ||
|
coderabbitai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| return tuple(issues) | ||
|
|
||
|
|
||
| _ACTION_VERB_RE: re.Pattern[str] = re.compile( | ||
| r"\b(?:" + "|".join(_ACTION_VERBS) + r")\b", | ||
| ) | ||
|
Comment on lines
+126
to
+128
|
||
|
|
||
|
|
||
| def _check_single_policy(policy: str) -> list[PolicyQualityIssue]: | ||
| """Run all heuristic checks on a single policy string. | ||
|
|
||
| Args: | ||
| policy: The policy text to validate. | ||
|
|
||
| Returns: | ||
| List of quality issues found (empty if the policy passes all checks). | ||
| """ | ||
| found: list[PolicyQualityIssue] = [] | ||
|
|
||
| if len(policy) < _MIN_POLICY_LENGTH: | ||
| found.append( | ||
| PolicyQualityIssue( | ||
| policy=policy, | ||
| issue=( | ||
| f"Too short ({len(policy)} chars) — likely not an actionable policy" | ||
| ), | ||
| severity="warning", | ||
| ), | ||
| ) | ||
|
|
||
| if len(policy) > _MAX_POLICY_LENGTH: | ||
| found.append( | ||
| PolicyQualityIssue( | ||
| policy=policy, | ||
| issue=( | ||
| f"Too long ({len(policy)} chars) — " | ||
| f"may contain inferable context rather than a policy" | ||
| ), | ||
| severity="warning", | ||
| ), | ||
| ) | ||
|
|
||
| for pattern in _CODE_PATTERNS: | ||
| if pattern.search(policy): | ||
| found.append( | ||
| PolicyQualityIssue( | ||
| policy=policy, | ||
| issue=( | ||
| "Contains code patterns (file paths, imports, or " | ||
| "definitions) — likely inferable from the codebase" | ||
| ), | ||
| severity="warning", | ||
| ), | ||
| ) | ||
| break # One code-pattern match is sufficient. | ||
|
|
||
| policy_lower = policy.lower() | ||
| if not _ACTION_VERB_RE.search(policy_lower): | ||
| found.append( | ||
| PolicyQualityIssue( | ||
| policy=policy, | ||
| issue=( | ||
| "Missing action verbs (must, should, always, never, " | ||
| "etc.) — may not be an actionable policy" | ||
| ), | ||
| severity="warning", | ||
| ), | ||
| ) | ||
|
|
||
| return found | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Split This helper is already over the 50-line limit and now mixes length, code-pattern, and action-verb heuristics. Extract those checks into small helpers so future rule changes stay isolated and easier to test. Refactor sketch def _check_single_policy(policy: str) -> list[PolicyQualityIssue]:
- found: list[PolicyQualityIssue] = []
-
- if len(policy) < _MIN_POLICY_LENGTH:
- found.append(...)
-
- if len(policy) > _MAX_POLICY_LENGTH:
- found.append(...)
-
- for pattern in _CODE_PATTERNS:
- if pattern.search(policy):
- found.append(...)
- break
-
- policy_lower = policy.lower()
- if not _ACTION_VERB_RE.search(policy_lower):
- found.append(...)
-
- return found
+ return [
+ *_check_policy_length(policy),
+ *_check_code_patterns(policy),
+ *_check_action_verbs(policy),
+ ]As per coding guidelines "Keep functions under 50 lines and files under 800 lines". 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prompt_tokensis populated fromresult.system_prompt.estimated_tokens, but the system prompt message is included inctx.conversationand is resent on every provider call. Sincetokens_per_taskaggregates tokens across all turns,prompt_token_ratiowill be underestimated for multi-turn runs. Consider either (a) makingprompt_tokensrepresent total prompt tokens across the run (e.g., estimate ×result.total_turns), or (b) renaming the field to clarify it's per-call and adjusting the ratio/warning accordingly.