Repository navigation
Conversation
Claude-Session-Id: 899e5f7a-9099-4d8f-b96d-79291e63b5b4
Claude-Session-Id: 899e5f7a-9099-4d8f-b96d-79291e63b5b4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Several hook and CI classifiers need a yes/no, a label, or a score in well under a second, and today each one prompts a chat model and parses JSON out of its reply. TypeSafe's Jev and OpenAI's Decisions API answer those questions directly with probabilities in about 120 ms. This adds one client for both, so capt-hook hooks and the per-site evals can ask a question set once and run it on either provider.
decideanddecide_synctake a state (text or JSON) and an ordered map ofBinary,Label, andScorequestions, and return aDecisionwith one typed answer per id in declared order. ALabelorScoreanswer carries its probabilities keyed by option, andRefusedstands in for a question OpenAI declines while it answers the rest.JEVpinsjev-1.13.0andOPENAIusesgpt-6-luna.Following the core-first rule, both wire mappings and the retry policy live in the Rust core as two new ops,
decide_plananddecide_resolve, so the Go and Rust hosts replay the same 21 golden vectors. Two of those vectors are recorded responses, one from each API.The Jev mapping reorders Jev's probability maps, which come back in arbitrary order, and OpenAI's refusals map to
Refused. The core retries 408, 429, 5xx, 529, and a lost connection, backing off from 0.5s to a 5s cap and honoringretry-afterandretry-after-ms. The Python host retries only while the next attempt still fits inside the caller'stimeout, then raisesTimeoutError. A 401 or 422 raisesDecideErroron the first try. The deadline starts before the Keychain read, and an answer that arrives after it raisesTimeoutErrorinstead of returning late. Each process keeps one keep-alivehttpxclient, anddeciderunsdecide_syncon a worker thread so it shares that pool without tying a client to an event loop.Keys come from
TYPESAFE_API_KEYorOPENAI_API_KEY. On macOS, an unset variable falls through to a Keychain item read at call time, andspawnllm key set jev|openaiwrites that item from stdin throughsecurity -i, so the key never appears in a process listing. I evaluated the vendor SDKs,typesafe_sdkandopenai. They add two dependencies with different retry policies and answer shapes, and Jev ships no Go SDK, so the client speaks HTTP directly.On this Mac I stored both keys in the Keychain from AWS Systems Manager and ran three-question calls live. Six warm Jev calls took 121 to 133 ms end to end, with about 105 ms of that on the wire; OpenAI took 118 to 160 ms. The async path returned the same answers.
A sol finder pass flagged four issues. Three are fixed in the second commit: the deadline overrun from per-phase httpx timeouts, the Keychain read outside the deadline, and async clients that pinned closed event loops. The fourth asked to honor an HTTP-date
Retry-After. I left it out because both providers send delta seconds, and a date header falls back to the capped backoff, which still stops at the deadline.