Hunter Companion: fix hallucination (strict no-invent rules), better text formatting with spacing
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled

This commit is contained in:
2025-12-17 14:56:39 +01:00
parent b35d5e0ba0
commit 442c1db580
2 changed files with 74 additions and 55 deletions

View File

@ -63,23 +63,26 @@ async def _get_user_tier(db: AsyncSession, user: User) -> str:
def _build_system_prompt(path: str) -> str:
tools = tool_catalog_for_prompt(path)
return (
"You are the Pounce Hunter Companion, an expert domain trading assistant. Always respond in English.\n"
"You help users with: domain analysis, auction hunting, portfolio management, and trading decisions.\n\n"
"RESPONSE FORMAT (CRITICAL):\n"
"- Write in plain text. NO markdown asterisks, NO ** or *, NO code blocks.\n"
"- Use simple dashes (-) for bullet points.\n"
"- Keep responses concise: 2-4 sentences intro, then bullets if needed.\n"
"- Never show tool outputs, JSON, or internal data to the user.\n\n"
"BEHAVIOR:\n"
"- Be helpful, direct, and conversational like a knowledgeable colleague.\n"
"- For domain questions: give a clear BUY / CONSIDER / SKIP recommendation with 3-5 reasons.\n"
"- Do NOT invent user preferences, keywords, or data. Ask if unclear.\n"
"- For greetings: respond naturally and ask how you can help.\n\n"
"You are the Pounce Hunter Companion, a domain trading expert. Always respond in English.\n\n"
"CRITICAL RULES:\n"
"1. NEVER invent or hallucinate data. You do NOT have access to SEMrush, Estibot, GoDaddy sales, or external databases.\n"
"2. If you don't have data, say so honestly. Only use data from tools you actually called.\n"
"3. Keep responses SHORT: 2-3 sentences max, then bullets if needed.\n"
"4. NO markdown: no ** or *, no code blocks, no headers with #.\n"
"5. Use dashes (-) for bullet points.\n\n"
"WHAT YOU CAN DO:\n"
"- Analyze domains using the analyze_domain tool (gives Pounce Score, risk, value estimate)\n"
"- Show user's watchlist, portfolio, listings, inbox, yield data\n"
"- Search auctions and drops\n"
"- Generate brandable names\n\n"
"WHAT YOU CANNOT DO:\n"
"- Access external sales databases or SEO tools\n"
"- Look up real-time WHOIS or DNS (unless via tool)\n"
"- Make up sales history or traffic stats\n\n"
"TOOL USAGE:\n"
"- Use tools when user asks about their data (watchlist, portfolio, listings, inbox, yield) or a specific domain.\n"
"- To call tools, respond with ONLY: {\"tool_calls\":[{\"name\":\"...\",\"args\":{...}}]}\n"
"- After receiving tool results, answer naturally without mentioning tools.\n\n"
f"AVAILABLE TOOLS:\n{json.dumps(tools, ensure_ascii=False)}\n"
"- To call a tool, respond with ONLY: {\"tool_calls\":[{\"name\":\"...\",\"args\":{...}}]}\n"
"- After tool results, summarize briefly without mentioning tools.\n\n"
f"TOOLS:\n{json.dumps(tools, ensure_ascii=False)}\n"
)
@ -143,13 +146,8 @@ async def run_agent(
{
"role": "assistant",
"content": (
"Hey! How can I help you today?\n\n"
"I can help with:\n"
"- Analyzing a specific domain\n"
"- Finding auction deals or drops\n"
"- Reviewing your portfolio or watchlist\n"
"- Checking your listings and leads\n\n"
"Just tell me what you need."
"Hey! What can I help you with?\n\n"
"Give me a domain to analyze, or ask about your watchlist, portfolio, or current auctions."
),
}
)
@ -206,11 +204,11 @@ async def stream_final_answer(convo: list[dict[str, Any]], *, model: Optional[st
{
"role": "system",
"content": (
"Final step: respond to the user in plain text.\n"
"- NO markdown: no ** or * for bold/italic, no code blocks.\n"
"- Use dashes (-) for bullets.\n"
"- Do NOT output JSON or mention tools.\n"
"- Be concise and helpful."
"Respond now. Rules:\n"
"- NEVER invent data. Only use data from tools you called.\n"
"- Keep it SHORT: 2-3 sentences, then bullet points if needed.\n"
"- NO markdown (no ** or *), just plain text with dashes for bullets.\n"
"- Do NOT mention tools or JSON."
),
}
],

View File

@ -98,27 +98,52 @@ function getTier(subscription: any): 'scout' | 'trader' | 'tycoon' {
return 'scout'
}
// Simple markdown-like formatting to clean HTML
// Format message text to clean HTML with proper spacing
function formatMessage(text: string): string {
if (!text) return ''
// Escape HTML first
let html = text
// Escape HTML
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// Bold: **text** or __text__
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/__(.+?)__/g, '<strong>$1</strong>')
// Italic: *text* or _text_ (but not inside words)
.replace(/(?<!\w)\*([^*]+)\*(?!\w)/g, '<em>$1</em>')
.replace(/(?<!\w)_([^_]+)_(?!\w)/g, '<em>$1</em>')
// Inline code: `code`
.replace(/`([^`]+)`/g, '<code class="bg-white/10 px-1 py-0.5 text-accent">$1</code>')
// Line breaks
.replace(/\n/g, '<br />')
// Bullet points: - item or • item
.replace(/<br \/>[-•]\s+/g, '<br />• ')
return html
// Remove markdown formatting (** and * for bold/italic)
html = html
.replace(/\*\*(.+?)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/__(.+?)__/g, '$1')
.replace(/_([^_]+)_/g, '$1')
// Split into paragraphs (double newline = paragraph break)
const paragraphs = html.split(/\n\n+/)
const formatted = paragraphs.map(para => {
// Check if this paragraph is a list (starts with - or number.)
const lines = para.split('\n')
const isList = lines.every(line => {
const trimmed = line.trim()
return trimmed === '' || trimmed.startsWith('-') || trimmed.startsWith('•') || /^\d+\./.test(trimmed)
})
if (isList) {
// Format as list
const items = lines
.map(line => line.trim())
.filter(line => line)
.map(line => {
// Remove leading dash, bullet, or number
const content = line.replace(/^[-•]\s*/, '').replace(/^\d+\.\s*/, '')
return `<div class="flex gap-2 py-0.5"><span class="text-accent/60 shrink-0">•</span><span>${content}</span></div>`
})
return `<div class="space-y-0.5">${items.join('')}</div>`
} else {
// Regular paragraph - convert single newlines to line breaks
return `<p class="mb-2 last:mb-0">${para.replace(/\n/g, '<br />')}</p>`
}
})
return formatted.join('')
}
// Suggestion chips based on current page
@ -514,21 +539,17 @@ export function HunterCompanion() {
.prose-chat {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.6;
line-height: 1.7;
}
.prose-chat strong {
color: rgba(255, 255, 255, 0.95);
font-weight: 600;
.prose-chat p {
margin-bottom: 0.75rem;
}
.prose-chat em {
color: rgba(255, 255, 255, 0.7);
font-style: italic;
.prose-chat p:last-child {
margin-bottom: 0;
}
.prose-chat code {
background: rgba(255, 255, 255, 0.08);
padding: 1px 4px;
border-radius: 2px;
font-size: 11px;
.prose-chat .space-y-0\.5 > div {
padding-top: 0.125rem;
padding-bottom: 0.125rem;
}
`}</style>
</>