11import { graphql } from "@/gql" ;
22import { getClient } from "@/lib/apollo.rsc" ;
3- import { checkAuthorizedStatus } from "@/lib/auth.rsc" ;
3+ import { getAuthorizedUserInfo } from "@/lib/auth.rsc" ;
44import { anthropic , type AnthropicProviderOptions } from "@ai-sdk/anthropic" ;
55import { convertToModelMessages , stepCountIs , streamText , tool , type UIMessage } from "ai" ;
6+ import { withTracing } from "@posthog/ai"
67import { NextResponse } from "next/server" ;
78import { z } from "zod" ;
9+ import { createPostHogClient } from "@/lib/posthog.rsc" ;
810
911export const maxDuration = 30 ;
1012
@@ -72,8 +74,8 @@ interface ChatRouteRequest {
7274}
7375
7476export async function POST ( req : Request ) {
75- const authorized = await checkAuthorizedStatus ( [ "*" , "ai" ] ) ;
76- if ( ! authorized ) {
77+ const userInfo = await getAuthorizedUserInfo ( [ "*" , "ai" ] ) ;
78+ if ( ! userInfo ) {
7779 return new NextResponse ( "Unauthorized" , { status : 401 } ) ;
7880 }
7981
@@ -98,104 +100,115 @@ export async function POST(req: Request) {
98100 . replace ( "{{QUESTION_DIFFICULTY}}" , data . question . difficulty )
99101 . replace ( "{{QUESTION_CATEGORY}}" , data . question . category ) ;
100102
101- const result = streamText ( {
102- model : anthropic ( "claude-sonnet-4-20250514" ) ,
103- providerOptions : {
104- anthropic : {
105- thinking : { type : "enabled" , budgetTokens : 12000 } ,
106- } satisfies AnthropicProviderOptions ,
107- } ,
108- messages : convertToModelMessages ( messages ) ,
109- system : preparedPrompt ,
110- stopWhen : stepCountIs ( 10 ) ,
111- tools : {
112- getMyAnswer : tool ( {
113- description :
114- "取得使用者最後提交的答案結果,包括查詢結果、錯誤訊息和狀態。如果使用者問關於他們的答案,使用這個工具。" ,
115- inputSchema : z . object ( { } ) ,
116- execute : async ( ) => {
117- const { data, error } = await apollo . query ( {
118- query : USER_ANSWER_RESULT ,
119- variables : { id : questionId } ,
120- errorPolicy : "all" ,
121- } ) ;
122- if ( ! data ?. question ) {
123- return { error : "無法取得題目資料" , details : error ?. message } ;
124- }
125-
126- const { lastSubmission } = data . question ;
127- if ( ! lastSubmission ) {
128- return { error : "使用者尚未提交答案" } ;
129- }
130-
131- return {
132- status : lastSubmission . status ,
133- submittedCode : lastSubmission . submittedCode ,
134- queryResult : lastSubmission . queryResult
135- ? {
136- columns : lastSubmission . queryResult . columns ,
137- rows : lastSubmission . queryResult . rows ,
138- }
139- : null ,
140- error : lastSubmission . error ,
141- } ;
142- } ,
143- } ) ,
144- getCorrectAnswer : tool ( {
145- description : "取得題目的正確答案結果,你可以對照和使用者的答案差異。" ,
146- inputSchema : z . object ( { } ) ,
147- execute : async ( ) => {
148- const { data, error } = await apollo . query ( {
149- query : CORRECT_ANSWER_RESULT ,
150- variables : { id : questionId } ,
151- errorPolicy : "all" ,
152- } ) ;
153- if ( ! data ?. question ) {
154- return { error : "無法取得題目資料" , details : error ?. message } ;
155- }
156-
157- return {
158- queryResult : data . question . referenceAnswerResult
159- ? {
160- columns : data . question . referenceAnswerResult . columns ,
161- rows : data . question . referenceAnswerResult . rows ,
162- }
163- : null ,
164- } ;
165- } ,
166- } ) ,
167- getQuestionSchema : tool ( {
168- description : "取得題目的資料庫結構,你可以用這個數據輔助了解 SQL 結構。" ,
169- inputSchema : z . object ( { } ) ,
170- execute : async ( ) => {
171- const { data, error } = await apollo . query ( {
172- query : QUESTION_SCHEMA ,
173- variables : { id : questionId } ,
174- errorPolicy : "all" ,
175- } ) ;
176- if ( ! data ?. question ) {
177- return { error : "無法取得題目資料" , details : error ?. message } ;
178- }
179-
180- return {
181- schema : data . question . database . structure
182- ? {
183- tables : data . question . database . structure . tables . map ( ( table ) => ( {
184- name : table . name ,
185- columns : table . columns ,
186- } ) ) ,
187- }
188- : null ,
189- } ;
190- } ,
191- } ) ,
192- webSearch : anthropic . tools . webSearch_20250305 ( {
193- maxUses : 5 ,
194- } ) ,
195- } ,
196- } ) ;
197-
198- return result . toUIMessageStreamResponse ( ) ;
103+ const model = anthropic ( "claude-sonnet-4-20250514" ) ;
104+ const posthogClient = await createPostHogClient ( ) ;
105+
106+ try {
107+ const tracedModel = withTracing ( model , posthogClient , {
108+ posthogDistinctId : userInfo . sub ,
109+ } ) ;
110+
111+ const result = streamText ( {
112+ model : tracedModel ,
113+ providerOptions : {
114+ anthropic : {
115+ thinking : { type : "enabled" , budgetTokens : 12000 } ,
116+ } satisfies AnthropicProviderOptions ,
117+ } ,
118+ messages : convertToModelMessages ( messages ) ,
119+ system : preparedPrompt ,
120+ stopWhen : stepCountIs ( 10 ) ,
121+ tools : {
122+ getMyAnswer : tool ( {
123+ description :
124+ "取得使用者最後提交的答案結果,包括查詢結果、錯誤訊息和狀態。如果使用者問關於他們的答案,使用這個工具。" ,
125+ inputSchema : z . object ( { } ) ,
126+ execute : async ( ) => {
127+ const { data, error } = await apollo . query ( {
128+ query : USER_ANSWER_RESULT ,
129+ variables : { id : questionId } ,
130+ errorPolicy : "all" ,
131+ } ) ;
132+ if ( ! data ?. question ) {
133+ return { error : "無法取得題目資料" , details : error ?. message } ;
134+ }
135+
136+ const { lastSubmission } = data . question ;
137+ if ( ! lastSubmission ) {
138+ return { error : "使用者尚未提交答案" } ;
139+ }
140+
141+ return {
142+ status : lastSubmission . status ,
143+ submittedCode : lastSubmission . submittedCode ,
144+ queryResult : lastSubmission . queryResult
145+ ? {
146+ columns : lastSubmission . queryResult . columns ,
147+ rows : lastSubmission . queryResult . rows ,
148+ }
149+ : null ,
150+ error : lastSubmission . error ,
151+ } ;
152+ } ,
153+ } ) ,
154+ getCorrectAnswer : tool ( {
155+ description : "取得題目的正確答案結果,你可以對照和使用者的答案差異。" ,
156+ inputSchema : z . object ( { } ) ,
157+ execute : async ( ) => {
158+ const { data, error } = await apollo . query ( {
159+ query : CORRECT_ANSWER_RESULT ,
160+ variables : { id : questionId } ,
161+ errorPolicy : "all" ,
162+ } ) ;
163+ if ( ! data ?. question ) {
164+ return { error : "無法取得題目資料" , details : error ?. message } ;
165+ }
166+
167+ return {
168+ queryResult : data . question . referenceAnswerResult
169+ ? {
170+ columns : data . question . referenceAnswerResult . columns ,
171+ rows : data . question . referenceAnswerResult . rows ,
172+ }
173+ : null ,
174+ } ;
175+ } ,
176+ } ) ,
177+ getQuestionSchema : tool ( {
178+ description : "取得題目的資料庫結構,你可以用這個數據輔助了解 SQL 結構。" ,
179+ inputSchema : z . object ( { } ) ,
180+ execute : async ( ) => {
181+ const { data, error } = await apollo . query ( {
182+ query : QUESTION_SCHEMA ,
183+ variables : { id : questionId } ,
184+ errorPolicy : "all" ,
185+ } ) ;
186+ if ( ! data ?. question ) {
187+ return { error : "無法取得題目資料" , details : error ?. message } ;
188+ }
189+
190+ return {
191+ schema : data . question . database . structure
192+ ? {
193+ tables : data . question . database . structure . tables . map ( ( table ) => ( {
194+ name : table . name ,
195+ columns : table . columns ,
196+ } ) ) ,
197+ }
198+ : null ,
199+ } ;
200+ } ,
201+ } ) ,
202+ webSearch : anthropic . tools . webSearch_20250305 ( {
203+ maxUses : 5 ,
204+ } ) ,
205+ } ,
206+ } ) ;
207+
208+ return result . toUIMessageStreamResponse ( ) ;
209+ } finally {
210+ await posthogClient . shutdown ( ) ;
211+ }
199212}
200213
201214export const prompt =
0 commit comments