-
Notifications
You must be signed in to change notification settings - Fork 189
Simplify databunny api. #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { z } from 'zod'; | ||
| import type { AssistantRequestType } from '../../schemas/assistant-schemas'; | ||
|
|
||
| export const AIResponseJsonSchema = z.object({ | ||
| sql: z.string().nullable().optional(), | ||
|
|
@@ -41,14 +42,11 @@ export const AIPlanSchema = z.object({ | |
| plan: z.array(z.string()), | ||
| }); | ||
|
|
||
| export const comprehensiveUnifiedPrompt = ( | ||
| userQuery: string, | ||
| export const comprehensiveSystemPrompt = ( | ||
| websiteId: string, | ||
| websiteHostname: string, | ||
| mode: 'analysis_only' | 'execute_chat' | 'execute_agent_step', | ||
| previousMessages?: Array<{ role: string; content: string }>, | ||
| agentToolResult?: Record<string, unknown>, | ||
| _model?: 'chat' | 'agent' | 'agent-max' | ||
| _model?: AssistantRequestType['model'] | ||
| ) => ` | ||
|
Comment on lines
+45
to
50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Prompt still references <user_query> and includes invalid ClickHouse INTERVAL examples—please fix
Suggested changes:
Additionally, to enforce correctness at the schema level (and reduce reliance on defaults), consider switching to Zod discriminated unions so chart responses must include a chart type: // Outside this hunk: refactor AIResponseJsonSchema to a discriminated union
const Base = z.object({
sql: z.string().nullable().optional(),
thinking_steps: z.array(z.string()).optional(),
});
const TextResp = Base.extend({
response_type: z.literal('text'),
text_response: z.string().nullable().optional(),
chart_type: z.null().optional(),
});
const MetricResp = Base.extend({
response_type: z.literal('metric'),
metric_value: z.string(),
metric_label: z.string().nullable().optional(),
text_response: z.string().nullable().optional(),
chart_type: z.null().optional(),
});
const ChartResp = Base.extend({
response_type: z.literal('chart'),
sql: z.string(), // require SQL for charts
chart_type: z.enum([/* …all chart types… */]).default('bar'), // enforce presence with default
text_response: z.string().nullable().optional(),
});
export const AIResponseJsonSchema = z.discriminatedUnion('response_type', [
TextResp, MetricResp, ChartResp,
]);This aligns with the PR note about discriminated unions and ensures the LLM output is structurally valid for each response type. |
||
| <persona> | ||
| You are Databunny, a world-class, specialized data analyst for the website ${websiteHostname}. You are precise, analytical, and secure. Your sole purpose is to help users understand their website's analytics data by providing insights, generating SQL queries, and creating visualizations. | ||
|
|
@@ -168,25 +166,8 @@ You are Databunny, a world-class, specialized data analyst for the website ${web | |
| <website_id>${websiteId}</website_id> | ||
| <website_hostname>${websiteHostname}</website_hostname> | ||
| <mode>${mode}</mode> | ||
| <user_query>${userQuery}</user_query> | ||
| <current_date_utc>${new Date().toISOString().split('T')[0]}</current_date_utc> | ||
| <current_timestamp_utc>${new Date().toISOString()}</current_timestamp_utc> | ||
| ${ | ||
| previousMessages && previousMessages.length > 0 | ||
| ? ` | ||
| <conversation_history> | ||
| ${previousMessages | ||
| .slice(-4) | ||
| .map( | ||
| (msg) => | ||
| `<message role="${msg.role}">${msg.content?.substring(0, 200)}${msg.content?.length > 200 ? '...' : ''}</message>` | ||
| ) | ||
| .join('\n')} | ||
| </conversation_history> | ||
| ` | ||
| : '' | ||
| } | ||
| ${agentToolResult ? `<agent_tool_result>${JSON.stringify(agentToolResult)}</agent_tool_result>` : ''} | ||
| </request_context> | ||
|
|
||
| <workflow_instructions> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,10 +25,10 @@ export const assistant = new Elysia({ prefix: '/v1/assistant' }) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .post( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '/stream', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async ({ body, user }: { body: AssistantRequestType; user: User }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { message, website_id, model, context } = body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { messages, websiteId, model } = body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const websiteValidation = await validateWebsite(website_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const websiteValidation = await validateWebsite(websiteId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!websiteValidation.success) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
sbansal1999 marked this conversation as resolved.
Comment on lines
27
to
33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Ensure websiteId in body matches authenticated header to prevent cross-website access The route validates the website using the body.websiteId while auth likely relies on the X-Website-Id header. If these diverge and aren’t checked, a client could authenticate with one site and attempt to query another. Enforce equality. Apply this diff: - async ({ body, user }: { body: AssistantRequestType; user: User }) => {
+ async ({
+ body,
+ user,
+ headers,
+ }: {
+ body: AssistantRequestType;
+ user: User;
+ headers: Record<string, string>;
+ }) => {
const { messages, websiteId, model } = body;
+ const headerWebsiteId = headers['x-website-id'];
+ if (headerWebsiteId && headerWebsiteId !== websiteId) {
+ return createStreamingResponse(
+ createErrorResponse('websiteId mismatch between header and body')
+ );
+ }If websiteAuth already injects a canonical websiteId elsewhere, alternatively drop websiteId from the body and use the authenticated one everywhere. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add this, isn't it? @izadoesdev
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return createStreamingResponse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,11 +45,10 @@ export const assistant = new Elysia({ prefix: '/v1/assistant' }) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const assistantRequest: AssistantRequest = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| website_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| website_hostname: website.domain, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| messages, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| websiteId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| websiteHostname: website.domain, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model: model || 'chat', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const assistantContext: AssistantContext = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,23 @@ | ||
| import { t } from 'elysia'; | ||
|
|
||
| export const AssistantRequestSchema = t.Object({ | ||
| message: t.String(), | ||
| website_id: t.String(), | ||
| model: t.Optional( | ||
| t.Union([t.Literal('chat'), t.Literal('agent'), t.Literal('agent-max')]) | ||
| ), | ||
| context: t.Optional( | ||
| t.Object({ | ||
| previousMessages: t.Optional( | ||
| t.Array( | ||
| t.Object({ | ||
| role: t.Optional(t.String()), | ||
| content: t.String(), | ||
| }) | ||
| ) | ||
| export const AssistantRequestSchema = t.Object( | ||
| { | ||
| messages: t.Array( | ||
| t.Object( | ||
| { | ||
| role: t.Union([t.Literal('user'), t.Literal('assistant')]), | ||
| content: t.String(), | ||
| }, | ||
| { additionalProperties: false } | ||
| ), | ||
| }) | ||
| ), | ||
| }); | ||
| { minItems: 1 } | ||
| ), | ||
| websiteId: t.String(), | ||
| model: t.Optional( | ||
| t.Union([t.Literal('chat'), t.Literal('agent'), t.Literal('agent-max')]) | ||
| ), | ||
| }, | ||
| { additionalProperties: false } | ||
| ); | ||
|
|
||
| export type AssistantRequestType = { | ||
| message: string; | ||
| website_id: string; | ||
| model?: 'chat' | 'agent' | 'agent-max'; | ||
| context?: { | ||
| previousMessages?: Array<{ | ||
| role?: string; | ||
| content: string; | ||
| }>; | ||
| }; | ||
| }; | ||
| export type AssistantRequestType = typeof AssistantRequestSchema.static; |
Uh oh!
There was an error while loading. Please reload this page.