grip_hook.providers¶
grip_hook.providers ¶
LLM providers.
A provider turns a diff into questions and answers into grades. Add a new one by
implementing :class:grip_hook.providers.base.Provider and registering it in
:data:REGISTRY.
REGISTRY
module-attribute
¶
REGISTRY: dict[str, ProviderFactory] = {'anthropic': _anthropic, 'openai': _openai, 'ollama': _openai, 'claude-code': _claude_code, 'claude': _claude_code, 'codex': _codex, 'gemini': _gemini, 'fake': _fake}
Provider name to factory.
ollama is openai with a local default URL; claude is an alias of claude-code.
The agent providers shell out to a CLI that is already installed and signed in.
Provider ¶
Bases: Protocol
Something that can write quiz questions and grade answers.
Source code in src/grip_hook/providers/base.py
generate_questions ¶
generate_board ¶
Draw the whiteboard for diff; problems explain why a first try was rejected.
grade ¶
appeal ¶
appeal(diff: Diff, question: Question, answer: Answer, grade: QuestionGrade, *, justification: str, difficulty: Difficulty) -> QuestionGrade
Grade one answer again, given the developer's case against grade.
Source code in src/grip_hook/providers/base.py
get_provider ¶
Instantiate the provider named by cfg.provider (auto resolved first).
Source code in src/grip_hook/providers/__init__.py
grip_hook.providers.base ¶
Provider interface.
Provider ¶
Bases: Protocol
Something that can write quiz questions and grade answers.
Source code in src/grip_hook/providers/base.py
generate_questions ¶
generate_board ¶
Draw the whiteboard for diff; problems explain why a first try was rejected.
grade ¶
appeal ¶
appeal(diff: Diff, question: Question, answer: Answer, grade: QuestionGrade, *, justification: str, difficulty: Difficulty) -> QuestionGrade
Grade one answer again, given the developer's case against grade.
Source code in src/grip_hook/providers/base.py
CallLog ¶
Keeps the :class:ModelCall of every request a provider makes, oldest first.
Providers that time their requests inherit it; :func:calls_of reads it.
Source code in src/grip_hook/providers/base.py
call_kind ¶
What a request for schema is for: questions, board, grade, appeal.
calls_of ¶
The requests provider made so far; empty for one that does not keep them.
token_count ¶
value when it is a usable token count (a non-negative int), else None.
missing_credentials ¶
What to do when provider has no key in key_env: every way out, concretely.
A coding agent installed on this machine is named, since it needs no key at all. The lines stay short enough not to wrap in an 80-column terminal.
Source code in src/grip_hook/providers/base.py
grip_hook.providers.anthropic ¶
Anthropic provider built on the official anthropic SDK.
AnthropicProvider ¶
Bases: CallLog
Ask Claude for questions and grades using structured outputs.
Source code in src/grip_hook/providers/anthropic.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |
generate_questions ¶
Write the question set for diff.
generate_board ¶
Draw the whiteboard for diff.
grade ¶
grade(diff: Diff, questions: list[Question], answers: list[Answer], difficulty: Difficulty) -> GradeSheet
Grade every answer in one call.
Source code in src/grip_hook/providers/anthropic.py
appeal ¶
appeal(diff: Diff, question: Question, answer: Answer, grade: QuestionGrade, *, justification: str, difficulty: Difficulty) -> QuestionGrade
Grade one answer again, given the developer's case against grade.
Source code in src/grip_hook/providers/anthropic.py
grip_hook.providers.openai_compat ¶
Provider for any OpenAI-compatible chat completions API (OpenAI, Ollama, vLLM, ...).
Implemented with the standard library so grip carries no extra dependency for it.
JSON output is requested through response_format with a JSON schema, which
Ollama and most OpenAI-compatible servers honour; the reply is validated with the
same pydantic models the Anthropic provider uses.
OpenAICompatibleProvider ¶
Bases: CallLog
Talk to {base_url}/chat/completions.
Source code in src/grip_hook/providers/openai_compat.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
generate_questions ¶
Write the question set for diff.
generate_board ¶
Draw the whiteboard for diff.
Source code in src/grip_hook/providers/openai_compat.py
grade ¶
grade(diff: Diff, questions: list[Question], answers: list[Answer], difficulty: Difficulty) -> GradeSheet
Grade every answer in one call.
Source code in src/grip_hook/providers/openai_compat.py
appeal ¶
appeal(diff: Diff, question: Question, answer: Answer, grade: QuestionGrade, *, justification: str, difficulty: Difficulty) -> QuestionGrade
Grade one answer again, given the developer's case against grade.
Source code in src/grip_hook/providers/openai_compat.py
grip_hook.providers.agents ¶
Providers that reuse a coding agent CLI already installed and signed in.
No API key is needed: each provider shells out to the agent in non-interactive mode, sends the same prompts the API providers use, and asks for JSON back.
claude-code:claude -pwith--json-schema(schema-enforced output).codex:codex execwith--output-schema(schema-enforced output).gemini:gemini --output-format json; the JSON is requested in the prompt and extracted from the reply.
Every agent runs with tools disabled where the CLI allows it, in an empty scratch directory, so it cannot read the repository, run commands, or pick up project-level instructions and hooks. It only ever sees the diff, its commit messages and the answers.
AgentCLIProvider ¶
Bases: CallLog
Common machinery: build a command, run it, validate the JSON it returns.
Requests are timed; the CLIs do not report token usage in a form grip relies on, so their calls carry none.
Source code in src/grip_hook/providers/agents.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
enforces_schema
class-attribute
¶
Whether the CLI constrains its output to the schema; otherwise it is asked for.
system_flag
class-attribute
¶
Flag that takes the system prompt; empty means prepend it to the user prompt.
command ¶
parse_output ¶
generate_questions ¶
Write the question set for diff.
generate_board ¶
Draw the whiteboard for diff.
grade ¶
grade(diff: Diff, questions: list[Question], answers: list[Answer], difficulty: Difficulty) -> GradeSheet
Grade every answer in one call.
Source code in src/grip_hook/providers/agents.py
appeal ¶
appeal(diff: Diff, question: Question, answer: Answer, grade: QuestionGrade, *, justification: str, difficulty: Difficulty) -> QuestionGrade
Grade one answer again, given the developer's case against grade.
Source code in src/grip_hook/providers/agents.py
ClaudeCodeProvider ¶
Bases: AgentCLIProvider
Claude Code (claude) in print mode with schema-enforced structured output.
Source code in src/grip_hook/providers/agents.py
command ¶
claude -p with no tools, no session persistence, JSON envelope output.
Source code in src/grip_hook/providers/agents.py
parse_output ¶
Read structured_output from the JSON envelope.
Source code in src/grip_hook/providers/agents.py
CodexProvider ¶
Bases: AgentCLIProvider
OpenAI Codex CLI (codex exec) with --output-schema.
Source code in src/grip_hook/providers/agents.py
command ¶
Non-interactive, read-only sandbox, last message written to a file.
Source code in src/grip_hook/providers/agents.py
parse_output ¶
Prefer the last-message file; fall back to scanning stdout.
Source code in src/grip_hook/providers/agents.py
GeminiProvider ¶
Bases: AgentCLIProvider
Gemini CLI (gemini) in non-interactive JSON mode.
Source code in src/grip_hook/providers/agents.py
command ¶
Prompt comes from stdin; the reply is a JSON envelope with a response key.
Source code in src/grip_hook/providers/agents.py
parse_output ¶
Unwrap the envelope, then extract the JSON object from the reply text.
Source code in src/grip_hook/providers/agents.py
extract_json ¶
Parse the first JSON object in text, tolerating code fences and chatter.
Source code in src/grip_hook/providers/agents.py
grip_hook.providers.fake ¶
A deterministic provider for tests, demos and grip quiz --provider fake.
Two modes:
- Default. Questions are derived from the changed file names; an answer scores full
marks when it mentions the word
becauseor is at least twenty characters long, half marks when it is non-empty, and zero otherwise. - Scripted. When
GRIP_FAKE_SCRIPTpoints to a JSON file, questions, grading rules and verdicts come from that file (see :func:load_script). This is how the demo recording and some tests get realistic, reproducible output without a network. An optionalboardin the script is the whiteboard; its pins are graded by the rules of the scripted question at the same position.
Without a script, the whiteboard is drawn from the changed files: an untrusted caller, one box per file in order, and the five whiteboard questions pinned across them.
An appeal (grip appeal) is upheld with full marks when the justification says
because, or with a scripted rule's score when one matches it and beats the first grade.
GRIP_FAKE_DELAY (seconds, float) makes each call sleep, so the spinner is visible.
Every call is timed and reports fixed token counts (:data:FAKE_USAGE), so the cost and
latency shown after a quiz can be tested.
FAKE_USAGE
module-attribute
¶
FAKE_USAGE: dict[str, tuple[int, int]] = {'questions': (1200, 400), 'board': (1500, 700), 'grade': (1400, 300), 'appeal': (900, 100)}
(input, output) tokens the fake provider reports for each kind of request.
GradeRule ¶
Bases: BaseModel
Award score when the lower-cased answer contains match.
Source code in src/grip_hook/providers/fake.py
DefaultGrade ¶
Bases: BaseModel
Grade used when no rule matches.
Source code in src/grip_hook/providers/fake.py
ScriptedQuestion ¶
Bases: Question
A question plus the rules used to grade answers to it.
Source code in src/grip_hook/providers/fake.py
grade ¶
First matching rule wins; rules are checked in order.
Source code in src/grip_hook/providers/fake.py
Verdict ¶
Script ¶
Bases: BaseModel
The scripted provider's data file.
Source code in src/grip_hook/providers/fake.py
verdict_for ¶
Pick the verdict with the highest min not above total.
Source code in src/grip_hook/providers/fake.py
FakeProvider ¶
Bases: CallLog
Offline stand-in for a real model.
Source code in src/grip_hook/providers/fake.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | |
generate_questions ¶
Produce five questions, scripted or templated over the changed files.
Source code in src/grip_hook/providers/fake.py
generate_board ¶
The scripted board, or one drawn from the changed files.
Source code in src/grip_hook/providers/fake.py
grade ¶
grade(diff: Diff, questions: list[Question], answers: list[Answer], difficulty: Difficulty) -> GradeSheet
Score answers deterministically, by script rules or by length.
Source code in src/grip_hook/providers/fake.py
appeal ¶
appeal(diff: Diff, question: Question, answer: Answer, grade: QuestionGrade, *, justification: str, difficulty: Difficulty) -> QuestionGrade
Uphold an appeal that says because (or matches a scripted rule), else keep it.
Source code in src/grip_hook/providers/fake.py
load_script ¶
Read and validate a script file.