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

View File

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