Skip to content

feat(api): integrate OpenRouter SDK and implement AI form generation … - #100

Merged
Basharkhan7776 merged 2 commits into
Openlabsops:mainfrom
yashraj639:feat/ai-form-generation
Jul 17, 2026
Merged

Basharkhan7776 merged 2 commits into
Openlabsops:mainfrom
yashraj639:feat/ai-form-generation

Conversation

@yashraj639

@yashraj639 yashraj639 commented Jul 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Integrates the OpenRouter SDK, sets up the prompt structure for AI form generation, and replaces the mock generation controller with a real schema-validated AI pipeline. Also resolves a database migration issue to ensure idempotency.

Changes Made

  • API & SDK setup: Added the @openrouter/sdk package dependency to apps/api.
  • Environment variables: Introduced OPENROUTER_API_KEY and configurable AI_MODEL with fallbacks inside env.ts and .env.example.
  • AI client & prompt utils:
    • Created apps/api/src/lib/ai/client.ts for unified OpenRouter client access.
    • Created apps/api/src/lib/ai/prompt.ts containing rules and schemas for element/option mapping.
  • Controller generation logic:
    • Swapped the hardcoded mock in POST /api/v1/forms/generate with a real AI call.
    • Implemented automatic retry logic (max 2 attempts) for JSON parsing and Zod schema validations.
    • Enabled native JSON Mode (responseFormat: { type: "json_object" }).
    • Added schema validation via Zod's FormDefinitionSchema.
  • Database Migration: Fixed a duplicate constraint error in migration 20260701000001_add_cloned_form_fk by adding DROP CONSTRAINT IF EXISTS.

Technical Decisions

  • ESM ("type": "module") — The project already uses ESNext module resolution with Bun. Using export/import consistently avoids interop issues between CJS and ESM, and aligns with the existing tsconfig.json ("module": "ESNext").
  • OpenRouter SDK — Chose OpenRouter over a direct provider SDK (OpenAI, Anthropic, etc.) because:
  • Single SDK grants access to 400+ models (free and paid)
  • Easy model swapping via AI_MODEL env var without code changes
  • Built-in fallback routing if a provider is down
  • chatRequest wrapper — The OpenRouter SDK's chat.send() expects the request body nested under a chatRequest key (separate from headers like HTTP-Referer). This is the SDK's own type signature, not an arbitrary choice. Our usage matches the SendChatCompletionRequestRequest type exactly

Testing

  • All 39 integration tests pass (bun run test in packages/api-tests is fully clean)
  • Manual testing completed (using a local scratch script confirming model response formats, valid UUID v4 structures for options, and correct Zod checks)
  • No regressions introduced (all existing auth, onboarding, and dashboard API tests pass)

Related Issues

Checklist

  • Code follows project conventions
  • Tests pass
  • Documentation updated
  • No linting errors
  • Integrated OpenRouter-based AI form generation.
  • Replaced mock form responses with schema-validated generated forms and retry handling.
  • Added configurable AI credentials and model settings.
  • Made the cloned-form foreign key migration idempotent.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The API replaces mock form generation with OpenRouter-backed JSON generation, prompt construction, schema validation, retry handling, and explicit HTTP errors. AI configuration and SDK wiring are added, the generation prompt becomes required, and a Prisma foreign key migration is made rerunnable.

Changes

AI form generation

Layer / File(s) Summary
AI client configuration
apps/api/.env.example, apps/api/package.json, apps/api/src/lib/env.ts, apps/api/src/lib/ai/client.ts
Adds OpenRouter configuration, the SDK dependency, AI environment settings, and a shared configured client.
Generation prompt and input contract
apps/api/src/lib/ai/prompt.ts, apps/api/src/lib/form-schemas.ts
Defines the required form JSON output and makes the generation prompt required with existing length and trimming validation.
AI generation controller flow
apps/api/src/controllers/form.controller.ts
Replaces the mock response with AI JSON generation, one retry, schema validation, and explicit error responses.

Foreign key migration

Layer / File(s) Summary
Foreign key replacement
packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql
Drops the cloned-form foreign key if present before recreating it with the existing referential actions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant generateForm
  participant OpenRouter
  participant FormDefinitionSchema
  Client->>generateForm: Submit generation prompt
  generateForm->>OpenRouter: Request JSON form definition
  OpenRouter-->>generateForm: Return generated content
  generateForm->>FormDefinitionSchema: Validate parsed definition
  FormDefinitionSchema-->>generateForm: Return validation result
  generateForm-->>Client: Return form or HTTP error
Loading

Possibly related PRs

  • Openlabsops/Snap-form#62: Replaces the placeholder form generation handler with provider-backed generation and schema validation.

Suggested reviewers: silky-x0

Poem

I’m a rabbit with prompts in my hat,
Form JSON hops out neat and flat.
OpenRouter carries the code,
Zod checks each element’s load,
While migrations tidy the track.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR covers provider SDK integration, env-based config, shared client, prompt-driven JSON generation, schema validation, and retry handling.
Out of Scope Changes check ✅ Passed The migration tweak is related to the stated objectives and no unrelated changes are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: integrating OpenRouter and adding AI form generation in the API.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/controllers/form.controller.ts`:
- Around line 304-314: Add a bounded timeout or AbortSignal to the request
constructed in the aiClient.chat.send call, using the surrounding controller’s
existing timeout configuration if available. Ensure an unresponsive AI service
causes the request to fail gracefully rather than hanging indefinitely, while
preserving the current chat payload and response format.
- Around line 298-309: Remove the duplicated user prompt from the
form-generation request: in apps/api/src/controllers/form.controller.ts lines
298-309, call buildFormGenerationPrompt without an argument while retaining the
existing user message; in apps/api/src/lib/ai/prompt.ts lines 5-39, remove the
userPrompt parameter and the appended “User request” section so the template
contains only system instructions.
- Line 343: Update the error response in the controller’s catch path to stop
returning err.message or other raw downstream details; log the actual error
server-side, then return a sanitized generic failure message in the response
while preserving the existing error status and handling flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 88225e88-1e35-4b71-b7ad-7d0d839b1adf

📥 Commits

Reviewing files that changed from the base of the PR and between 6bee544 and 2862ddb.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • apps/api/.env.example
  • apps/api/package.json
  • apps/api/src/controllers/form.controller.ts
  • apps/api/src/lib/ai/client.ts
  • apps/api/src/lib/ai/prompt.ts
  • apps/api/src/lib/env.ts
  • apps/api/src/lib/form-schemas.ts
  • packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-06-16T07:39:18.285Z
Learnt from: Basharkhan7776
Repo: Openlabsops/Snap-form PR: 22
File: apps/api/package.json:10-10
Timestamp: 2026-06-16T07:39:18.285Z
Learning: In the Openlabsops/Snap-form monorepo (Bun workspace), internal workspace package dependencies using the `repo/*` scope must use the version specifier `"*"` (not `"workspace:*"`). Apply this consistently when reviewing all `apps/*/package.json` and `packages/*/package.json` files (e.g., `repo/typescript-config`, `repo/eslint-config`, `repo/types`): don’t restrict the check to newly added `repo/*` packages—`"*"` is the repo-wide convention.

Applied to files:

  • apps/api/package.json
🪛 dotenv-linter (4.0.0)
apps/api/.env.example

[warning] 11-11: [UnorderedKey] The OPENROUTER_API_KEY key should go before the PORT key

(UnorderedKey)


[warning] 12-12: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 12-12: [UnorderedKey] The AI_MODEL key should go before the BETTER_AUTH_SECRET key

(UnorderedKey)

🔇 Additional comments (5)
packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql (1)

2-3: LGTM!

apps/api/.env.example (1)

11-12: LGTM!

apps/api/src/lib/env.ts (1)

31-34: LGTM!

apps/api/src/lib/form-schemas.ts (1)

42-42: LGTM!

apps/api/src/lib/ai/client.ts (1)

1-6: 🎯 Functional Correctness

@openrouter/sdk is the correct client here

The package exists, exports OpenRouter, and supports chat.send(...) as used in this code.

			> Likely an incorrect or invalid review comment.

Comment on lines +298 to +309
const systemPrompt = buildFormGenerationPrompt(prompt);

const MAX_RETRIES = 1;

for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
const result = await aiClient.chat.send({
chatRequest: {
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: prompt },
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Remove duplicated user prompt.

The user's prompt is being injected into both the system message (via buildFormGenerationPrompt(prompt)) and the user message array. This duplication is redundant and wastes tokens.

  • apps/api/src/controllers/form.controller.ts#L298-L309: Call buildFormGenerationPrompt() without arguments, as the user input is already provided in the { role: "user", content: prompt } message.
  • apps/api/src/lib/ai/prompt.ts#L5-L39: Remove the userPrompt parameter and the appended ## User request: section from the template so it only defines the system instructions.
📍 Affects 2 files
  • apps/api/src/controllers/form.controller.ts#L298-L309 (this comment)
  • apps/api/src/lib/ai/prompt.ts#L5-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/controllers/form.controller.ts` around lines 298 - 309, Remove
the duplicated user prompt from the form-generation request: in
apps/api/src/controllers/form.controller.ts lines 298-309, call
buildFormGenerationPrompt without an argument while retaining the existing user
message; in apps/api/src/lib/ai/prompt.ts lines 5-39, remove the userPrompt
parameter and the appended “User request” section so the template contains only
system instructions.

Comment thread apps/api/src/controllers/form.controller.ts
Comment thread apps/api/src/controllers/form.controller.ts Outdated
@silky-x0

Copy link
Copy Markdown
Collaborator

LGTM!!

@Basharkhan7776 Basharkhan7776 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool work, but don't know about the performance and test. @yashraj639 and @silky-x0 we need test for this implementations.

@Basharkhan7776
Basharkhan7776 merged commit 3d4f622 into Openlabsops:main Jul 17, 2026
3 checks passed
@silky-x0

silky-x0 commented Jul 17, 2026 •

Copy link
Copy Markdown
Collaborator

@Basharkhan7776 yeah already raised issue and working on it(unit test), as for the integration test we'll check once UI is done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement AI Generation Logic (Prompt + Schema → FormDefinition) Choose AI Provider & Integrate SDK

3 participants