Vercel Logo

Keep policy out of the prompt

Vendor Review has rules the company can state exactly:

  • Requests at or above a defined annual cost require Procurement review
  • Requests involving restricted data require Security review
  • Requests missing required fields cannot proceed

Open the unfinished lib/policy.ts from the scaffold and implement the cost and data-routing rules in code.

export function routeByPolicy(
  request: VendorRequestInput
): PolicyRoute {
  const reviewers = new Set<ReviewerGroup>();
  const reasons: string[] = [];
 
  if (request.annualCost >= 50_000) {
    reviewers.add("procurement");
    reasons.push("Annual cost is at least $50,000");
  }
  if (request.dataTypes.includes("restricted")) {
    reviewers.add("security");
    reasons.push(
      "Vendor will handle restricted company data"
    );
  }
 
  return {
    requiresHumanReview: reviewers.size > 0,
    reviewerGroups: [...reviewers],
    reasons,
    policyVersion: "vendor-routing-v1"
  };
}

The model handles work that benefits from judgment: classifying the vendor category, identifying missing context in the business purpose, and describing concerns for a reviewer.

Define that boundary with structured output:

const assessmentSchema = z.object({
  category: z.enum([
    "productivity",
    "development",
    "data",
    "security",
    "other"
  ]),
  suggestedRisk: z.enum(["low", "medium", "high"]),
  missingInformation: z.array(z.string()).max(5),
  summary: z.string().min(1).max(600)
});
 
const result = await generateText({
  model: ASSESSMENT_MODEL,
  instructions: assessmentInstructions,
  prompt: formatRequestForAssessment(request),
  output: Output.object({ schema: assessmentSchema })
});
 
return result.output;

The complete implementation reads the validated object from result.output in lib/ai.ts. Do not parse result.text or treat an unvalidated string as the assessment.

Structured output constrains the response shape. It does not guarantee correct judgment, prevent prompt injection, or authorize an action. Those concerns remain separate.

Combine without hiding the source

The API response should preserve policy and assessment as distinct objects. Do not collapse them into one unexplained risk field.

policy:       cost threshold → Procurement review
assessment:   data vendor, medium suggested risk, missing retention details
final route:  waiting for Procurement

Write down who decides what

Complete Who decides what in docs/readiness.md. Put each current behavior under deterministic policy, model judgment, or human authority. Resolve any unclear responsibilities before implementing those behaviors.

Summary

Keep the policy route, model assessment, and authorized human decision separate so a reviewer can tell which source determined each part of the result.

Check your work

Pass a $50,000 request with restricted data to routeByPolicy(). The result should require both Procurement and Security, include two plain-language reasons, and name vendor-routing-v1. Inspect the function’s inputs and explain why the model’s suggested risk cannot change this route.

Compare your implementation with lib/policy.ts on complete.

Was this helpful?

supported.