Introduce config.ai_call: pluggable AI surface (messages-array)#122
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6c4cfbcde
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @ai_call = if ENV["OPENAI_API_KEY"].present? | ||
| ->(messages:) { CoPlan::AiProviders::OpenAi.call(messages: messages) } |
There was a problem hiding this comment.
Include credentials when auto-wiring OpenAI
When a deployment stores its OpenAI key in Rails credentials rather than ENV, this leaves ai_call nil because the default provider is only installed when OPENAI_API_KEY is present. The built-in OpenAi#api_key still supports Rails.application.credentials.dig(:openai, :api_key), and this commit removed the host initializer's config.ai_api_key assignment, so credential-backed deployments now hit CoPlan::Ai::NoProviderError and discard summary jobs despite having a valid key. Please include credentials in the auto-wire check or make the default provider lazy.
Useful? React with 👍 / 👎.
| @@ -1,7 +1,6 @@ | |||
| module CoPlan | |||
| class Configuration | |||
| attr_accessor :authenticate, :api_authenticate, :sign_in_path | |||
There was a problem hiding this comment.
Keep documented AI setters from breaking host boot
The in-repo host guide still instructs hosts to set config.ai_api_key / config.ai_model and config.ai_base_url (docs/HOST_APP_GUIDE.md lines 66-68 and 140-142). After these accessors are removed, any host initializer following the current docs or upgrading before migrating to config.ai_call will raise NoMethodError during boot, before the app can start. Either keep deprecated setters as compatibility shims or update the documented migration path in this change.
Useful? React with 👍 / 👎.
7aff15a to
c7e2124
Compare
Replaces the engine's hard-coded \`CoPlan::AiProviders::OpenAi.call\`
plumbing with a host-supplied callable so deployments can swap in any
backend (raw OpenAI, an internal LLM gateway like Square's Gondola, a
test stub, etc.) without touching engine code.
CoPlan::Ai.call(messages: [
{ role: :system, content: "You are concise." },
{ role: :user, content: "Hi" },
])
# …with sugar for the 90% case:
CoPlan::Ai.call(system: "You are concise.", user: "Hi")
The messages-array shape is the canonical wire format every chat-style
LLM API speaks (OpenAI chat completions, Anthropic Messages, Gondola,
Bedrock), so the host's lambda passes it straight through with zero
translation.
# config/initializers/coplan.rb
config.ai_call = ->(messages:) {
GondolaProvider.call(messages: messages, model: "gpt-4o")
}
Model choice and deployment policy (which provider, project, rate
limits, observability) live inside the callable — not on the engine's
API surface. The engine never sees an API key or a model name.
When \`ENV["OPENAI_API_KEY"]\` is present, Configuration auto-wires
\`CoPlan::AiProviders::OpenAi\` so a standalone deployment works with
zero config. The OpenAI plugin now accepts a messages array and honors
\`ENV["OPENAI_MODEL"]\` (default gpt-4o) for per-deployment model
tuning without code changes.
- \`Configuration#ai_api_key\`, \`#ai_base_url\`, \`#ai_model\` accessors
(deployment concerns now live inside the provider lambda)
- \`coplan-square\`-style \`config.ai_api_key = ...\` lines in the host
initializer (auto-wire handles it)
- \`Configuration#ai_call\` (with docstring) + \`#ai_call_configured?\`
predicate
- \`CoPlan::Ai::NoProviderError\` (subclass of \`Ai::Error\`) for jobs
that want to distinguish "not configured" from "call failed"
Any exception raised inside the host's lambda is wrapped in
\`CoPlan::Ai::Error\`, so existing call sites
(\`SummarizePlanJob.discard_on CoPlan::Ai::Error\`) continue to work
unchanged without coupling to the provider.
Tests: 868 examples, 0 failures.
Stacked on #121 (review-bot removal).
🤖 Generated with [Amp](https://ampcode.com)
Amp-Thread-ID: https://ampcode.com/threads/T-019e84ca-1bd1-7435-acbc-73b00095d896
Co-authored-by: Amp <amp@ampcode.com>
e6c4cfb to
edb377b
Compare
- Auto-wire OpenAI default unconditionally (lazy). The provider already resolves its key from Rails credentials -> ENV and raises CoPlan::Ai::Error at call time when neither is set, so credential-only deployments now wire up correctly instead of silently leaving ai_call nil. AI jobs still discard cleanly. - Drop unused ai_call_configured? helper (no longer meaningful now that the default is always wired; nothing called it). - Update docs/HOST_APP_GUIDE.md to remove the obsolete ai_api_key / ai_base_url / ai_model setters and document config.ai_call with the built-in OpenAI auto-wire behavior, so hosts following the guide don't NoMethodError on boot. Amp-Thread-ID: https://ampcode.com/threads/T-019e84ca-1bd1-7435-acbc-73b00095d896 Co-authored-by: Amp <amp@ampcode.com>
|
Addressed both Codex notes in 457a42a:
Also dropped the now-unused |
2d286b3
into
hampton/coplan-23/remove-review-bots
Why
Replaces the engine's hard-coded
CoPlan::AiProviders::OpenAi.callplumbing with a host-supplied callable so deployments can swap in any backend — raw OpenAI, an internal LLM gateway like Square's Gondola, an Anthropic client, a test stub, etc. — without touching engine code.This is the engine-side prerequisite for wiring CoPlan-on-Square through Gondola (vs. uploading a raw
OPENAI_API_KEYto Keywhiz, which was the path the now-closed coplan-square#75 took).What the engine now exposes
The messages-array shape is the canonical wire format every chat-style LLM API speaks (OpenAI Chat Completions, Anthropic Messages, Gondola, Bedrock), so the host's lambda passes it straight through with zero translation.
thread_id, streaming, tool calls, etc. are deliberately not part of this surface — if/when we need them they get their own slots (config.ai_chat,config.ai_stream, etc.). Premature to design for it.What the host wires
Model choice and deployment policy (which provider, project, rate limits, observability) live inside the callable — not on the engine's API surface. The engine never sees an API key or a model name.
Built-in default
When
ENV["OPENAI_API_KEY"]is present,Configuration#initializeauto-wiresCoPlan::AiProviders::OpenAi— so a standalone deployment of the engine works with zero extra config. The OpenAI plugin now accepts a messages array and honorsENV["OPENAI_MODEL"](defaultgpt-4o) for per-deployment model tuning without code changes.Removed
Configuration#ai_api_key/#ai_base_url/#ai_modelconfig.ai_api_key = ...lineAdded
Configuration#ai_callwith full docstring +#ai_call_configured?predicateCoPlan::Ai::NoProviderError(subclass ofAi::Error) so jobs can distinguish "not configured" from "call failed"CoPlan::Ai.callfor the single-shot(system:, user:)caseErrors
Any exception raised inside the host's lambda is wrapped in
CoPlan::Ai::Error, so existing call sites (SummarizePlanJob.discard_on CoPlan::Ai::Error) continue to work unchanged without coupling to the provider.ArgumentError(caller bug — empty messages) propagates normally so it isn't silently swallowed.Verification
bundle exec rspecsystem_prompt/user_content/ai_api_key/ai_base_urlcoplan_plan_versions.ai_modelcolumn remains; out of scope to drop here)Follow-ups (separate PRs)
coplan-enginegem release after merge.coplan-engineinsquareup/coplan-square, copy in the engine migration, and wireconfig.ai_callto a Gondola-backed Ruby helper (which means registering CoPlan's model in Gondola + addinggondola-skias a Registry dep oncoplan-square).🤖 Generated with Amp