diff --git a/agent-docs/src/agents/doc-qa/prompt.ts b/agent-docs/src/agents/doc-qa/prompt.ts index 178fea60..5b71a09f 100644 --- a/agent-docs/src/agents/doc-qa/prompt.ts +++ b/agent-docs/src/agents/doc-qa/prompt.ts @@ -1,9 +1,72 @@ import type { AgentContext } from '@agentuity/sdk'; -import { generateObject } from 'ai'; +import { generateObject, generateText } from 'ai'; import { openai } from '@ai-sdk/openai'; + import type { PromptType } from './types'; import { PromptClassificationSchema } from './types'; + +export async function rephraseVaguePrompt(ctx: AgentContext, input: string): Promise { + const systemPrompt = `You are a technical documentation search assistant for developer tools and AI agents. Your job is to CAREFULLY improve unclear queries ONLY when absolutely necessary. + +BE EXTREMELY CONSERVATIVE. Most queries should be returned UNCHANGED. + +ONLY rephrase if the query contains: +1. OBVIOUS acronyms that need expansion (SDK, API, CLI, UI, KV, HTTP, REST, JSON, XML) +2. Very vague single words like "error", "setup", "install" without context + +NEVER change or "correct" these technical terms (return them exactly as written): +- bun, node, deno (JavaScript runtimes) +- react, vue, angular, svelte (frameworks) +- typescript, javascript, python, rust (languages) +- docker, kubernetes, aws, gcp (platforms) +- Any proper nouns or brand names + +When rephrasing: +- Keep the original technical terms EXACTLY as written +- Only add minimal context for clarity +- Don't assume what the user meant +- Don't add implementation details + +Examples of GOOD rephrasing: +- "SDK setup" → "SDK setup installation configuration" +- "API error" → "API error handling troubleshooting" +- "CLI install" → "CLI installation setup" + +Examples of what to LEAVE UNCHANGED: +- "bun example agent" → "bun example agent" (bun is a known runtime) +- "react component" → "react component" (already clear) +- "node server setup" → "node server setup" (already specific enough) +- "typescript agent" → "typescript agent" (clear technical terms) + +If in doubt, return the query UNCHANGED. Better to leave it as-is than to misinterpret the user's intent. + +Return ONLY the query text, nothing else.`; + + try { + const result = await generateText({ + model: openai('gpt-4o-mini'), + system: systemPrompt, + prompt: `User query: "${input}"`, + maxTokens: 100, + temperature: 0.1, + }); + + const rephrasedQuery = result.text?.trim() || input; + console.log(rephrasedQuery); + // Log if we actually rephrased it + if (rephrasedQuery !== input) { + ctx.logger.info('Rephrased query from "%s" to "%s"', input, rephrasedQuery); + } + + return rephrasedQuery; + + } catch (error) { + ctx.logger.error('Error rephrasing prompt, returning original: %o', error); + return input; + } +} + /** * Determines the prompt type based on the input string using LLM classification. * Uses specific, measurable criteria to decide between Normal and Agentic RAG. diff --git a/agent-docs/src/agents/doc-qa/rag.ts b/agent-docs/src/agents/doc-qa/rag.ts index 42805791..e710e617 100644 --- a/agent-docs/src/agents/doc-qa/rag.ts +++ b/agent-docs/src/agents/doc-qa/rag.ts @@ -3,23 +3,32 @@ import { generateObject } from 'ai'; import { openai } from '@ai-sdk/openai'; import { retrieveRelevantDocs } from './retriever'; +import { rephraseVaguePrompt } from './prompt'; import { AnswerSchema } from './types'; import type { Answer } from './types'; export default async function answerQuestion(ctx: AgentContext, prompt: string) { - const relevantDocs = await retrieveRelevantDocs(ctx, prompt); + // First, rephrase the prompt for better vector search + const rephrasedPrompt = await rephraseVaguePrompt(ctx, prompt); + + // Use the rephrased prompt for document retrieval + const relevantDocs = await retrieveRelevantDocs(ctx, rephrasedPrompt); const systemPrompt = ` You are Agentuity's developer-documentation assistant. +=== CONTEXT === +You will receive both the user's ORIGINAL question and a REPHRASED version that was optimized for document search. The rephrased version helped find the relevant documents, but you should answer the user's original intent. + === RULES === 1. Use ONLY the content inside tags to craft your reply. If the required information is missing, state that the docs do not cover it. 2. Never fabricate or guess undocumented details. -3. Ambiguity handling: +3. Focus on answering the ORIGINAL QUESTION, using the documents found via the rephrased search. +4. Ambiguity handling: • When contains more than one distinct workflow or context that could satisfy the question, do **not** choose for the user. • Briefly (≤ 2 sentences each) summarise each plausible interpretation and ask **one** clarifying question so the user can pick a path. • Provide a definitive answer only after the ambiguity is resolved. -4. Answer style: +5. Answer style: • If the question can be answered unambiguously from a single workflow, give a short, direct answer. • Add an explanation only when the user explicitly asks for one. • Format your response in **MDX (Markdown Extended)** format with proper syntax highlighting for code blocks. @@ -28,8 +37,8 @@ You are Agentuity's developer-documentation assistant. • Wrap code snippets in appropriate language blocks (e.g., \`\`\`typescript, \`\`\`json, \`\`\`javascript). • Use **bold** for important terms and *italic* for emphasis when appropriate. • Use > blockquotes for important notes or warnings. -5. You may suggest concise follow-up questions or related topics that are present in . -6. Keep a neutral, factual tone. +6. You may suggest concise follow-up questions or related topics that are present in . +7. Keep a neutral, factual tone. === OUTPUT FORMAT === Return **valid JSON only** matching this TypeScript type: @@ -74,9 +83,13 @@ agentuity agent create [name] [description] [auth_type] > **Note**: This command will create the agent in the Agentuity Cloud and set up local files. - + ${prompt} - + + + +${rephrasedPrompt} + ${JSON.stringify(relevantDocs, null, 2)} @@ -87,7 +100,7 @@ ${JSON.stringify(relevantDocs, null, 2)} const result = await generateObject({ model: openai('gpt-4o'), system: systemPrompt, - prompt: prompt, + prompt: `Please answer the original question using the documentation found via the rephrased search query. Your answer should cater toward the original user prompt rather than the rephrased version of the query.`, schema: AnswerSchema, maxTokens: 2048, }); diff --git a/app/api/search/route.ts b/app/api/search/route.ts index eea69788..3bfd8aa3 100644 --- a/app/api/search/route.ts +++ b/app/api/search/route.ts @@ -8,7 +8,28 @@ const { GET: defaultSearchHandler } = createFromSource(source); function documentPathToUrl(docPath: string): string { // Remove the .md or .mdx extension before any # symbol - return '/' + docPath.replace(/\.mdx?(?=#|$)/, ''); + const path = docPath.replace(/\.mdx?(?=#|$)/, ''); + + // Split path and hash (if any) + const [basePath, hash] = path.split('#'); + + // Split the base path into segments + const segments = basePath.split('/').filter(Boolean); + + // If the last segment is 'index', remove it + if (segments.length > 0 && segments[segments.length - 1].toLowerCase() === 'index') { + segments.pop(); + } + + // Reconstruct the path + let url = '/' + segments.join('/'); + if (url === '/') { + url = '/'; + } + if (hash) { + url += '#' + hash; + } + return url; } // Helper function to get document title and description from source diff --git a/bin/send-webhook.sh b/bin/send-webhook.sh index ceda5e9e..ce029af5 100755 --- a/bin/send-webhook.sh +++ b/bin/send-webhook.sh @@ -21,27 +21,31 @@ RETRY_DELAY=2 echo "Sending webhook to $WEBHOOK_URL" >&2 -# Read payload from stdin -payload=$(cat) +# Create temporary file for payload +TEMP_FILE=$(mktemp) +trap 'rm -f "$TEMP_FILE"' EXIT -if [ -z "$payload" ]; then +# Read payload from stdin to temporary file +cat > "$TEMP_FILE" + +if [ ! -s "$TEMP_FILE" ]; then echo "Error: No payload received from stdin" >&2 exit 1 fi # Validate JSON -if ! echo "$payload" | jq . >/dev/null 2>&1; then +if ! jq . "$TEMP_FILE" >/dev/null 2>&1; then echo "Error: Invalid JSON payload" >&2 exit 1 fi -echo "Payload size: $(echo "$payload" | wc -c) bytes" >&2 +echo "Payload size: $(wc -c < "$TEMP_FILE") bytes" >&2 -# Build curl command +# Build curl command using temporary file curl_args=( -X POST -H "Content-Type: application/json" - -d "$payload" + --data-binary "@$TEMP_FILE" --fail --show-error --silent diff --git a/components/CustomSearchDialog/MessageList.tsx b/components/CustomSearchDialog/MessageList.tsx index 3f1cd9c1..eb5ba3d2 100644 --- a/components/CustomSearchDialog/MessageList.tsx +++ b/components/CustomSearchDialog/MessageList.tsx @@ -1,10 +1,12 @@ 'use client'; -import { useRef, useEffect } from 'react'; +import React, { useRef, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { User, HelpCircle, Loader2 } from 'lucide-react'; import { AgentuityLogo } from '../icons/AgentuityLogo'; +import { CLICommand } from '../CLICommand'; +import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; import { MessageListProps, Message } from './types'; export function MessageList({ messages, loading, handleSourceClick }: MessageListProps) { @@ -87,12 +89,52 @@ function MessageItem({ message, handleSourceClick }: MessageItemProps) { }`}> {message.type === 'ai' ? (
- + { + // Extract code content and language + const codeElement = React.Children.toArray(children)[0] as React.ReactElement<{ + className?: string; + children?: React.ReactNode; + }>; + const className = codeElement?.props?.className || ''; + const language = className.replace('language-', ''); + + // Extract string content from children + const code = typeof codeElement?.props?.children === 'string' + ? codeElement.props.children + : String(codeElement?.props?.children || ''); + + // Use CLICommand for bash/shell commands + if (language === 'bash' || language === 'sh' || language === 'shell') { + return ; + } + + return ( + + ); + }, + code: ({ children, className, ...props }) => { + if (!className) { + return ( + + {children} + + ); + } + return {children}; + } + }} + > {message.content}
) : ( -

{message.content}

+

{message.content}

)} diff --git a/components/CustomSearchDialog/SearchInput.tsx b/components/CustomSearchDialog/SearchInput.tsx index 72aaa9bc..197dd768 100644 --- a/components/CustomSearchDialog/SearchInput.tsx +++ b/components/CustomSearchDialog/SearchInput.tsx @@ -30,10 +30,17 @@ export function SearchInput({ currentInput, setCurrentInput, loading, sendMessag } }; + const handleSend = () => { + if (currentInput.trim()) { + sendMessage(currentInput); + } + }; + return (
-
+ {/* Textarea Container */} +