AI Outline System — Admin Guide¶
This guide covers setup, configuration, prompt management, cache tuning, and cost monitoring for the TripMaker AI Outline feature.
1. Setup¶
Prerequisites¶
- TQPro server running with
TLINQ_HOMEpointing to theconfig/directory - Database migrations applied (specifically
0015-tripmaker-phase0.sqlwhich creates theai_outline_cache,ai_usage_log, andai_outline_feedbacktables) - An Anthropic API key with access to the Claude Messages API
Configuration¶
All AI settings live in config/tourlinq.properties:
ai.provider=anthropic
ai.api.key=sk-ant-api03-... # Replace with your Anthropic API key
ai.api.endpoint=https://api.anthropic.com/v1/messages
ai.api.version=2023-06-01
ai.model=claude-sonnet-4-5-20250929 # Model identifier (see "Changing Models" below)
ai.cache.ttl.hours=720 # Cache lifetime in hours (720 = 30 days)
ai.prompt.file=ai-outline-prompt.txt # System prompt filename (relative to TLINQ_HOME)
ai.max.tokens=4096 # Max tokens in Claude response
ai.timeout.seconds=30 # HTTP timeout for API calls
API Key Security: In production, set the API key via AWS Systems Manager Parameter Store using the ##ai.api.key placeholder syntax. The ## prefix tells the config loader to resolve the value from SSM at startup. Never commit a real API key to the properties file.
Verify Setup¶
After configuring the API key, verify the integration by calling:
POST /ai/outline/generate
{
"session": "<valid-session>",
"destination": "London, UK",
"startDate": "2026-06-01",
"endDate": "2026-06-08",
"tripType": "Leisure",
"budgetTier": "mid-range",
"adults": 2,
"childAges": [],
"nationalities": ["GB"],
"residences": ["AE"],
"agentId": 1,
"tripId": 1
}
A successful response contains apiData.outlineJson with the generated JSON brief. If the API key is missing or invalid, the response will contain an error message indicating the configuration problem.
2. How the Prompt System Works¶
The AI outline generation follows this flow:
Agent requests outline
|
Build cache key (SHA-256 of normalized inputs)
|
Check DB cache ──── hit + not expired ──→ return cached outline
|
cache miss
|
Load system prompt from file
|
Build user message from request params
|
POST to Claude API (system + user message)
|
Parse JSON response
|
Save to cache with TTL expiry
|
Log usage (tokens, cost, agent, trip)
|
Return outline to agent
Cache Key Composition¶
The cache key is a SHA-256 hash of these normalized (lowercase, trimmed) fields joined by |:
This means the same destination/month/travelers/budget/nationality combination returns a cached result regardless of the exact travel dates within that month. Changing any input field produces a different cache key and triggers a fresh generation.
Pax profile format: {adults}a[_{children}c_{age1}_{age2}...] — for example, 2a_2c_8_12 means 2 adults with 2 children aged 8 and 12.
Nationality profile format: nat:{sorted codes}|res:{sorted codes} — for example, nat:DE,GB|res:AE means travelers hold German and British passports, residing in the UAE. This ensures that travelers with different nationalities get visa-specific advice.
Two-Part Prompt Structure¶
The Claude API receives two parts:
-
System prompt (
ai-outline-prompt.txt) — defines the AI's role, rules, and expected JSON output schema. This is shared across all requests and cached in memory after first load. -
User message — dynamically built per request with the specific destination, dates, traveler profile, and budget tier. Example:
Destination: London, UK
Departure city: Dubai
Travel dates: 1 June 2026 to 8 June 2026
Travel month: June 2026
Trip type: Leisure
Budget tier: mid-range
Travelers: 2 adult(s), 2 child(ren) ages 8, 12
Traveler nationalities: GB
Traveler country of residence: AE
The nationality and residence lines are included when the traveler details have this information set. This allows the AI to provide specific visa requirements per nationality rather than generic advice.
3. Editing the System Prompt¶
The system prompt file is at config/ai-outline-prompt.txt. Changes take effect on the next server restart (the prompt is cached in memory after first load).
Prompt Structure¶
The current prompt has these sections:
| Section | Purpose |
|---|---|
| Role definition | Sets the AI as a Dubai-based travel consultant |
| Rules (numbered 1-6) | Behavioral constraints: be specific, respect budget tier, consider travelers, account for departure city, assess timing honestly, use USD |
| Response format | The exact JSON schema the AI must follow |
| Quantity guidance | "2-3 areas, 5-7 sights, 5-7 activities" |
Editing Guidelines¶
Do:
- Keep the
Respond ONLY with valid JSONinstruction — the facade parses the response as raw JSON - Keep all field names in the JSON schema unchanged — the frontend expects this exact structure
- Adjust quantity guidance (e.g., change "5-7 sights" to "3-5 sights" to reduce token usage)
- Add domain-specific rules (e.g., "Always mention UAE resident visa waiver countries")
- Refine the role description to better match agency positioning
- Add or remove fields in the
practicalsection as business needs evolve
Don't:
- Remove the JSON-only instruction — any narrative wrapper will break parsing
- Rename top-level JSON fields (
headline,narrative,timing_score,areas,sights,activities,practical) — the frontend renders these by name - Add instructions that would cause the model to refuse reasonable requests
- Make the prompt excessively long — longer prompts increase input token costs on every non-cached call
Testing Prompt Changes¶
- Edit the prompt file
- Restart the server (or restart the specific worker if running multiple)
- Make a test request to
/ai/outline/generatewith a destination that is not already cached - Inspect the
outlineJsonfield in the response - If the JSON structure is malformed, check the prompt for conflicting instructions
Tip: To test without polluting the production cache, use an unusual destination + budget tier combination that no agent would have queried.
4. Enriching the Prompt¶
Adding New Output Fields¶
To add a new field to the AI response (e.g., dining recommendations):
- Add the new field to the JSON schema in
ai-outline-prompt.txt: - Add a corresponding instruction in the rules section (e.g., "Include 3-5 dining recommendations matching the budget tier")
- Restart the server
- The
outlineJsonfield will now include the new data — the frontend can parse it from the raw JSON without backend changes
Adjusting for Different Departure Cities¶
The prompt currently assumes Dubai as the primary departure city. If the agency expands to other base cities, add conditional guidance:
When the departure city is Dubai or UAE-based, emphasize:
- UAE resident visa advantages
- Emirates/flydubai direct route availability
- Gulf-specific cultural tips
When the departure city is European, emphasize:
- EU transit rules
- Budget airline options
- Schengen visa considerations
Seasonal Fine-Tuning¶
Add seasonal rules to improve timing accuracy:
For Gulf-based travelers (Dubai, Abu Dhabi, Doha departure):
- Peak outbound months: June-August (summer escape)
- Secondary peak: December-January (winter holiday)
- Budget opportunity: shoulder seasons (April-May, September-October)
Budget Tier Calibration¶
Adjust per-tier expectations to match agency pricing:
Budget tier reference ranges (per person per night, accommodation):
- budget: $50-100 USD
- mid-range: $100-250 USD
- luxury: $250-500 USD
- ultra-luxury: $500+ USD
5. Cache Management¶
How Caching Works¶
Each unique combination of destination + departure city + travel month + trip type + pax profile + budget tier generates one cache entry. The entry is stored in the nts.ai_outline_cache table with an absolute expiry timestamp.
Cached entries are served until they expire, then the next request triggers a fresh generation.
Cache TTL Tuning¶
The ai.cache.ttl.hours property controls how long cached outlines remain valid.
| Value | Duration | Use Case |
|---|---|---|
| 24 | 1 day | During prompt development/testing |
| 168 | 1 week | High-freshness requirement |
| 720 | 30 days | Default — good balance of freshness and cost |
| 2160 | 90 days | Stable destinations, cost-sensitive |
Invalidating Cache¶
To force regeneration for a specific destination (e.g., after a prompt change):
-- Delete specific cached outline
DELETE FROM nts.ai_outline_cache WHERE destination ILIKE '%london%';
-- Delete all expired entries (cleanup)
DELETE FROM nts.ai_outline_cache WHERE expires_at < NOW();
-- Nuclear option: clear entire cache (after major prompt rewrite)
TRUNCATE nts.ai_outline_cache;
After deleting cache entries, the next agent request for that destination will trigger a fresh API call.
6. Changing Models¶
To switch Claude model versions, update ai.model in tourlinq.properties:
# Sonnet (default — balanced cost/quality)
ai.model=claude-sonnet-4-5-20250929
# Haiku (faster, cheaper, less detailed)
ai.model=claude-haiku-4-5-20251001
# Opus (most capable, higher cost)
ai.model=claude-opus-4-6
After changing the model, clear the cache to regenerate outlines with the new model's output quality:
Cost impact (approximate per outline, ~1500 input tokens + ~2000 output tokens):
| Model | Input Cost | Output Cost | Total per Outline |
|---|---|---|---|
| Haiku | ~$0.001 | ~$0.005 | ~$0.006 |
| Sonnet | ~$0.005 | ~$0.030 | ~$0.035 |
| Opus | ~$0.023 | ~$0.150 | ~$0.173 |
With 30-day caching, even at 100 unique destinations/month, Sonnet costs approximately $3.50/month.
7. Monitoring¶
Usage & Cost Tracking¶
Query the usage log to monitor API spend:
-- Daily cost summary (last 30 days)
SELECT DATE(created_at) AS day,
COUNT(*) AS total_requests,
SUM(CASE WHEN cached THEN 1 ELSE 0 END) AS cache_hits,
SUM(CASE WHEN NOT cached THEN 1 ELSE 0 END) AS api_calls,
ROUND(SUM(cost_usd)::numeric, 4) AS total_cost_usd
FROM nts.ai_usage_log
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY day DESC;
-- Monthly cost summary
SELECT TO_CHAR(created_at, 'YYYY-MM') AS month,
COUNT(*) AS requests,
ROUND(SUM(cost_usd)::numeric, 4) AS cost_usd,
ROUND(AVG(input_tokens)::numeric, 0) AS avg_input_tokens,
ROUND(AVG(output_tokens)::numeric, 0) AS avg_output_tokens
FROM nts.ai_usage_log
WHERE NOT cached
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month DESC;
-- Cache hit rate
SELECT ROUND(100.0 * SUM(CASE WHEN cached THEN 1 ELSE 0 END) / COUNT(*), 1) AS cache_hit_pct
FROM nts.ai_usage_log
WHERE created_at > NOW() - INTERVAL '7 days';
Feedback Monitoring¶
Track agent satisfaction with generated outlines:
-- Feedback summary
SELECT COUNT(*) AS total_feedback,
SUM(CASE WHEN rating = 1 THEN 1 ELSE 0 END) AS thumbs_up,
SUM(CASE WHEN rating = -1 THEN 1 ELSE 0 END) AS thumbs_down,
ROUND(100.0 * SUM(CASE WHEN rating = 1 THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0), 1) AS approval_pct
FROM nts.ai_outline_feedback;
-- Recent negative feedback with comments (for prompt improvement)
SELECT f.created_at, f.comment, c.destination, c.budget_tier, c.pax_profile
FROM nts.ai_outline_feedback f
JOIN nts.ai_outline_cache c ON c.cache_id = f.cache_id
WHERE f.rating = -1 AND f.comment IS NOT NULL
ORDER BY f.created_at DESC
LIMIT 20;
Use negative feedback patterns to identify prompt improvements — if agents consistently dislike outlines for a certain destination type or budget tier, refine the prompt's guidance for that scenario.
Health Checks¶
If agents report "AI outline generation failed" errors:
- Check API key: Verify
ai.api.keyintourlinq.propertiesis set and not the##placeholder - Check connectivity:
curl -s https://api.anthropic.com/v1/messages -H "x-api-key: YOUR_KEY" -H "anthropic-version: 2023-06-01" -H "content-type: application/json" -d '{"model":"claude-sonnet-4-5-20250929","max_tokens":10,"messages":[{"role":"user","content":"hello"}]}' - Check prompt file: Ensure
config/ai-outline-prompt.txtexists and is readable - Check logs: Look for
AiOutlineFacadeentries in the application log at WARNING or SEVERE level - Check model availability: If an older model ID has been deprecated, update
ai.modelto a current version