ทุกวันนี้คนรู้จัก OpenAI ซึ่งOpenAI ก็เป็นหนึ่งในLLMs ที่ใช้กันอย่างแพร่หลาย ซึ่ง LLMs หรือ Large Language Models เป็นระบบปัญญาประดิษฐ์ขั้นสูงที่ถูกออกแบบมาเพื่อเข้าใจและสร้างภาษามนุษย์ พวกมันมีความสามารถในการจัดการกับงานที่เกี่ยวข้องกับภาษาในรูปแบบต่างๆ ได้อย่างเชี่ยวชาญ ตั้งแต่การตอบคำถาม, การเขียนข้อความ, การแปลภาษา, ไปจนถึงการสร้างสรรค์เนื้อหาใหม่ๆ
หนึ่งในตัวอย่างที่โดดเด่นของ LLMs คือ Generative Pre-trained Transformer (GPT) จาก OpenAI, ซึ่งมีหลายเวอร์ชันเช่น GPT-3 และ GPT-4 ที่ใช้งานในปัจจุบัน
ซึ่งเราก็นำ LLMs ไปใช้งาน, ในโปรเจ็คครั้งนี้พวกเราได้นำ LLMs มาใช้ในให้ LLMs สร้าง Task งานให้กับบุคคล Role ต่างๆ ซึ่งประกอบด้วย Frontend, Backend และ Designer
สำหรับใครที่สนใจก็มาลองเรียน course กับพวกเราได้ The AI Curator | 6-AI Projects for non-AI developer ที่สำคัญฟรีนะไม่มีค่าใช้จ่าย 😎
Implement input box for user prompts
ในการ Implement input เราจะใช้ library react-hook-form, @hookform/resolvers/zod และ 'zod'
ซึ่งในการขั้นแรกเราจะทำการสร้าง schema ขึ้นมา
ตัวของ schema จะประกอบด้วย query สำหรับ input และ bot สำหรับใช้แสดง message error สำหรับตัว bot
export const askScheme = z.object({
query: z.string().trim().min(1, { message: 'Please enter your message' }),
bot: z.string({}).optional(),
})
เมื่อเราทำการสร้าง schema เเล้วเราจะทำการสร้าง type interface สำหรับ Form นี้
export interface IOpenAIForm extends z.infer<typeof askScheme> {}
หลังจากนั้นเราจะทำการสร้าง methods ขึ้นมา
const methods = useForm<IOpenAIForm>({
resolver: zodResolver(askScheme),
shouldFocusError: true,
defaultValues: {
query: '',
},
})
const { handleSubmit, setError, setValue } = methods
const onSubmit = async (data: IOpenAIForm) => {
...TO DO Something
}
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
...
</FormProvider>
)
จากตัวของ schema จะทำการ validate input ที่เรารับมาจาก user โดย หาก user ทำการ submit โดยไม่พิมพ์จะแสดง Error message ใน user interface ว่า 'Please enter your message'
ซึ่งใน component input จะใช้ lib ของ 'react-hook-form'และใช้ useFormContext, Controller
ในการจัดการ input
import { InputHTMLAttributes } from 'react'
import { useFormContext, Controller } from 'react-hook-form'
import { twMerge } from 'tailwind-merge'
// ----------------------------------------------------------------------
interface IProps extends InputHTMLAttributes<HTMLInputElement> {
name: string
helperText?: string
label?: string
}
const RHFTextField = ({
name,
helperText,
label,
className,
...other
}: IProps) => {
const { control } = useFormContext()
return (
<Controller
name={name}
control={control}
render={({ field, fieldState: { error } }) => (
<div className='w-full flex flex-col gap-y-2 '>
{label && <label className='text-sm font-semibold'>{label}</label>}
<input
{...field}
{...other}
className={twMerge(
'outline-none w-full border border-gray-200 rounded-lg px-2 py-1',
className
)}
/>
{(!!error || helperText) && (
<div className={twMerge(error?.message && 'text-rose-500 text-sm')}>
{error?.message || helperText}
</div>
)}
</div>
)}
/>
)
}
export default RHFTextField
ซึ่งในส่วนของการเรียกใช้ input เราจะใส่ แอตทริบิวต์ name เป็น query เหมือนใน schema ที่เราได้ทำการประกาศตัวแปรในschema นั้นไว้
const methods = useForm<IOpenAIForm>({
resolver: zodResolver(askScheme),
shouldFocusError: true,
defaultValues: {
query: '',
},
})
const { handleSubmit, setError, setValue } = methods
const onSubmit = async (data: IOpenAIForm) => {
console.log(data)
}
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
....
<RHFTextField
type='text'
placeholder='What do you need ? ...'
className='outline-none w-full border-none'
name='query'
/>
<button
type='submit'
disabled={isLoading}
className='text-gray-400 disabled:text-gray-200'
>Submit</button>
</FormProvider>
)
เมื่อเราทำการ กด submit ถ้า input validate ถูกต้องเเล้วเราจะเห็นข้อมูลที่เรา log มาจาก func onSubmit ซึ่งเราจะนำข้อมูลที่เราได้ไปเชื่อต่อกับ backend ได้เเล้ว
Handle API integration for chained-LLM agents & Display output from each API
ในการต่อกับ Backend เราจะก็จะมี state ต่างๆ เช่น loading, error เป็นต้น ซึ่งในการเชื่อมกับ API เราจะทำการ set default base URL ไว้
axios.defaults.baseURL = process.env.NEXT_PUBLIC_API
เมื่อทำการ set เสร็จเเล้ว เราจะทำการเชื่อม API
const onSubmit = async (data: IOpenAIForm) =< {
try {
const { data: result } = await axios.post(
`/query?question=${data.query}`,
undefined,
)
console.log(result) //ค่าที่ได้จาก API
//TO DO Something
} catch (error) {
const err = error as AxiosError<{ detail: string }<
setError('bot', {
message: err?.response?.data?.detail ?? 'Something went wrong',
})
}
}
เราจะใช้ import { useFormContext } from 'react-hook-form' ในการจัดการ state loading และ erorrs
const {
formState: { isSubmitting, errors },
} = useFormContext()
ซึ่งถ้าหาก API Error มาเราจะแสดง Message Error ที่ bot และ display ในส่วนของ Loading ด้วย เราก็จะเรียกใช้ state จาก 'useFormContext' เพื่อแสดง ซึ่งค่าที่เราใช้จะมีค่า isSubmitting, errors
export default function ChatWidget({ answer }: { answer: ChatProps[] }) {
const chatWindowRef = useRef<HTMLDivElement>(null)
const {
formState: { isSubmitting, errors },
} = useFormContext()
return (
<div className='h-full flex flex-col w-full'>
<Header />
<ChatWindow
messages={answer}
isLoading={isSubmitting}
error={errors?.bot?.message as string}
chatWindowRef={chatWindowRef}
/>
<ChatInput isLoading={isSubmitting} />
</div>
)
}
ซึ่งใน Component ChatWindow เราจะทำการจัดการ state ต่างๆ เพื่อแสดงในส่วนของ UI รวมถึงการแสดงผลลัพธ์จาก API ที่เราได้มาด้วย
import { useEffect } from 'react'
import Image from 'next/image'
import { CopyClipboard } from './CopyClipboard'
interface Message {
role: 'user' | 'ai'
message: string
id: string
raw: string
}
interface ChatWindowProps {
messages: Message[]
isLoading?: boolean
error?: string
chatWindowRef: any | null
}
export const ChatWindow: React.FC<ChatWindowProps> = ({
messages,
isLoading,
error,
chatWindowRef,
}) => {
useEffect(() => {
if (
chatWindowRef !== null &&
chatWindowRef?.current &&
messages.length > 0
) {
chatWindowRef.current.scrollTop = chatWindowRef.current.scrollHeight
}
}, [messages.length, chatWindowRef])
return (
<div
ref={chatWindowRef}
className='flex-1 overflow-y-auto p-4 space-y-8'
id='chatWindow'
>
{messages.map((item, index) => (
<div key={item.id} className='w-full'>
{item.role === 'user' ? (
<div className='flex gap-x-8 '>
<div className='min-w-[48px] min-h-[48px]'>
<Image
src='/img/chicken.png'
width={48}
height={48}
alt='user'
/>
</div>
<div>
<p className='font-bold'>User</p>
<p>{item.message}</p>
</div>
</div>
) : (
<div className='flex gap-x-8 w-full'>
<div className='min-w-[48px] min-h-[48px]'>
<Image
src='/img/robot.png'
width={48}
height={48}
alt='robot'
/>
</div>
<div className='w-full'>
<div className='flex justify-between mb-1 w-full '>
<p className='font-bold'>Ai</p>
<div />
<CopyClipboard content={item.raw} />
</div>
<div
className='prose whitespace-pre-line'
dangerouslySetInnerHTML={{ __html: item.message }}
/>
</div>
</div>
)}
</div>
))}
{isLoading && (
<div className='flex gap-x-8 w-full mx-auto'>
<div className='min-w-[48px] min-h-[48px]'>
<Image src='/img/robot.png' width={48} height={48} alt='robot' />
</div>
<div>
<p className='font-bold'>Ai</p>
<div className='mt-4 flex space-x-2 items-center '>
<p>Hang on a second </p>
<span className='sr-only'>Loading...</span>
<div className='h-2 w-2 bg-blue-600 rounded-full animate-bounce [animation-delay:-0.3s]'></div>
<div className='h-2 w-2 bg-blue-600 rounded-full animate-bounce [animation-delay:-0.15s]'></div>
<div className='h-2 w-2 bg-blue-600 rounded-full animate-bounce'></div>
</div>
</div>
</div>
)}
{error && (
<div className='flex gap-x-8 w-full mx-auto'>
<div className='min-w-[48px] min-h-[48px]'>
<Image src='/img/error.png' width={48} height={48} alt='error' />
</div>
<div>
<p className='font-bold'>Ai</p>
<p className='text-rose-500'>{error}</p>
</div>
</div>
)}
</div>
)
}
Implement process continuation functionality
หลังจากที่เราต่อกับ Backend เรียบร้อยเเล้วเราจะต้องทำการ เก็บ result message ที่เราได้จาก API ออกมา ซึ่งเราจะทำการสร้าง state ไว้สำหรับเก็บ answer ของ user และ bot
export default function ChatBotDemo() {
const [answer, setAnswer] = useState<ChatProps[]>([])
const methods = useForm<IOpenAIForm>({
resolver: zodResolver(askScheme),
shouldFocusError: true,
defaultValues: {
query: '',
},
})
const { handleSubmit, setError, setValue } = methods
const onSubmit = async (data: IOpenAIForm) => {
try {
const id = answer.length
setAnswer((prevState) => [
...prevState,
{
id: id.toString(),
role: 'user',
message: data.query,
raw: '',
},
])
setValue('query', '')
const { data: result } = await axios.post(
`/query?question=${data.query}`,
undefined
)
setAnswer((prevState) => [
...prevState,
{
id: (prevState.length + 1).toString(),
role: 'ai',
message: result.raw,
raw: result.json.customer_need,
},
])
} catch (error) {
const err = error as AxiosError<{ detail: string }>
setError('bot', {
message: err?.response?.data?.detail ?? 'Something went wrong',
})
}
}
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<div className='flex justify-center flex-col items-center bg-white mx-auto max-w-7xl h-screen '>
<ChatWidget answer={answer} />
</div>
</FormProvider>
)
}
ซึ่งเมือเราถามกับ AI ก็ได้ เช่น เราอยากสร้าง Blog ข่าว ตัว LLMs Agent จะทำการตอบ Task การทำงานของแต่ละ Role เช่น Design, Frontend และ Backend