π€ Computer Science
Generative UI & AI
1. Generative UIλ?
μΈκ³΅μ§λ₯μ΄ μ€μκ°μΌλ‘ μ¬μ©μ μꡬμ λ§₯λ½μ λ§κ² λμ μΌλ‘ μμ±νλ μ¬μ©μ μΈν°νμ΄μ€
Generative UI vs. AI-Assisted Design
νΉμ§ | Generative UI | AI-Assisted Design |
λͺ©μ | μ¬μ©μ λ§μΆ€ν μΈν°νμ΄μ€ μ 곡 | λμμΈ λ° κ΅¬ν κ³Όμ κ°μν |
λμ | μ΅μ’
μ¬μ©μ | λμμ΄λμ μ ν ν |
μν₯ | κ°μΈνλ μΈν°νμ΄μ€ μ 곡 | λμμΈ νλ‘μΈμ€ κ°μν |
Β
Example
Scenario: μ¬μ©μκ° ν곡νΈμ μμ½ν λ κ°λ³ μꡬμ λ§μΆ λ§μΆ€ν μΈν°νμ΄μ€ μ 곡
- κ°μΈν μμ: μ½κΈ° μ¬μ΄ ν°νΈ λ° μμ λλΉ, μ νΈνλ μ’μ μ ν, λΉν μ΅μ μ°μ μμ (λΉμ©, μ¬ν μκ° λ±)
Β
ν¨λ¬λ€μ μ ν: κ²°κ³Ό μ§ν₯μ λμμΈ
- μ¬μ©μ λͺ©νμ μ΅μ’ κ²°κ³Όμ μ΄μ μ λ§μΆ κ²½ν λμμΈ.
- μΈν°νμ΄μ€ μ€κ³λ³΄λ€ μ¬μ©μ λͺ©ν λ¬μ±μ μ€μ .
- λμμ΄λλ κ°λ³ μ¬μ©μ μꡬμ λ§μΆ μΈν°νμ΄μ€ μμ±.
- λμμ΄λλ μ¬μ©μμ λͺ©νμ λΉμ¦λμ€ λͺ©νλ₯Ό μ΄ν΄νκ³ , μ΄λ₯Ό AI μμ€ν μ λ°μνμ¬ μΈν°νμ΄μ€λ₯Ό μ€κ³ν¨
2. ꡬνν΄λ³΄κΈ° with Vercel AI SDK
Vercel AI SDKλ?
AI μ΄ν리μΌμ΄μ
μ νμ
μ€ν¬λ¦½νΈ κΈ°λ° νλ‘μ νΈμμ κ°λ ₯νκ² λΉλν μ μλλ‘ λ§λ ν΄ν·. LLM λͺ¨λΈκ³Ό ν΄λΌμ΄μΈνΈ μ¬μ΄μ λ°μ΄ν° νλ¦μ μ½κ² μ μ΄ν μ μλ€. μ¬λ¬ LLM λͺ¨λΈμ API params λ₯Ό νλλ‘ ν΅ν©νμ¬ νμ€ννμ¬ λΉ λ₯΄κ² ꡬνν μ μλ€λ μ₯μ μ΄ μλ€.
- AI SDK Core, SDK UI, SDK RSC
import { generateText } from "ai" import { openai } from "@ai-sdk/openai" const { text } = await generateText({ model: openai("gpt-4-turbo"), // or anthropic("claude-3-opus-20240229"), etc. prompt: "What is love?" })
Tool μ΄λ?
- LLM λͺ¨λΈμ λλΌμ΄ μμ± κΈ°λ₯μ κ°μ§μ§λ§, μ¬λ¬ λ¨κ³λ₯Ό κ±°μ³μΌ νλ μμ μ΄λ μΈλΆ μΈκ³μμ μνΈ μμ©μ΄ μ΄λ €μ. β μ¬μ©μκ° λ§λ λꡬλ€μ LLMμκ² μ μνκ³ , LLMμ μ΄λ€ λꡬλ₯Ό μ νν μ§ κ²°μ ν ν, λꡬμ input λ§μ κ²°μ ν¨
import { z } from 'zod'; import { generateText, tool } from 'ai'; const result = await generateText({ model: yourModel, tools: { // λ μ¨ κ²°μ λꡬ weather: tool({ description: 'Get the weather in a location', parameters: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ( const { data } = axios.get(`/weather?location=${location}`); return { data, location }; ), }), recommendedPlace: (...), }, toolChoice: 'required', // λͺ¨λΈμ΄ λ°λμ λꡬλ₯Ό μ ννλλ‘ μ§μ prompt: 'What is the weather in San Francisco and what attractions should I visit?', });
ꡬν μμ: ν΄λ ꡬ쑰 λ° λ°μ΄ν° νλ¦
Scenario: λ©λ΄λ₯Ό νμνμ§ μκ³ λΉ λ₯΄κ² λμμ μνν μ μλ μ μ¬μ λ΄μ λ§λ€μ! (μ‘κΈ, κ²°μ λ΄μ νμΈ λ±)
|-- playground // λΌμ°ν κ²½λ‘ | |-- _action // Server function | | |-- index.tsx | |-- _component | | |-- chat // Generative UI ν΄λ | | | |-- UserMessage.tsx | | | |-- BotMessage.tsx | | | |-- TranslateMessage.tsx | | | |-- (...) | | |-- SectionPlaygroundAI.tsx // UI State Render | | |-- (...) | |-- layout.tsx | |-- page.tsx // AI Provider κ°μΈκΈ°
AI State vs. UI State
UI State: ν΄λΌμ΄μΈνΈ μ»΄ν¬λνΈμ λ λλ§λ λΆλΆ μ 체λ₯Ό μν κ΄λ¦¬νλ€.
{ id: number, display: ReactNode }
AI State: Server functionμμ νμμ λ§μΆμ΄ LLM λͺ¨λΈμ΄ μ΄μ νμ€ν 리λ₯Ό μ΄ν΄ν μ μλλ‘ μν κ΄λ¦¬νλ€.
{ id: string, role: LLMRole, content: string | Array<ToolCallPart> | (...) }
Action: λ Stateλ₯Ό λ³νμν¬ Server functionμ λ±λ‘νλ€.
// action.tsx export const AI = createAI({ actions: { submitUserMessage, }, initialUIState, initialAIState, }); // page.tsx <main className="p-10 flex flex-col min-h-screen z-10 relative"> <h1 className="text-3xl z-10">Playground</h1> // provider λ‘ κ°μΈκΈ° <AI> <SectionPlaygroundAI /> </AI> </div> </main>
Client: UI State λ λλ§νκΈ° / μλ‘μ΄ input λ±λ‘νκΈ°
'use client'; const SectionPlaygroundAI: React.FC = () => { const [messages, setMessages] = useUIState<typeof AI>(); const { submitUserMessage } = useActions<typeof AI>(); const [form] = Form.useForm(); const handleFinish = async ({ currentInput }: { currentInput: string }) => { // UI μν μ λ°μ΄νΈ setMessages((currentMessages) => [ ...currentMessages, { id: Date.now(), type: 'user', display: <UserMessage>{currentInput}</UserMessage> }, ]); try { // Action νΈμΆ const responseMessage = await submitUserMessage(currentInput); // responseλ₯Ό UI μνμ μ λ°μ΄νΈ setMessages((currentMessages) => [...currentMessages, responseMessage]); form.resetFields(); } catch (e) { message.error('AI μλ²μμ ν΅μ μ΄ μννμ§ μμ΅λλ€.'); } }; return ( <section className="rounded-2xl flex flex-col flex-wrap p-6 gap-5 border border-solid border-indigo-500 relative box-content"> <ul className="flex flex-col gap-4 max-h-[calc(100vh-220px)] mb-16 overflow-y-auto whitespace-pre-line leading-snug"> {(...) // UI λ©μΈμ§ 리μ€νΈ λ λ} </ul> <Form layout="inline" onFinish={handleFinish}> {(...) // μΈν μ λ ₯ νΌ} </Form> </article> </section> ); }; export default SectionPlaygroundAI;
Server: AI State κ΄λ¦¬νκΈ° / Action μ μνκΈ°
const submitUserMessage = async (content: string) => { 'use server'; const aiState = getMutableAIState<typeof AI>(); aiState.update(...); // μ μ input νμ€ν 리μ μΆκ° // streamable UI μ μ const reply = createStreamableUI( <BotMessage tool="Loading..." noBg> <Skeleton className="w-2/3 mt-2" active /> </BotMessage>); const defaultParameters = z.object({ isSelect: z.boolean(), reason: z.string().describe('The reason why you choose this tool. Answer In Korean.'), }); const { toolResults, toolClls, text } = await generateText({ model: openai('gpt-4o'), messages: aiState.get().map((info: any) => ({ role: info.role, content: info.content, name: info.name, })), tools: { // streaming tool translateToKorean: { description: 'If user want to translate the input that from another language to Korean.', parameters: defaultParameters, // function name return execute: async (params) => params, }, // generating tool send: { description: 'send money to another company/user', parameters: z.object({ amount: z.number().optional(), receiverName: z.string().describe('name of the receiver').optional(), reason: z.string().describe('The reason why you choose this tool. Answer In Korean.'), }), execute: async ({ amount, receiverName, reason }) => { let label = `${receiverName}λκ» \n ${amount?.toLocaleString()}μμ 보λΌκ²μ.`; if (!receiverName?.length) label = `${amount?.toLocaleString()}μμ \n μ΄λ λΆκ» μ‘κΈν κΉμ?`; if (!amount) label = `${receiverName}λκ» \n μΌλ§λ₯Ό μ‘κΈν κΉμ?`; reply.update( <BotMessage tool="μ‘κΈλ΄" reason={reason}> <SendMoneyMessage label={label} receiverName={receiverName} amount={amount} /> </BotMessage> ); reply.done(); return { reason }; }, }, }, }); const generativeUIActions: Record<any, (args: any) => Promise<any>> = { translateToKorean: async (content: string) => { const { partialObjectStream } = await streamObject({ model: openai('gpt-4-turbo'), system: 'You need to translate the input to Korean. Please also provide important word list.', prompt: content, schema: z.object({ originText: z.string().describe('The original language text.'), sentence: z.string().describe('KOREAN translation of the input.'), wordList: z.array( z.object({ word: z.string(), mean: z.string(), }) ), }), }); // κ°μ²΄ μ€νΈλ¦¬λ° λ‘μ§ for await (const partialObject of partialObjectStream) { reply.update( <BotMessage tool="λ²μλ΄" reason={toolResults?.[0]?.result?.reason}> <TranslateMessage originText={partialObject?.originText} sentence={partialObject?.sentence} wordList={partialObject?.wordList ?? []} /> </BotMessage> ); } reply.done(); }, }; // toolμ΄ μ νλμ§ μμ κ²½μ° if (toolResults.length === 0) { // μ΄μμ€ν΄νΈ μλ΅ νμ€ν 리 μΆκ° aiState.done([...aiState.get(), { role: 'assistant', content: text }]); reply.update(<BotMessage>{text}</BotMessage>); reply.done(); } else { if (['translateToKorean', (...)].includes(toolResults?.[0]?.toolName)) { generativeUIActions[toolResults?.[0]?.toolName]?.(content); } aiState.done(...); // function callingμ λν νμ€ν 리 μΆκ° } // ν΄λΌμ΄μΈνΈ UI λ¦¬ν΄ return { id: Date.now(), display: reply.value, }; };
Result
Β
3. Reference
Β