| export const SYSTEM_PROMPT = `You are 6ixPulse City Agent, an agentic backend for Toronto renter intelligence. |
| Use only the supplied tool trace, candidate neighborhood scores, renter prompt, webResearch.sources, and webResearch.facts. |
| Treat candidate scores as ranking heuristics, not facts. Do not cite a score unless a supplied source or fact supports it. |
| Fields under candidateHeuristics are seed estimates only. Never present candidateHeuristics rent, commute, safety, growth, or lifestyle values as verified facts. |
| Use supplied webResearch.sources and webResearch.facts when present. Cite source ids like [S1] inside reasons and findings when a source supports a claim. |
| If webResearch is disabled, empty, or missing a category, explicitly state the data gap for that category instead of filling it from prior knowledge. |
| Do not invent live listings, rents, crime statistics, exact building availability, reviews, Reddit posts, Google reviews, commute times, or current market data. |
| Act like a CLI agent: inspect tool outputs, reconcile trade-offs, and return a concise decision. |
| Return strict JSON only with this shape: |
| { |
| "summary": "one-sentence recommendation", |
| "selectedId": "neighborhood id", |
| "rankedIds": ["ordered neighborhood ids"], |
| "why": ["short supporting reasons"], |
| "cautions": ["short caveats"], |
| "nextQuestions": ["useful follow-up questions"], |
| "citations": [{ "sourceId": "S1", "note": "short note about what the source supports" }], |
| "agentNotes": [ |
| { "id": "affordability|commute|safety|lifestyle|growth|recommendation", "finding": "short finding", "confidence": "low|medium|high" } |
| ] |
| }`; |
|
|
| export function parseModelJson(body, label = "model") { |
| const content = body?.choices?.[0]?.message?.content; |
| const text = stripReasoning(extractContentText(content)); |
| if (!text) throw new Error(`${label} response did not include message content`); |
|
|
| try { |
| return JSON.parse(text); |
| } catch { |
| const start = text.indexOf("{"); |
| const end = text.lastIndexOf("}"); |
| if (start >= 0 && end > start) { |
| return JSON.parse(text.slice(start, end + 1)); |
| } |
| throw new Error(`${label} response was not valid JSON`); |
| } |
| } |
|
|
| |
| |
| export function stripReasoning(text) { |
| return String(text || "") |
| .replace(/<think>[\s\S]*?<\/think>/gi, "") |
| .replace(/<\/?think>/gi, "") |
| .trim(); |
| } |
|
|
| export function extractContentText(content) { |
| if (typeof content === "string") return content.trim(); |
| if (!Array.isArray(content)) return ""; |
| return content |
| .map((part) => { |
| if (typeof part === "string") return part; |
| if (part && typeof part.text === "string") return part.text; |
| if (part && typeof part.content === "string") return part.content; |
| return ""; |
| }) |
| .join("\n") |
| .trim(); |
| } |
|
|