Spaces:
Running
Running
| [ | |
| { | |
| "id": "docs-lab-1", | |
| "title": "[๋จ๊ณ 1] Ollama โ ๋ก์ปฌ LLM ์ค์น", | |
| "task_description": "**์ํฉ**\nOpenAI API๋ก ๊ฐ๋ฐํ๋ ์ค ์ฒญ๊ตฌ์๊ฐ ๋์์ต๋๋ค. ํ ์คํธ๋ง ์์ญ ๋ฒ ํ๋๋ฐ ์๊ธ์ด ์ฒญ๊ตฌ๋์ต๋๋ค. ๋ก์ปฌ์์ ๋ฌด๋ฃ๋ก ๋๋ฆด ์ ์์๊น์?\n\n**๋ฌธ์ **\nAPI ํค ์์ด LLM์ ์ฐ๋ ค๋ฉด ๋ชจ๋ธ์ ์ง์ ๋ก์ปฌ์ ๋ด๋ ค๋ฐ์์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nOllama๋ก `llama3.2` ๋ชจ๋ธ์ ๋ก์ปฌ์ ๋ค์ด๋ก๋ํ์ธ์. (๋ํ ์์ด ๋ค์ด๋ก๋๋ง)\n\n> `run`์ด ์๋, ๋ค์ด๋ก๋๋ง ํ๋ ๋ช ๋ น์ด์ ๋๋ค.", | |
| "expected_command": "ollama pull llama3.2", | |
| "grading_conditions": ["ollama pull", "llama3.2"], | |
| "hint": "`ollama pull <๋ชจ๋ธ๋ช >` ํ์์ ๋๋ค.", | |
| "explanation": "`ollama pull`์ ๋ชจ๋ธ ํ์ผ์ ๋ก์ปฌ์ ๋ด๋ ค๋ฐ์ต๋๋ค. ์ดํ `ollama list`๋ก ๋ชฉ๋ก ํ์ธ, `ollama run llama3.2`๋ก ๋ํ๋ฅผ ์์ํ ์ ์์ต๋๋ค. API ํค๋, ์ธํฐ๋ท ์ฐ๊ฒฐ๋ ์์ด ์คํ๋ฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "API ํค ์์ด ollama pull ํ ๋ฒ์ผ๋ก ๋ก์ปฌ LLM ์คํ ํ๊ฒฝ์ด ์์ฑ๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-2", | |
| "title": "[๋จ๊ณ 2] Ollama REST API โ Python์์ ํธ์ถํ๊ธฐ", | |
| "task_description": "**์ํฉ**\n`ollama run llama3.2`๋ก ํฐ๋ฏธ๋ ๋ํ๋ ๋๋๋ฐ, ๋ด Python ์ฝ๋์์ LLM์ ํธ์ถํ๋ ค๋ ๋ฐฉ๋ฒ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.\n\n**๋ฌธ์ **\nCLI ๋ํ ๋ชจ๋๋ ์๋ํ๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค. ์ฝ๋์์ ํธ์ถํ๋ ค๋ฉด HTTP API๊ฐ ํ์ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\ncurl๋ก Ollama REST API๋ฅผ ์ง์ ํธ์ถํด ๋์์ ํ์ธํ์ธ์.\n- ์๋ํฌ์ธํธ: `http://localhost:11434/api/generate`\n- ๋ชจ๋ธ: `llama3.2`, ํ๋กฌํํธ: `'2+2๋?'`, ์คํธ๋ฆฌ๋ฐ ์์ด", | |
| "expected_command": "curl http://localhost:11434/api/generate -d '{\"model\":\"llama3.2\",\"prompt\":\"2+2๋?\",\"stream\":false}'", | |
| "grading_conditions": ["curl", "localhost:11434/api/generate", "stream", "false"], | |
| "hint": "`-d` ํ๋๊ทธ๋ก JSON ๋ฐ๋๋ฅผ ์ ๋ฌํฉ๋๋ค. `\"stream\": false`๋ฅผ ๋ฃ์ด์ผ ํ ๋ฒ์ ์๋ต์ด ์ต๋๋ค.", | |
| "explanation": "Ollama๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ค ๋จ์ ์คํธ๋ฆฌ๋ฐ์ผ๋ก ์๋ตํฉ๋๋ค. `\"stream\": false`๋ฅผ ๋ฃ์ผ๋ฉด ์์ฑ๋ ์๋ต์ ํ ๋ฒ์ ๋ฐ์ ์ ์์ด Python์์ ํ์ฑํ๊ธฐ ์ฝ์ต๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "Ollama REST API๋ OpenAI ํธํ ํ์์ด๋ผ ์ด๋ค ์ธ์ดยทํ๋ ์์ํฌ์์๋ ๋์ผํ ๋ฐฉ๋ฒ์ผ๋ก ํธ์ถํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-3", | |
| "title": "[๋จ๊ณ 3] Ollama ์ฑํ API โ ๋ํ ํ์คํ ๋ฆฌ ๊ตฌ์กฐ", | |
| "task_description": "**์ํฉ**\n`/api/generate`๋ก '์ด์ ์ ๋ด๊ฐ ๋ญ๋ผ๊ณ ํ์ด?'๋ฅผ ๋ฌผ์ด๋ดค๋๋ ๋ชจ๋ฅธ๋ค๊ณ ํฉ๋๋ค. ๋งค ์์ฒญ์ด ์์ ํ ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.\n\n**๋ฌธ์ **\n`/api/generate`๋ ๋จ์ผ ํ๋กฌํํธ๋ง ๋ฐ์ต๋๋ค. ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ๋ถ์ด๋ ค๋ฉด ๋ค๋ฅธ ์๋ํฌ์ธํธ๊ฐ ํ์ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`/api/chat`์ `messages` ๋ฐฐ์ด๋ก ํ์คํ ๋ฆฌ๋ฅผ ์ ๋ฌํฉ๋๋ค. `llama3.2`์๊ฒ `user` ๋ฉ์์ง `'ํ์ด์ฌ์ด ๋ญ์ผ?'`๋ฅผ ๋ณด๋ด์ธ์. (stream: false)", | |
| "expected_command": "curl http://localhost:11434/api/chat -d '{\"model\":\"llama3.2\",\"messages\":[{\"role\":\"user\",\"content\":\"ํ์ด์ฌ์ด ๋ญ์ผ?\"}],\"stream\":false}'", | |
| "grading_conditions": ["curl", "localhost:11434/api/chat", "messages", "role", "user"], | |
| "hint": "`/api/chat`์ `messages` ๋ฐฐ์ด๋ก ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ์ ๋ฌํฉ๋๋ค.", | |
| "explanation": "`messages` ๋ฐฐ์ด์ `{\"role\": \"user\", \"content\": \"...\"}` ํ์์ผ๋ก ์ด์ ๋ํ๋ฅผ ์์ผ๋ฉด ๋ฉํฐํด ๋ํ๊ฐ ๋ฉ๋๋ค. LangChain์ ์ฑํ ๋ชจ๋ธ๋ ๋ด๋ถ์ ์ผ๋ก ์ด ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "messages ๋ฐฐ์ด์ {role, content} ์์ ์๋ ๊ตฌ์กฐ๊ฐ ๋ชจ๋ LLM ๋ฉํฐํด ๋ํ์ ๊ธฐ๋ณธ ํ์์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-4", | |
| "title": "[๋จ๊ณ 4] .env โ API ํค๋ฅผ ์ฝ๋์์ ๋ถ๋ฆฌํ๊ธฐ", | |
| "task_description": "**์ํฉ**\n```python\napi_key = 'sk-abc123...'\n```\n์ด๋ ๊ฒ ์ฝ๋์ ์ง์ ์ผ๋ค๊ฐ GitHub์ pushํ๋๋ API ํค๊ฐ ๊ณต๊ฐ๋์ต๋๋ค. GitHub์ด ์๋์ผ๋ก ํ์งํด์ ํค๋ฅผ ์ฆ์ ๋ฌดํจํํ์ต๋๋ค.\n\n**๋ฌธ์ **\nAPI ํค๋ฅผ ์ฝ๋์ ํ๋์ฝ๋ฉํ๋ฉด ๋ฒ์ ๊ด๋ฆฌ ์์คํ ์ ๋ ธ์ถ๋ฉ๋๋ค. `.gitignore`์ ์ถ๊ฐํ ๋ณ๋ ํ์ผ์ ๋ณด๊ดํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`.env` ํ์ผ์ ๋ก๋ํ๊ณ `OPENAI_API_KEY`๋ฅผ ์ฝ๋ ์ฝ๋ 2์ค์ ์ ๋ ฅํ์ธ์.\n\n```python\nfrom dotenv import load_dotenv\nimport os\n# ์ฌ๊ธฐ์ ์ ๋ ฅํ์ธ์\n```", | |
| "expected_command": "load_dotenv()\napi_key = os.getenv('OPENAI_API_KEY')", | |
| "grading_conditions": ["load_dotenv()", "os.getenv", "OPENAI_API_KEY"], | |
| "hint": "`load_dotenv()`๋ฅผ ๋ฐ๋์ ๋จผ์ ํธ์ถํด์ผ `os.getenv()`๊ฐ ๊ฐ์ ์ฝ์ ์ ์์ต๋๋ค.", | |
| "explanation": "`load_dotenv()` ํธ์ถ ์ ์ `os.getenv()`๋ฅผ ์ฐ๋ฉด ํญ์ `None`์ด ๋ฐํ๋ฉ๋๋ค. `.env` ํ์ผ์ `.gitignore`์ ์ถ๊ฐํ๊ณ , `.env.example`์๋ ํค ์ด๋ฆ๋ง ์ ์ด ํ์ ๊ณต์ ํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "load_dotenv() โ os.getenv() ์์๊ฐ ๋ชจ๋ Python ํ๋ก์ ํธ์ ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌ ํ์ค์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-5", | |
| "title": "[๋จ๊ณ 5] ChatPromptTemplate โ ํ๋กฌํํธ ๊ตฌ์กฐํ", | |
| "task_description": "**์ํฉ**\n```python\nresponse = llm.invoke(f'๋ฌธ์: {context}\\n์ง๋ฌธ: {question}')\n```\nf-string์ผ๋ก ํ๋กฌํํธ๋ฅผ ๋ง๋ค์๋๋ ๋ณ์ ์์๋ฅผ ์ค์ํ๊ธฐ ์ฝ๊ณ , ์์คํ ์ญํ (์ด์์คํดํธ ํ๋ฅด์๋)์ ์ง์ ํ ์ ์์ต๋๋ค.\n\n**๋ฌธ์ **\n๋จ์ ๋ฌธ์์ด ๋ฐฉ์์ ์ญํ ๊ตฌ๋ถ์ด ์๊ณ LangChain ํ์ดํ๋ผ์ธ์ ์ฐ๊ฒฐ๋์ง ์์ต๋๋ค.\n\n**ํด๊ฒฐ**\n์๋ ์กฐ๊ฑด์ผ๋ก `ChatPromptTemplate`์ ์์ฑํ์ธ์.\n- ์์คํ : `\"๋น์ ์ ๋ฌธ์ ๊ธฐ๋ฐ AI ์ด์์คํดํธ์ ๋๋ค. ์ปจํ ์คํธ: {context}\"`\n- ์ฌ์ฉ์: `\"{question}\"`\n\n๊ฒฐ๊ณผ๋ฅผ `prompt` ๋ณ์์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "prompt = ChatPromptTemplate.from_messages([\n (\"system\", \"๋น์ ์ ๋ฌธ์ ๊ธฐ๋ฐ AI ์ด์์คํดํธ์ ๋๋ค. ์ปจํ ์คํธ: {context}\"),\n (\"human\", \"{question}\")\n])", | |
| "grading_conditions": ["ChatPromptTemplate.from_messages", "system", "context", "human", "question"], | |
| "hint": "`ChatPromptTemplate.from_messages([(\"system\", \"...\"), (\"human\", \"...\")])` ํ์์ ๋๋ค.", | |
| "explanation": "ChatPromptTemplate์ ์ญํ ๋ณ ๋ฉ์์ง๋ฅผ ๊ตฌ์กฐํํ๊ณ ๋ณ์ ์๋ฆฌ(`{context}`, `{question}`)๋ฅผ ์์ ํ๊ฒ ๊ด๋ฆฌํฉ๋๋ค. `chain.invoke({\"context\": ..., \"question\": ...})`๋ก ์คํ ์ ์๋์ผ๋ก ์ฑ์์ง๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "ChatPromptTemplate์ ์ญํ (system/human)๊ณผ ๋ณ์({})๋ฅผ ๋ถ๋ฆฌํด ํ๋กฌํํธ๋ฅผ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ก ๋ง๋ญ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-6", | |
| "title": "[๋จ๊ณ 6] LCEL โ ์ฒด์ธ์ ํ ์ค๋ก", | |
| "task_description": "**์ํฉ**\n```python\nformatted = prompt.format(context=ctx, question=q)\nllm_result = llm.invoke(formatted)\nfinal = output_parser.parse(llm_result)\n```\n3๋จ๊ณ๋ฅผ ๋งค๋ฒ ์ง์ ์ฐ๊ฒฐํ๋ ์ฝ๋๊ฐ ๋ฐ๋ณต๋ฉ๋๋ค. ์ฒด์ธ์ด ๊ธธ์ด์ง์๋ก ๋ ๋ณต์กํด์ง๋๋ค.\n\n**๋ฌธ์ **\n๊ฐ ๋จ๊ณ๋ฅผ ์๋์ผ๋ก ์ฐ๊ฒฐํ๋ฉด ์ฝ๋๊ฐ ๊ธธ๊ณ ๋จ๊ณ ์ถ๊ฐ/๋ณ๊ฒฝ์ด ๋ฒ๊ฑฐ๋กญ์ต๋๋ค.\n\n**ํด๊ฒฐ**\n`prompt`, `llm`, `output_parser`๋ฅผ LCEL `|` ์ฐ์ฐ์๋ก ์ฐ๊ฒฐํด `chain` ๋ณ์์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "chain = prompt | llm | output_parser", | |
| "grading_conditions": ["chain", "prompt", "|", "llm", "|", "output_parser"], | |
| "hint": "์ ๋์ค ํ์ดํ์ฒ๋ผ `|`๋ก ์ฐ๊ฒฐํฉ๋๋ค.", | |
| "explanation": "`chain = prompt | llm | output_parser` ํ ์ค์ด ์์ 3์ค์ ๋์ฒดํฉ๋๋ค. `chain.invoke({\"context\": ..., \"question\": ...})`๋ก ์คํํ๋ฉด ์์๋๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. ๋จ๊ณ ์ถ๊ฐ๋ `| ์๋จ๊ณ`๋ฅผ ๋ถ์ด๋ฉด ๋ฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "| ํ์ดํ ์ฐ์ฐ์๋ก Runnable์ ์ฐ๊ฒฐํ๋ LCEL์ด LangChain ๋ชจ๋ ์ฒด์ธ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-7", | |
| "title": "[๋จ๊ณ 7] TextSplitter โ ํ ํฐ ์ด๊ณผ ํด๊ฒฐ", | |
| "task_description": "**์ํฉ**\n100ํ์ด์ง PDF ์ ์ฒด๋ฅผ LLM ํ๋กฌํํธ์ ๋ฃ์๋๋:\n```\nError: This model's maximum context length is 128000 tokens. \nYour message resulted in 210000 tokens.\n```\n๋ฌธ์๊ฐ ๊ธธ์๋ก LLM์ ํต์งธ๋ก ๋ฃ๋ ๊ฑด ๋ถ๊ฐ๋ฅํฉ๋๋ค.\n\n**๋ฌธ์ **\nLLM์ ํ ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋ ํ ํฐ ์์ ํ๊ณ๊ฐ ์์ต๋๋ค. ๋ฌธ์๋ฅผ ์๋ผ์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`RecursiveCharacterTextSplitter`๋ก ์ฒญํฌ ํฌ๊ธฐ 500, ์ค๋ฒ๋ฉ 50์ผ๋ก `docs`๋ฅผ ๋ถํ ํด `chunks`์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)\nchunks = splitter.split_documents(docs)", | |
| "grading_conditions": ["RecursiveCharacterTextSplitter", "chunk_size=500", "chunk_overlap=50", "split_documents"], | |
| "hint": "์์ฑ ํ `.split_documents(docs)`๋ฅผ ํธ์ถํฉ๋๋ค.", | |
| "explanation": "chunk_size=500์ ์ฒญํฌ๋น ์ต๋ 500์, chunk_overlap=50์ ์ธ์ ์ฒญํฌ๊ฐ 50์๋ฅผ ๊ณต์ ํฉ๋๋ค. ์ค๋ฒ๋ฉ์ด ์์ผ๋ฉด ์ฒญํฌ ๊ฒฝ๊ณ์์ ๋ฌธ์ฅ์ด ์๋ ค ๊ฒ์ ํ์ง์ด ๋จ์ด์ง๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "chunk_overlap์ด ์์ผ๋ฉด ์ฒญํฌ ๊ฒฝ๊ณ์์ ๋ฌธ๋งฅ์ด ์๋ ค ๊ฒ์ ํ์ง์ด ๋จ์ด์ง๋๋ค. ์ผ๋ฐ์ ์ผ๋ก chunk_size์ 10% ์ ๋๋ฅผ ์๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-8", | |
| "title": "[๋จ๊ณ 8] FAISS VectorStore โ ์๋ฏธ ๊ธฐ๋ฐ ๊ฒ์ ์ธ๋ฑ์ค ์์ฑ", | |
| "task_description": "**์ํฉ**\n๋ฌธ์๋ฅผ ์ฒญํฌ๋ก ์๋๋๋ฐ, ์ง๋ฌธ์ด ๋ค์ด์ฌ ๋๋ง๋ค 500๊ฐ ์ฒญํฌ๋ฅผ ์ ๋ถ LLM์ ๋ฃ์ผ๋ฉด ์ฒ์๊ณผ ๋๊ฐ์ด ํ ํฐ์ด ์ด๊ณผ๋ฉ๋๋ค.\n\n**๋ฌธ์ **\n\"๊ด๋ จ ์๋ ์ฒญํฌ๋ง\" ์ฐพ์์ผ ํฉ๋๋ค. ํค์๋ ๊ฒ์์ ๋์์ด๋ ์๋ฏธ์ ์ ์ฌ์ฑ์ ๋ชป ์ก์ต๋๋ค.\n\n**ํด๊ฒฐ**\n`chunks`์ `embeddings` ๋ชจ๋ธ๋ก FAISS VectorStore๋ฅผ ๋ง๋ค๊ณ , `./faiss_index`์ ์ ์ฅํ์ธ์. (๋ค์ ์คํ ์ ์ฌ์ฌ์ฉํ๊ธฐ ์ํด)", | |
| "expected_command": "db = FAISS.from_documents(chunks, embeddings)\ndb.save_local(\"./faiss_index\")", | |
| "grading_conditions": ["FAISS.from_documents", "chunks", "embeddings", "save_local"], | |
| "hint": "`FAISS.from_documents(๋ฌธ์, ์๋ฒ ๋ฉ๋ชจ๋ธ)`๋ก ์์ฑ, `.save_local(๊ฒฝ๋ก)`๋ก ์ ์ฅํฉ๋๋ค.", | |
| "explanation": "FAISS๋ ๊ฐ ์ฒญํฌ๋ฅผ ๋ฒกํฐ๋ก ๋ณํํด ์ธ๋ฑ์ค๋ฅผ ๋ง๋ญ๋๋ค. ์ง๋ฌธ๋ ๋ฒกํฐ๋ก ๋ณํํด ๊ฐ์ฅ ๊ฐ๊น์ด(์๋ฏธ์ ์ผ๋ก ์ ์ฌํ) ์ฒญํฌ๋ฅผ O(1)์ ๊ฐ๊น๊ฒ ์ฐพ์ต๋๋ค. `save_local`๋ก ์ ์ฅํ๋ฉด ๋ค์ ์คํ ์ ์๋ฒ ๋ฉ ์ฌ๊ณ์ฐ ์์ด ๋ก๋ํ ์ ์์ต๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "FAISS.from_documents()๊ฐ ์๋ฒ ๋ฉ ์์ฑ๊ณผ ์ธ๋ฑ์ค ๊ตฌ์ถ์ ํ ๋ฒ์ ์ฒ๋ฆฌํ๊ณ , save_local()๋ก ์ฌ์์ ์ ์ฌ๊ณ์ฐ์ ํผํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-9", | |
| "title": "[๋จ๊ณ 9] RAG ์ ์ฒด ํ์ดํ๋ผ์ธ โ ์ฐ๊ฒฐ", | |
| "task_description": "**์ํฉ**\n```python\ndocs = retriever.invoke(question) # 1. ๊ฒ์\ncontext = '\\n'.join(d.page_content for d in docs) # 2. ํฉ์น๊ธฐ\nprompt_text = prompt.format(...) # 3. ํ๋กฌํํธ\nresponse = llm.invoke(prompt_text) # 4. LLM ํธ์ถ\nfinal = parser.parse(response) # 5. ํ์ฑ\n```\n5์ค ์ฝ๋๋ฅผ ๋งค ์ง๋ฌธ๋ง๋ค ๋ฐ๋ณตํด์ผ ํฉ๋๋ค.\n\n**๋ฌธ์ **\nretriever โ prompt โ llm โ parser๋ฅผ ์๋์ผ๋ก ์ฐ๊ฒฐํ๋ ์ฝ๋๊ฐ ์ฅํฉํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nLCEL๋ก RAG ์ฒด์ธ์ ํ ์ค๋ก ๋ง๋์ธ์.\n- `retriever`, `prompt`, `llm`, `StrOutputParser` ๋ณ์๋ ์ด๋ฏธ ์ ์๋์ด ์์ต๋๋ค.", | |
| "expected_command": "chain = {\"context\": retriever, \"question\": RunnablePassthrough()} | prompt | llm | StrOutputParser()", | |
| "grading_conditions": ["retriever", "RunnablePassthrough", "|", "prompt", "|", "llm", "StrOutputParser"], | |
| "hint": "`{\"context\": retriever, \"question\": RunnablePassthrough()}`๋ก retriever์ ์ง๋ฌธ์ ๋์์ prompt์ ๋๊น๋๋ค.", | |
| "explanation": "๋์ ๋๋ฆฌ๋ก ๋ฌถ์ผ๋ฉด `RunnableParallel`์ฒ๋ผ ๋์ํด retriever ๊ฒ์๊ณผ ์ง๋ฌธ ์ ๋ฌ์ ๋์์ ์ฒ๋ฆฌํฉ๋๋ค. `RunnablePassthrough()`๋ ์ ๋ ฅ(์ง๋ฌธ)์ ๊ทธ๋๋ก ๋ค์ ๋จ๊ณ๋ก ์ ๋ฌํฉ๋๋ค.", | |
| "difficulty": "hard", | |
| "takeaway": "{context: retriever, question: RunnablePassthrough()} ํจํด์ด RAG ์ฒด์ธ์์ ๊ฒ์๊ณผ ์ง๋ฌธ์ ๋์์ ํ๋กฌํํธ์ ์ฃผ์ ํ๋ ํต์ฌ ๊ตฌ์กฐ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-10", | |
| "title": "[๋จ๊ณ 10] session_state โ ๋ํ๊ฐ ์ฌ๋ผ์ง๋ ๋ฌธ์ ํด๊ฒฐ", | |
| "task_description": "**์ํฉ**\nStreamlit ์ฑ๋ด์ ๋ง๋ค์๋๋ฐ, ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ๋๋ง๋ค ์ด์ ๋ํ๊ฐ ์ ๋ถ ์ฌ๋ผ์ง๋๋ค.\n\n**๋ฌธ์ **\nStreamlit์ ๋ฒํผ ํด๋ฆญ ๋ฑ ๋ชจ๋ ์ํธ์์ฉ๋ง๋ค Python ์คํฌ๋ฆฝํธ๋ฅผ ์ฒ์๋ถํฐ ๋ค์ ์คํํฉ๋๋ค. `messages = []`์ฒ๋ผ ์ ์ธํ๋ฉด ๋งค๋ฒ ์ด๊ธฐํ๋ฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`messages` ํค๊ฐ ์์ ๋๋ง ๋น ๋ฆฌ์คํธ๋ก ์ด๊ธฐํํ๋ ํจํด์ ์์ฑํ์ธ์.\n\n> ํต์ฌ: `st.session_state`์ ์ ์ฅ๋ ๊ฐ์ rerunํด๋ ์ ์ง๋ฉ๋๋ค.", | |
| "expected_command": "if \"messages\" not in st.session_state:\n st.session_state.messages = []", | |
| "grading_conditions": ["\"messages\" not in st.session_state", "st.session_state.messages", "[]"], | |
| "hint": "`if \"ํค\" not in st.session_state:` ๊ฐ๋๋ฅผ ์ฐ๋ฉด ์ฒซ ์คํ์๋ง ์ด๊ธฐํ๋ฉ๋๋ค.", | |
| "explanation": "`if ... not in st.session_state` ํจํด์ด ์์ผ๋ฉด ๋งค rerun๋ง๋ค `[]`๋ก ๋ฆฌ์ ๋ฉ๋๋ค. ์ด ๊ฐ๋๊ฐ ์์ผ๋ฉด ์ด๋ฏธ ๊ฐ์ด ์์ ๋๋ ๊ฑด๋๋๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "`if key not in st.session_state:` ๊ฐ๋ ํจํด ํ๋๊ฐ Streamlit ์ํ ๋ณด์กด์ ์ ๋ถ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-11", | |
| "title": "[๋จ๊ณ 11] cache_resource โ ์ง๋ฌธ๋ง๋ค LLM์ด ์๋ก ๋ก๋๋๋ ๋ฌธ์ ", | |
| "task_description": "**์ํฉ**\n์ฑ๋ด์ ์ง๋ฌธํ ๋๋ง๋ค 3~4์ด์ฉ ๊ฑธ๋ฆฝ๋๋ค. ๋ก๊ทธ๋ฅผ ๋ณด๋ LLM ๊ฐ์ฒด๊ฐ ๋งค๋ฒ ์๋ก ์์ฑ๋๊ณ ์์ต๋๋ค.\n\n```python\ndef load_llm(): # ์ด ํจ์๊ฐ ๋งค rerun๋ง๋ค ์คํ๋จ\n return OllamaLLM(model=\"llama3.2\")\n```\n\n**๋ฌธ์ **\nStreamlit์ rerun๋ง๋ค ๋ชจ๋ ํจ์๋ฅผ ๋ค์ ์คํํฉ๋๋ค. LLM ๋ก๋๋ ๋น์ฉ์ด ํฌ๋ฏ๋ก ํ ๋ฒ๋ง ํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`load_llm()` ํจ์์ `st.cache_resource` ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ๋ถ์ฌ ์ฑ ์๋ช ๋์ ํ ๋ฒ๋ง ๋ก๋๋๊ฒ ํ์ธ์.", | |
| "expected_command": "@st.cache_resource\ndef load_llm():\n return OllamaLLM(model=\"llama3.2\")", | |
| "grading_conditions": ["@st.cache_resource", "def load_llm", "OllamaLLM"], | |
| "hint": "ํจ์ ์ ์ ๋ฐ๋ก ์์ `@st.cache_resource`๋ฅผ ๋ถ์ ๋๋ค.", | |
| "explanation": "`@st.cache_resource`๋ ๋ฐํ ๊ฐ์ฒด๋ฅผ ์ฑ ์ ์ฒด(๋ชจ๋ ์ธ์ )์์ ๊ณต์ ํฉ๋๋ค. ์ฒซ ๋ฒ์งธ ํธ์ถ์๋ง ํจ์๋ฅผ ์คํํ๊ณ ์ดํ์๋ ์บ์๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "@st.cache_resource๋ LLMยทDB์ฒ๋ผ ๊ณต์ ๊ฐ๋ฅํ ๋ฌด๊ฑฐ์ด ๊ฐ์ฒด์, @st.cache_data๋ ๊ณ์ฐ ๊ฒฐ๊ณผ(DataFrame ๋ฑ)์ ์๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-12", | |
| "title": "[๋จ๊ณ 12] Streamlit secrets โ ๋ฐฐํฌ ํ๊ฒฝ์ API ํค ๊ด๋ฆฌ", | |
| "task_description": "**์ํฉ**\n๋ก์ปฌ์์ `.env`๋ก ์ ์๋ํ๋ ์ฑ๋ด์ Streamlit Cloud์ ๋ฐฐํฌํ๋๋:\n```\nKeyError: 'OPENAI_API_KEY'\n```\nStreamlit Cloud๋ ๋ก์ปฌ์ `.env` ํ์ผ์ ์ฝ์ ์ ์์ต๋๋ค.\n\n**๋ฌธ์ **\n๋ฐฐํฌ ํ๊ฒฝ์์๋ `.env` ํ์ผ์ด ์์ต๋๋ค. ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก API ํค๋ฅผ ์ฃผ์ ํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nStreamlit Cloud ๋์๋ณด๋์ Secrets์ ํค๋ฅผ ๋ฑ๋กํ ๋ค, ์ฝ๋์์ ์ฝ๋ ํ ์ค์ ์์ฑํ์ธ์.", | |
| "expected_command": "api_key = st.secrets[\"OPENAI_API_KEY\"]", | |
| "grading_conditions": ["st.secrets", "OPENAI_API_KEY"], | |
| "hint": "`st.secrets[\"ํค์ด๋ฆ\"]`์ผ๋ก ์ ๊ทผํฉ๋๋ค.", | |
| "explanation": "`st.secrets`๋ ๋ก์ปฌ์์๋ `.streamlit/secrets.toml`, ๋ฐฐํฌ ์์๋ ๋์๋ณด๋ Secrets์์ ๊ฐ์ ์ฝ์ต๋๋ค. ์ฝ๋๋ฅผ ๋ฐ๊พธ์ง ์์๋ ๋ ํ๊ฒฝ์์ ๋์ผํ๊ฒ ์๋ํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "st.secrets๋ ๋ก์ปฌ(.streamlit/secrets.toml)๊ณผ ๋ฐฐํฌ(Streamlit Cloud) ํ๊ฒฝ์ ์ฝ๋ ๋ณ๊ฒฝ ์์ด ๋์ผํ๊ฒ ์ฒ๋ฆฌํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-13", | |
| "title": "[๋จ๊ณ 13] ๋ํ ๋ฉ๋ชจ๋ฆฌ โ ์ด์ ๋ํ๋ฅผ ๊ธฐ์ตํ๊ฒ", | |
| "task_description": "**์ํฉ**\n```\n์ฌ์ฉ์: ํ์ด์ฌ์ด ๋ญ์ผ?\n์ฑ๋ด: ํ์ด์ฌ์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋๋ค...\n์ฌ์ฉ์: ๊ทธ๊ฑฐ ์ด๋์ ๋ฐฐ์ธ ์ ์์ด?\n์ฑ๋ด: ๋ฌด์์ ๋ฐฐ์ฐ๊ณ ์ถ์ผ์ ๊ฐ์? (??)\n```\n\"๊ทธ๊ฑฐ\"๊ฐ ๋ฌด์์ธ์ง ์ฑ๋ด์ด ๋ชจ๋ฆ ๋๋ค. ๋งค ์ง๋ฌธ์ด ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.\n\n**๋ฌธ์ **\nRAG ์ฒด์ธ์ ์ด์ ๋ํ ๋ด์ฉ์ ๋ฐ์ง ์์ต๋๋ค. ๋ฉํฐํด ๋ํ๋ฅผ ์ํด ํ์คํ ๋ฆฌ๋ฅผ ์ฃผ์ ํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n๊ธฐ์กด `chain`์ `RunnableWithMessageHistory`๋ก ๊ฐ์ธ์ธ์.\n- `input_messages_key`: `\"question\"`\n- `history_messages_key`: `\"chat_history\"`\n- ๊ฒฐ๊ณผ๋ฅผ `chain_with_history`์ ์ ์ฅ", | |
| "expected_command": "chain_with_history = RunnableWithMessageHistory(\n chain,\n get_session_history,\n input_messages_key=\"question\",\n history_messages_key=\"chat_history\"\n)", | |
| "grading_conditions": ["RunnableWithMessageHistory", "chain", "get_session_history", "input_messages_key", "history_messages_key"], | |
| "hint": "`RunnableWithMessageHistory(์ฒด์ธ, ํ์คํ ๋ฆฌํจ์, input_messages_key=..., history_messages_key=...)`", | |
| "explanation": "`RunnableWithMessageHistory`๋ ๋งค invoke๋ง๋ค session_id์ ํด๋นํ๋ ํ์คํ ๋ฆฌ๋ฅผ ์๋์ผ๋ก ๋ถ๋ฌ์ ํ๋กฌํํธ์ ์ฃผ์ ํฉ๋๋ค. invoke ์ `config={\"configurable\": {\"session_id\": \"user-1\"}}`์ ๋๊ฒจ์ผ ํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "RunnableWithMessageHistory๋ก ๊ธฐ์กด ์ฒด์ธ์ ๊ฐ์ธ๊ธฐ๋ง ํ๋ฉด ๋ฉํฐํด ๋ํ๊ฐ ๋ฉ๋๋ค. session_id ๊ด๋ฆฌ๊ฐ ์ ๋ถ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-14", | |
| "title": "[๋จ๊ณ 14] HF Spaces โ ๋ค๋ฅธ ์ฌ๋๋ ์ธ ์ ์๊ฒ ๋ฐฐํฌ", | |
| "task_description": "**์ํฉ**\n์์ฑ๋ ์ฑ๋ด์ ํ์์๊ฒ ๋ณด์ฌ์ฃผ๋ ค๋ \"๋ก์ปฌ์์๋ง ๋์๊ฐ๊ณ ๋ฐ์์ ์ ๊ทผ์ด ์ ๋ฉ๋๋ค.\"\n\n**๋ฌธ์ **\n์๋ฒ๋ฅผ ์ง์ ๊ตฌ๋งคํ๊ฑฐ๋ AWS/GCP๋ฅผ ์ค์ ํ์ง ์๊ณ ๋ ์ธ๋ถ์ ๊ณต๊ฐํ๊ณ ์ถ์ต๋๋ค.\n\n**ํด๊ฒฐ**\nHugging Face Spaces๋ก ๋ฌด๋ฃ ๋ฐฐํฌํฉ๋๋ค. `README.md` ์๋จ์ SDK๋ฅผ ์ ์ธํ์ธ์.\n- SDK: `streamlit`, ์ฑ ํ์ผ: `app.py`, Python: `3.11`", | |
| "expected_command": "---\nsdk: streamlit\napp_file: app.py\npython_version: \"3.11\"\n---", | |
| "grading_conditions": ["sdk: streamlit", "app_file: app.py"], | |
| "hint": "README.md ๋งจ ์ `---` YAML ๋ธ๋ก ์์ ์ ์ธํฉ๋๋ค.", | |
| "explanation": "HF Spaces๋ README ๋ฉํ๋ฐ์ดํฐ์ `sdk` ํ๋๋ก ๋ฐํ์์ ๊ฒฐ์ ํฉ๋๋ค. `sdk: streamlit`์ด๋ฉด ์๋์ผ๋ก Streamlit ์๋ฒ๋ฅผ ์คํํฉ๋๋ค. ๋ฌด๋ฃ ํ๋์ผ๋ก๋ ์ธ๋ถ URL์ด ์ฆ์ ์์ฑ๋ฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "README YAML ๋ฉํ๋ฐ์ดํฐ 3์ค(sdk, app_file, python_version)๋ง์ผ๋ก HF Spaces ๋ฐฐํฌ๊ฐ ์๋ฃ๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-15", | |
| "title": "[๋จ๊ณ 15] HF Docker Space โ SDK ์ ์ฝ ์์ด ๋ฐฐํฌ", | |
| "task_description": "**์ํฉ**\nHF Spaces Streamlit SDK๋ก ๋ฐฐํฌํ๋๋ฐ, FastAPI ์๋ฒ๋ฅผ ํจ๊ป ๋์ฐ๊ณ ์ถ์ด ํฌํธ๋ฅผ ์ปค์คํ ํ๋ ค๋ ์ค์ ์ด ๋งํ์์ต๋๋ค.\n\n**๋ฌธ์ **\nStreamlit/Gradio SDK๋ ๋ฐํ์ ํ๊ฒฝ์ด ๊ณ ์ ๋ฉ๋๋ค. ์ง์ ์๋ฒ๋ฅผ ์ ์ดํ๋ ค๋ฉด Dockerfile์ด ํ์ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nDocker Space์์ HF๊ฐ ์ธ์ํ๋ ํฌํธ๋ก ์ฑ์ ๋ ธ์ถํ๋ Dockerfile ์ง์์ด๋ฅผ ์์ฑํ์ธ์.\n\n> HF Docker Space ๊ธฐ๋ณธ ํฌํธ: 7860", | |
| "expected_command": "EXPOSE 7860\nENV PORT=7860", | |
| "grading_conditions": ["EXPOSE 7860"], | |
| "hint": "Streamlit ๊ธฐ๋ณธ ํฌํธ๋ 8501์ด์ง๋ง HF๋ 7860์ ๊ธฐ๋ํฉ๋๋ค.", | |
| "explanation": "Docker Space์์๋ `EXPOSE 7860`์ผ๋ก ํฌํธ๋ฅผ ์ ์ธํ๊ณ , `streamlit run app.py --server.port 7860`์ผ๋ก Streamlit๋ 7860์ ๋ฐ์ธ๋ฉํด์ผ ํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "HF Docker Space๋ ํญ์ ํฌํธ 7860์ ๊ธฐ๋ํฉ๋๋ค. EXPOSE 7860๊ณผ ์ฑ์ ํฌํธ ์ค์ ์ ์ผ์น์์ผ์ผ ํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-16", | |
| "title": "[๋จ๊ณ 16] FastAPI โ ์ฑ๋ด ๋ก์ง์ API๋ก ๋ถ๋ฆฌ", | |
| "task_description": "**์ํฉ**\nํ๋ก ํธ์๋ ํ์์ \"์ฑ๋ด API ์๋ํฌ์ธํธ๋ง ์ฃผ์ธ์. React์์ ๋ถ์ด๊ฒ ์ต๋๋ค.\"๋ผ๊ณ ํฉ๋๋ค. ์ง๊ธ์ Streamlit์ ๋ก์ง์ด ๋ฌถ์ฌ ์์ด ๋ถ๋ฆฌ๊ฐ ์ ๋ฉ๋๋ค.\n\n**๋ฌธ์ **\nStreamlit ์ฑ์ UI์ ๋ก์ง์ด ๊ฒฐํฉ๋์ด ์์ด ๋ค๋ฅธ ํ๋ก ํธ์๋์์ ์ฌ์ฌ์ฉํ๊ธฐ ์ด๋ ต์ต๋๋ค.\n\n**ํด๊ฒฐ**\n์๋ `???`๋ฅผ ์ฑ์ POST `/ask` ์๋ํฌ์ธํธ๋ฅผ ์์ฑํ์ธ์.\n\n```python\nclass Query(BaseModel):\n question: str\n\n@app.post('/ask')\nasync def ask(q: Query):\n answer = ???\n return {\"answer\": answer}\n```", | |
| "expected_command": "answer = chain.invoke(q.question)", | |
| "grading_conditions": ["chain.invoke", "q.question"], | |
| "hint": "Pydantic ๋ชจ๋ธ `q`์ `.question` ์์ฑ์ผ๋ก ์ง๋ฌธ์ ์ ๊ทผํฉ๋๋ค.", | |
| "explanation": "FastAPI๋ Pydantic ๋ชจ๋ธ ํ์ ์ ํ๋ผ๋ฏธํฐ๋ก ์ง์ ํ๋ฉด ์๋์ผ๋ก JSON ๋ฐ๋๋ฅผ ํ์ฑํฉ๋๋ค. `q.question`์ผ๋ก ์ง๋ฌธ ๋ฌธ์์ด์ ๊บผ๋ด chain์ ์ ๋ฌํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "FastAPI + Pydantic ์กฐํฉ์ด LLM ์๋น์ค๋ฅผ API๋ก ๋ ธ์ถํ๋ ๊ฐ์ฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ์ ๋๋ค. Pydantic์ด JSON ๊ฒ์ฆ์ ์๋ ์ฒ๋ฆฌํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-17", | |
| "title": "[๋จ๊ณ 17] FastAPI ๊ฐ๋ฐ ์๋ฒ โ ์ฝ๋ ์์ ์ฆ์ ๋ฐ์", | |
| "task_description": "**์ํฉ**\n```python\nuvicorn main:app\n```\n์๋ํฌ์ธํธ ์ฝ๋๋ฅผ ์์ ํ ๋๋ง๋ค ์๋ฒ๋ฅผ `Ctrl+C`๋ก ์ข ๋ฃํ๊ณ ๋ค์ ์คํํด์ผ ํฉ๋๋ค. ๊ฐ๋ฐ ์ค์ ๋๋ฌด ๋ฒ๊ฑฐ๋กญ์ต๋๋ค.\n\n**๋ฌธ์ **\n๊ธฐ๋ณธ uvicorn์ ํ์ผ ๋ณ๊ฒฝ์ ๊ฐ์งํ์ง ์์ต๋๋ค.\n\n**ํด๊ฒฐ**\n`main.py`์ `app` ์ธ์คํด์ค๋ฅผ ํ์ผ ๋ณ๊ฒฝ ์ ์๋ ์ฌ์์๋๋ ๋ชจ๋๋ก ์คํํ์ธ์.", | |
| "expected_command": "uvicorn main:app --reload", | |
| "grading_conditions": ["uvicorn", "main:app", "--reload"], | |
| "hint": "`--reload` ํ๋๊ทธ ํ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.", | |
| "explanation": "`--reload`๋ ์์ค ํ์ผ ๋ณ๊ฒฝ์ ๊ฐ์งํด ์๋ฒ๋ฅผ ์๋ ์ฌ์์ํฉ๋๋ค. ๊ฐ๋ฐ ์ค์๋ง ์ฌ์ฉํ๋ฉฐ, ํ๋ก๋์ ์์๋ ์ ๊ฑฐํ๊ณ `gunicorn` ๊ฐ์ ํ๋ก๋์ ์๋ฒ๋ฅผ ์๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "--reload๋ ๊ฐ๋ฐ์ฉ, ํ๋ก๋์ ์์๋ gunicorn์ ์๋๋ค. ์ด ๊ตฌ๋ถ์ ๊ธฐ์ตํ๋ ๊ฒ์ด ํต์ฌ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-18", | |
| "title": "[๋จ๊ณ 18] LangSmith โ ์ด๋ ๋จ๊ณ์์ ํ๋ฆฐ ๋ต์ด ๋์ค๋๊ฐ", | |
| "task_description": "**์ํฉ**\n์ฑ๋ด์ด ์๋ฑํ ๋ต์ ํ์ต๋๋ค. ๋๋ฒ๊น ์ ์ํด `print()`๋ฅผ ์ถ๊ฐํ์ง๋ง:\n\n```python\nprint('์ฒด์ธ ์คํ ์ค...')\nresult = chain.invoke({'question': q})\nprint('๊ฒฐ๊ณผ:', result)\n```\n\n์ต์ข ์ ์ถ๋ ฅ๋ง ๋ณด์ผ ๋ฟ, **retriever๊ฐ ์๋ชป๋ ๋ฌธ์๋ฅผ ๊ฐ์ ธ์๋์ง**, **ํ๋กฌํํธ๊ฐ ์๋ชป ๋ ๋๋ง๋๋์ง**, **LLM์ด ํ๋ฆฐ ๋ต์ ํ๋์ง** ๊ตฌ๋ถ์ด ์ ๋ฉ๋๋ค.\n\n**๋ฌธ์ **\n`print()`๋ ๊ฐ ๋จ๊ณ์ ๋ด๋ถ ์ ์ถ๋ ฅ์ ๋ณผ ์ ์์ต๋๋ค.\n\n**ํด๊ฒฐ**\nLangSmith ์ถ์ ์ ํ์ฑํํ์ธ์. ํ๊ฒฝ๋ณ์ 3๊ฐ๋ฅผ `os.environ`์ผ๋ก ์ค์ ํ์ธ์.\n- `LANGCHAIN_TRACING_V2 = 'true'`\n- `LANGCHAIN_PROJECT = 'rag-debug'`\n- `LANGCHAIN_API_KEY = 'ls__your_key'`", | |
| "expected_command": "os.environ['LANGCHAIN_TRACING_V2'] = 'true'\nos.environ['LANGCHAIN_PROJECT'] = 'rag-debug'\nos.environ['LANGCHAIN_API_KEY'] = 'ls__your_key'", | |
| "grading_conditions": ["LANGCHAIN_TRACING_V2", "true", "LANGCHAIN_PROJECT", "LANGCHAIN_API_KEY"], | |
| "hint": "`os.environ['๋ณ์๋ช '] = '๊ฐ'` ํจํด์ผ๋ก 3์ค ์์ฑํฉ๋๋ค.", | |
| "explanation": "์ด 3์ค ์ดํ์ ๋ชจ๋ LangChain ์คํ์ LangSmith์ ์๋ ๊ธฐ๋ก๋ฉ๋๋ค. LangSmith UI์์ retriever ์ ์ถ๋ ฅ, ๋ ๋๋ง๋ ํ๋กฌํํธ, LLM ์๋ต์ tree ํํ๋ก ๋จ๊ณ๋ณ๋ก ๋ณผ ์ ์์ต๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "ํ๊ฒฝ๋ณ์ 3๊ฐ(TRACING_V2=true, PROJECT, API_KEY)๋ง ์ค์ ํ๋ฉด ์ดํ ๋ชจ๋ LangChain ์คํ์ด ์๋์ผ๋ก LangSmith์ ๊ธฐ๋ก๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-19", | |
| "title": "[๋จ๊ณ 19] Guardrails โ LLM์ด ๋์ ๋ง์ ํ๋ฉด", | |
| "task_description": "**์ํฉ**\nํ ์คํธ ์ค ์ฌ์ฉ์๊ฐ ์ด๋ ๊ฒ ์ ๋ ฅํ์ต๋๋ค:\n\n> \"์์ค์ ์ต๋ํ ์์ด์ ์์ฝํด์ค\"\n\nLLM์ด ๊ทธ๋๋ก ๋ฐ๋ผ ์ ํดํ ๋ด์ฉ์ ์ถ๋ ฅํ์ต๋๋ค. ์๋น์ค์ ์ด๋ฐ ์๋ต์ด ๋ ธ์ถ๋๋ฉด ์ ๋ฉ๋๋ค.\n\n**๋ฌธ์ **\nLLM์ ํ๋กฌํํธ ์ธ์ ์ ์ ์ทจ์ฝํ๊ณ , ์ ํด ์ฝํ ์ธ ๋ฅผ ๊ทธ๋๋ก ์์ฑํ ์ ์์ต๋๋ค.\n\n**ํด๊ฒฐ**\nGuardrails AI๋ก ์ ํด์ฑ ๊ฒ์ฆ๊ธฐ๋ฅผ ์ ์ฉํ์ธ์.\n- ์๊ณ๊ฐ: `0.5` (์ด ์ด์์ด๋ฉด ์ ํด ํ์ )\n- ์คํจ ์: `'exception'` (์์ธ ๋ฐ์์ผ๋ก ์๋ต ์ฐจ๋จ)", | |
| "expected_command": "guard = Guard().use(ToxicLanguage(threshold=0.5, on_fail='exception'))", | |
| "grading_conditions": ["Guard()", "ToxicLanguage", "threshold=0.5", "on_fail='exception'"], | |
| "hint": "`Guard().use(๊ฒ์ฆ๊ธฐ(ํ๋ผ๋ฏธํฐ))` ํจํด์ ๋๋ค.", | |
| "explanation": "์ด Guard๋ก LLM์ ๊ฐ์ธ๋ฉด LLM ์๋ต์ด ์ ํด์ฑ ์๊ณ๊ฐ์ ์ด๊ณผํ ๋ ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค. `on_fail='reask'`๋ก ๋ฐ๊พธ๋ฉด LLM์๊ฒ ์๋์ผ๋ก ์ฌ์์ฑ์ ์์ฒญํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "Guard().use(๊ฒ์ฆ๊ธฐ) ํจํด์ผ๋ก LLM ์ถ๋ ฅ์ ์์ ์ฅ์น๋ฅผ ์ถ๊ฐํฉ๋๋ค. on_fail='exception'์ ์ฐจ๋จ, 'reask'๋ ์๋ ์ฌ์์ฑ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-20", | |
| "title": "[๋จ๊ณ 20] Airflow โ ๋ฌธ์ ์ ๋ฐ์ดํธ๋ฅผ ๋ชป ๋์น๋ ๋ฌธ์ ", | |
| "task_description": "**์ํฉ**\n๋งค์ฃผ ์ ๋ฌธ์๊ฐ ์ถ๊ฐ๋๋๋ฐ, ์ฌ์ธ๋ฑ์ฑ์ ์์ด๋ฒ๋ ค ์ฑ๋ด์ด ์ค๋๋ ์ ๋ณด๋ก๋ง ๋ตํฉ๋๋ค. ํ์์ด \"์ ์ด ๋ด์ฉ์ด ๊ฒ์์ด ์ ๋ผ์?\"๋ผ๊ณ ๋ฌผ์ด๋ดค๋๋ฐ, 2์ฃผ ์ ์ ์ถ๊ฐ๋ ๋ฌธ์์์ต๋๋ค.\n\n**๋ฌธ์ **\n์ฌ์ธ๋ฑ์ฑ ์คํฌ๋ฆฝํธ๋ฅผ ์๋์ผ๋ก ์คํํ๋ฉด ๋ฐ๋์ ๋น ๋จ๋ฆฌ๋ ๋ ์ด ์๊น๋๋ค.\n\n**ํด๊ฒฐ**\n`run_reindex` ํจ์๋ฅผ ๋งค์ผ ์์ ์ ์๋ ์คํํ๋ Airflow DAG๋ฅผ ์ ์ํ์ธ์.\n- DAG ID: `daily_reindex`, ์ค์ผ์ค: `@daily`, start_date: `datetime(2024, 1, 1)`", | |
| "expected_command": "with DAG('daily_reindex', schedule='@daily', start_date=datetime(2024, 1, 1)) as dag:", | |
| "grading_conditions": ["DAG", "daily_reindex", "schedule='@daily'", "start_date", "datetime"], | |
| "hint": "`with DAG('id', schedule='@daily', start_date=datetime(...)) as dag:` ํจํด์ ๋๋ค.", | |
| "explanation": "`@daily`๋ ๋งค์ผ ์์ ์ ์คํํ๋ Airflow ๋ด์ฅ ์ค์ผ์ค ํํ์์ ๋๋ค. ์ด DAG๊ฐ ์์ผ๋ฉด ๋ด๋น์๊ฐ ์๋ ์ฃผ๋ง์๋ ์๋์ผ๋ก ์ฌ์ธ๋ฑ์ฑ๋ฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "@daily ์ค์ผ์ค ํํ์ ํ๋๋ก ๋งค์ผ ์์ ์๋ ์คํ์ด ๋ฉ๋๋ค. ์๋ ์คํ์ ํญ์ ๋น ๋จ๋ฆฌ๋ ๋ ์ด ์๊น๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-21", | |
| "title": "[๋จ๊ณ 21] Airflow PythonOperator โ ์ฌ์ธ๋ฑ์ฑ ํจ์ ๋ฑ๋ก", | |
| "task_description": "**์ํฉ**\nDAG ๊ตฌ์กฐ๋ ๋ง๋ค์๋๋ฐ, ์ค์ ๋ก ์ด๋ค ํจ์๋ฅผ ์คํํ ์ง ์ง์ ํ์ง ์์์ต๋๋ค.\n\n**๋ฌธ์ **\nAirflow DAG๋ \"์ธ์ ์คํํ ์ง\"๋ง ์ ์ํฉ๋๋ค. \"๋ฌด์์ ์คํํ ์ง\"๋ Operator๋ก ๋ฑ๋กํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`reindex` ํจ์๋ฅผ ์คํํ๋ PythonOperator๋ฅผ DAG ์์ ๋ฑ๋กํ์ธ์.\n- task_id: `'reindex'`, python_callable: `reindex`", | |
| "expected_command": "task = PythonOperator(task_id='reindex', python_callable=reindex)", | |
| "grading_conditions": ["PythonOperator", "task_id='reindex'", "python_callable=reindex"], | |
| "hint": "`PythonOperator(task_id='...', python_callable=ํจ์)` ํจํด์ ๋๋ค.", | |
| "explanation": "`python_callable`์ ํจ์ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํฉ๋๋ค. ํจ์ ํธ์ถ(`reindex()`)์ด ์๋ ํจ์ ์ฐธ์กฐ(`reindex`)๋ฅผ ๋๊ฒจ์ผ Airflow๊ฐ ์ค์ผ์ค์ ๋ง์ถฐ ์คํํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "python_callable์๋ ํจ์ ํธ์ถ(reindex())์ด ์๋ ํจ์ ์ฐธ์กฐ(reindex)๋ฅผ ๋๊ฒจ์ผ ํฉ๋๋ค. ๊ดํธ์ ์ ๋ฌด๊ฐ ํต์ฌ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-22", | |
| "title": "[๋จ๊ณ 22] MLflow โ ์ด๋ค ์ค์ ์ด ๋ ์ข์์ง ๊ธฐ์ต์ด ์ ๋จ", | |
| "task_description": "**์ํฉ**\nchunk_size๋ฅผ 500์ผ๋ก ํ์ ๋์ 1000์ผ๋ก ํ์ ๋ ๋ต๋ณ ํ์ง์ด ๋ฌ๋์ต๋๋ค. ๋ฉฐ์น ํ ๋ค์ ๋น๊ตํ๋ ค๋ \"์ด๋ค ์ค์ ์ผ๋ก ์ด๋ค ์ ์๊ฐ ๋์๋๋ผ?\"๊ฐ ๊ธฐ์ต์ด ์ ๋ฉ๋๋ค. ๋ ธ์ ์ ์๋์ผ๋ก ๊ธฐ๋กํ์ง๋ง ๋๋ฝ์ด ๋ง์ต๋๋ค.\n\n**๋ฌธ์ **\nํ๋ผ๋ฏธํฐ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์๋์ผ๋ก ๊ธฐ๋กํ๋ฉด ๋น ๋จ๋ฆฌ๊ฑฐ๋ ์์ด๋ฒ๋ฆฝ๋๋ค.\n\n**ํด๊ฒฐ**\nMLflow๋ก ์คํ์ ๊ธฐ๋กํ์ธ์.\n- run ์ด๋ฆ: `'rag-chunk500-k4'`\n- ํ๋ผ๋ฏธํฐ: `chunk_size=500`, `k=4`\n- ๋ฉํธ๋ฆญ: `answer_relevance=0.87`", | |
| "expected_command": "with mlflow.start_run(run_name='rag-chunk500-k4'):\n mlflow.log_param('chunk_size', 500)\n mlflow.log_param('k', 4)\n mlflow.log_metric('answer_relevance', 0.87)", | |
| "grading_conditions": ["mlflow.start_run", "log_param", "chunk_size", "log_metric", "answer_relevance"], | |
| "hint": "`with mlflow.start_run(run_name='...'):`๋ก ๋ธ๋ก์ ์ด๊ณ , ์์์ `log_param`, `log_metric`์ ํธ์ถํฉ๋๋ค.", | |
| "explanation": "`mlflow ui` ๋ช ๋ น์ผ๋ก ์คํ ๋น๊ต ๋์๋ณด๋๋ฅผ ์ด๋ฉด ํ๋ผ๋ฏธํฐ๋ณ ๋ฉํธ๋ฆญ ์ฐจํธ๋ฅผ ์๊ฐ์ ์ผ๋ก ๋น๊ตํ ์ ์์ต๋๋ค. ์๋ ๊ธฐ๋ก ์์ด ๋ชจ๋ ์คํ์ด ์๋ ๋ณด์กด๋ฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "mlflow.start_run() context manager๋ก ํ๋ผ๋ฏธํฐ์ ๋ฉํธ๋ฆญ์ ๊ธฐ๋กํ๋ฉด ๋์ค์ mlflow ui์์ ์คํ์ ์๊ฐ์ ์ผ๋ก ๋น๊ตํ ์ ์์ต๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-23", | |
| "title": "[๋จ๊ณ 23] MCP โ Claude์์ ๋ฐ๋ก ๋ด ๋ฌธ์๋ฅผ ๊ฒ์ํ๋ ค๋ฉด", | |
| "task_description": "**์ํฉ**\nClaude Desktop์ผ๋ก ๋ํ ์ค \"์ฐ๋ฆฌ ๋ด๋ถ ๋ฌธ์์ ์ด ๋ด์ฉ์ด ์์๋๋ฐ...\"๋ผ๋ ์๊ฐ์ด ๋ค ๋๋ง๋ค Streamlit ์ฑ์ผ๋ก ์ด๋ํด์ ๊ฒ์ํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ณต์ฌํด Claude์ ๋ถ์ฌ๋ฃ์ด์ผ ํฉ๋๋ค.\n\n**๋ฌธ์ **\nClaude๊ฐ ๋ด RAG ๊ฒ์ ๊ธฐ๋ฅ์ ์ง์ ํธ์ถํ ์ ์์ต๋๋ค. ๋ณ๋ ์ฑ์ ์๋ณตํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nMCP ์๋ฒ๋ฅผ ๋ง๋ค์ด Claude์๊ฒ `search_docs` ๋๊ตฌ๋ฅผ ์ ๊ณตํ์ธ์.\n- ์๋ฒ๋ช : `'rag-server'`\n- ๋๊ตฌ ํจ์: `search_docs(query: str)` โ `retriever.invoke(query)`๋ก ๊ฒ์ ํ page_content๋ฅผ ์ค๋ฐ๊ฟ์ผ๋ก ํฉ์ณ ๋ฐํ", | |
| "expected_command": "mcp = FastMCP('rag-server')\n\n@mcp.tool()\ndef search_docs(query: str) -> str:\n results = retriever.invoke(query)\n return '\\n'.join(r.page_content for r in results)", | |
| "grading_conditions": ["FastMCP", "@mcp.tool()", "def search_docs", "query: str", "retriever.invoke"], | |
| "hint": "`FastMCP('์๋ฒ๋ช ')`์ผ๋ก ์ธ์คํด์ค๋ฅผ ๋ง๋ค๊ณ , `@mcp.tool()`์ ํจ์ ์์ ๋ถ์ ๋๋ค.", | |
| "explanation": "`@mcp.tool()` ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ๋ถ์ผ๋ฉด Claude๋ ๋ํ ์ค ์ด ํจ์๋ฅผ ์ง์ ํธ์ถํ ์ ์์ต๋๋ค. ํจ์ docstring์ด Claude์๊ฒ \"์ธ์ ์ด ๋๊ตฌ๋ฅผ ์ฐ๋๊ฐ\"๋ฅผ ์๋ ค์ฃผ๋ ์ค๋ช ์ด ๋ฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "@mcp.tool() ๋ฐ์ฝ๋ ์ดํฐ ํ๋๋ก ํ์ด์ฌ ํจ์๊ฐ Claude๊ฐ ๋ํ ์ค ์ง์ ํธ์ถํ๋ ๋๊ตฌ๊ฐ ๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-24", | |
| "title": "[๋จ๊ณ 24] MCP ์๋ฒ ์คํ โ Claude Desktop์ ์ฐ๊ฒฐ", | |
| "task_description": "**์ํฉ**\nMCP ์๋ฒ ์ฝ๋๋ฅผ ์์ฑํ๋๋ฐ, Claude Desktop์ด ์ด๋ป๊ฒ ์ด ์๋ฒ๋ฅผ ์ฐพ๊ณ ์คํํ๋์ง ๋ชจ๋ฆ ๋๋ค.\n\n**๋ฌธ์ **\nClaude Desktop์ stdio(ํ์ค ์ ์ถ๋ ฅ)๋ก MCP ์๋ฒ ํ๋ก์ธ์ค์ ํต์ ํฉ๋๋ค. ์ฌ๋ฐ๋ฅธ transport๋ฅผ ์ง์ ํด์ผ ์ฐ๊ฒฐ๋ฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`mcp` ์ธ์คํด์ค๋ฅผ stdio ๋ชจ๋๋ก ์คํํ๋ ์ฝ๋๋ฅผ ์ ๋ ฅํ์ธ์.\n\n> Claude Desktop ์ค์ ํ์ผ์ ์ด ์คํฌ๋ฆฝํธ ์คํ ๋ช ๋ น์ด๋ฅผ ๋ฑ๋กํ๋ฉด ์๋ ์ฐ๊ฒฐ๋ฉ๋๋ค.", | |
| "expected_command": "mcp.run(transport='stdio')", | |
| "grading_conditions": ["mcp.run", "transport='stdio'"], | |
| "hint": "`mcp.run(transport='...')` ํจํด์ ๋๋ค. ๋ก์ปฌ์ stdio, HTTP ์๋ฒ๋ sse์ ๋๋ค.", | |
| "explanation": "Claude Desktop์ `claude_desktop_config.json`์ ๋ฑ๋ก๋ ๋ช ๋ น์ด๋ก MCP ์๋ฒ ํ๋ก์ธ์ค๋ฅผ ์คํํ๊ณ stdio๋ก ํต์ ํฉ๋๋ค. ์๋ฒ๊ฐ ์คํ๋๋ฉด ๋ํ ์ค `search_docs` ๋๊ตฌ๊ฐ ์๋์ผ๋ก ํ์ฑํ๋ฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "transport='stdio'๊ฐ Claude Desktop ์ฐ๊ฒฐ, 'sse'๊ฐ HTTP ์๋ฒ ์ฐ๊ฒฐ์ ๋๋ค. ์ฐ๊ฒฐ ๋ฐฉ์์ ํ๊ฒฝ์ ๋ง๊ฒ ์ ํํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-lab-25", | |
| "title": "[๋ณด๋์ค] Gradio โ Streamlit ์์ด ์ฑ๋ด UI ๋ง๋ค๊ธฐ", | |
| "task_description": "**์ํฉ**\nStreamlit ์ฑ๋ด์ ์คํธ๋ฆฌ๋ฐ ์๋ต(ํ์ดํ ํจ๊ณผ)์ ๊ตฌํํ๋ ค๋ ์ฝ๋๊ฐ ๋ณต์กํฉ๋๋ค. `st.write_stream`๊ณผ generator ํจ์ ์ค์ ์ด ๋ฒ๊ฑฐ๋กญ์ต๋๋ค.\n\n**๋ฌธ์ **\nStreamlit์ rerun ๊ธฐ๋ฐ์ด๋ผ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ UI ๊ตฌํ์ด ์๋์ ์ผ๋ก ๋ณต์กํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nGradio์ `ChatInterface`๋ generator ํจ์๋ฅผ ๋๊ธฐ๋ฉด ์๋์ผ๋ก ์คํธ๋ฆฌ๋ฐ UI๊ฐ ๋ฉ๋๋ค.\n\n`chat(message, history)` ํจ์๋ฅผ `gr.ChatInterface`์ ์ฐ๊ฒฐํ๊ณ , ์ ๋ชฉ `'RAG ์ฑ๋ด'`์ ์ค์ ํ ๋ค ์คํํ์ธ์.", | |
| "expected_command": "demo = gr.ChatInterface(\n fn=chat,\n title='RAG ์ฑ๋ด'\n)\ndemo.launch()", | |
| "grading_conditions": ["gr.ChatInterface", "fn=chat", "demo.launch()"], | |
| "hint": "`gr.ChatInterface(fn=ํจ์, title='์ ๋ชฉ')`์ผ๋ก ๋ง๋ค๊ณ `.launch()`๋ก ์คํํฉ๋๋ค.", | |
| "explanation": "`gr.ChatInterface`๋ `fn`์ด `(message: str, history: list) -> str` ์๊ทธ๋์ฒ๋ฅผ ๋ฐ๋ฅด๋ฉด ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํฉ๋๋ค. generator ํจ์๋ก ๋ฐ๊พธ๋ฉด ํ ํฐ ๋จ์ ์คํธ๋ฆฌ๋ฐ์ด ๋ฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "gr.ChatInterface๋ ๋ํ ํ์คํ ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํด ์ฑ๋ด์ ํจ์ ํ๋๋ก ๋ง๋ค ์ ์์ต๋๋ค." | |
| }, | |
| { | |
| "id": "docs-chroma-1", | |
| "title": "[Chroma 1] FAISS โ Chroma ๊ต์ฒด โ ์ฌ์์ํด๋ ๋ฐ์ดํฐ ์ ์ง", | |
| "task_description": "**์ํฉ**\n์๋ฒ๋ฅผ ์ฌ์์ํ๋๋ VectorStore๊ฐ ์ฌ๋ผ์ก์ต๋๋ค. ๋งค๋ฒ `ollama pull`์ ๋ค์ ํ ์๋ ์์ด `save_local()`์ ํธ์ถํ๋๋ก ํ๋๋ฐ, ํธ์ถํ๋ ๊ฑธ ํ ๋ฒ ์์ด๋ฒ๋ ธ๋๋ ๋ฐ์ดํฐ๊ฐ ๋ ์๊ฐ์ต๋๋ค.\n\n**๋ฌธ์ **\nFAISS๋ ์๋์ผ๋ก `save_local()`์ ํธ์ถํด์ผ ํฉ๋๋ค. ์์ด๋ฒ๋ฆฌ๋ ์๊ฐ ๋ฐ์ดํฐ๊ฐ ์ฌ๋ผ์ง๋๋ค.\n\n**ํด๊ฒฐ**\nChroma๋ `persist_directory`๋ง ์ง์ ํ๋ฉด ์๋์ผ๋ก ์ ์ฅ๋ฉ๋๋ค.\n\n`chunks`์ `embeddings`๋ก Chroma VectorStore๋ฅผ ๋ง๋ค๊ณ , `./chroma_db`์ ์ ์ฅํ์ธ์. ๊ฒฐ๊ณผ๋ฅผ `db`์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "db = Chroma.from_documents(chunks, embeddings, persist_directory='./chroma_db')", | |
| "grading_conditions": ["Chroma.from_documents", "chunks", "embeddings", "persist_directory='./chroma_db'"], | |
| "hint": "`Chroma.from_documents(๋ฌธ์, ์๋ฒ ๋ฉ, persist_directory='๊ฒฝ๋ก')` ํจํด์ ๋๋ค.", | |
| "explanation": "`persist_directory`๋ฅผ ์ง์ ํ๋ฉด `save_local()` ํธ์ถ ์์ด ์๋์ผ๋ก SQLite ๊ธฐ๋ฐ ๋ก์ปฌ DB์ ์ ์ฅ๋ฉ๋๋ค. `Chroma(persist_directory='./chroma_db', embedding_function=embeddings)`๋ก ์ฌ๋ก๋ํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "Chroma๋ persist_directory ์ง์ ๋ง์ผ๋ก save_local() ์์ด ์๋ ์ ์ฅ๋ฉ๋๋ค. FAISS์ ๊ฐ์ฅ ํฐ ์ฐจ์ด์ ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-chroma-2", | |
| "title": "[Chroma 2] ๊ธฐ์กด Chroma DB ์ฌ๋ก๋ โ ์๋ฒ ๋ฉ ์ฌ๊ณ์ฐ ์์ด", | |
| "task_description": "**์ํฉ**\n์๋ฒ๋ฅผ ์ฌ์์ํ์ต๋๋ค. `./chroma_db` ํด๋์ ์ด๋ฏธ ์ธ๋ฑ์ฑ๋ ๋ฐ์ดํฐ๊ฐ ์๋๋ฐ, ๋ค์ `Chroma.from_documents()`๋ฅผ ํธ์ถํ๋ฉด ์๋ฒ ๋ฉ์ ๋ ๊ณ์ฐํด ๋ช ๋ถ์ด ๊ฑธ๋ฆฝ๋๋ค.\n\n**๋ฌธ์ **\n์ด๋ฏธ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ๋ ค๋ฉด `from_documents`๊ฐ ์๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ๋ก๋ํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`./chroma_db`์ ์ ์ฅ๋ ๊ธฐ์กด Chroma DB๋ฅผ `embeddings` ๋ชจ๋ธ๋ก ์ฌ๋ก๋ํ์ธ์. ๊ฒฐ๊ณผ๋ฅผ `db`์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "db = Chroma(persist_directory='./chroma_db', embedding_function=embeddings)", | |
| "grading_conditions": ["Chroma(", "persist_directory='./chroma_db'", "embedding_function=embeddings"], | |
| "hint": "`Chroma(persist_directory='๊ฒฝ๋ก', embedding_function=์๋ฒ ๋ฉ๋ชจ๋ธ)` ํจํด์ ๋๋ค. `from_documents`์ ๋ค๋ฆ ๋๋ค.", | |
| "explanation": "`Chroma()`๋ ๊ธฐ์กด DB๋ฅผ ๋ถ๋ฌ์ต๋๋ค. `Chroma.from_documents()`๋ ์๋ก ์์ฑํฉ๋๋ค. ์ฌ์์ ์์๋ `Chroma()`๋ฅผ ์จ์ผ ์๋ฒ ๋ฉ ์ฌ๊ณ์ฐ์ ํผํ ์ ์์ต๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "Chroma()๋ ๊ธฐ์กด DB ์ฌ๋ก๋, Chroma.from_documents()๋ ์ ๊ท ์์ฑ์ ๋๋ค. ์ฌ์์ ์ ์ด๋ ์ชฝ์ ์ฐ๋์ง๊ฐ ํต์ฌ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-embed-1", | |
| "title": "[sentence-transformers] OpenAI ์์ด ๋ก์ปฌ ์๋ฒ ๋ฉ ์์ฑ", | |
| "task_description": "**์ํฉ**\nRAG๋ฅผ ๊ตฌ์ฑํ๋๋ฐ ์๋ฒ ๋ฉ ์์ฑ์ OpenAI API๋ฅผ ์ฐ๊ณ ์์ต๋๋ค. ๋ฌธ์๊ฐ ๋ง์์ง์ ์๋ฒ ๋ฉ ๋น์ฉ์ด ์์๋ณด๋ค ๋ง์ด ๋์์ต๋๋ค.\n\n**๋ฌธ์ **\nOpenAI ์๋ฒ ๋ฉ์ ํ ํฐ๋น ๋น์ฉ์ด ๋ฐ์ํฉ๋๋ค. ๋์ฉ๋ ๋ฌธ์๋ ๋ฐ๋ณต ์คํ์ ๋ถ๋ด์ด ๋ฉ๋๋ค.\n\n**ํด๊ฒฐ**\nHuggingFace์ `all-MiniLM-L6-v2` ๋ชจ๋ธ๋ก ์์ ๋ฌด๋ฃ ๋ก์ปฌ ์๋ฒ ๋ฉ์ ์ค์ ํ์ธ์. ๊ฒฐ๊ณผ๋ฅผ `embeddings` ๋ณ์์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "embeddings = HuggingFaceEmbeddings(model_name='sentence-transformers/all-MiniLM-L6-v2')", | |
| "grading_conditions": ["HuggingFaceEmbeddings", "model_name='sentence-transformers/all-MiniLM-L6-v2'"], | |
| "hint": "`HuggingFaceEmbeddings(model_name='...')` ํจํด์ ๋๋ค. ์ฒซ ์คํ ์ ๋ชจ๋ธ ํ์ผ์ ๋ค์ด๋ก๋ํฉ๋๋ค.", | |
| "explanation": "์ดํ `Chroma.from_documents(chunks, embeddings)` ๋๋ `FAISS.from_documents(chunks, embeddings)`์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค. OpenAI Embeddings์ ์ธํฐํ์ด์ค๊ฐ ๋์ผํด ์ฝ๋ ๋ณ๊ฒฝ์ด ํ ์ค์ ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "HuggingFaceEmbeddings๋ OpenAI Embeddings์ ์ธํฐํ์ด์ค๊ฐ ๋์ผํด model_name ํ ์ค๋ง ๋ฐ๊พธ๋ฉด ๋ฌด๋ฃ ๋ก์ปฌ ์๋ฒ ๋ฉ์ผ๋ก ์ ํ๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-ragas-1", | |
| "title": "[Ragas 1] ํ๊ฐ ๋ฐ์ดํฐ์ ๊ตฌ์ฑ", | |
| "task_description": "**์ํฉ**\nMLflow๋ก `chunk_size`์ `k`๋ฅผ ๋ฐ๊ฟ๊ฐ๋ฉฐ ์คํํ๋๋ฐ, `answer_relevance=0.87`์ด๋ผ๊ณ ๊ธฐ๋กํ์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ์ด ์ซ์๋ฅผ ์ด๋ป๊ฒ ์ธก์ ํ๋์ง ๊ธฐ์ค์ด ์์ด ๋ค์ ์คํ๊ณผ ๋น๊ต๊ฐ ์ ๋ฉ๋๋ค.\n\n**๋ฌธ์ **\n\"์ข์ RAG\"์ ๊ธฐ์ค์ด ์์ต๋๋ค. ์์นํ๋ ํ๊ฐ๊ฐ ํ์ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nRagas ํ๊ฐ์ฉ Dataset์ ๊ตฌ์ฑํ์ธ์. ์๋ ๋ฐ์ดํฐ๋ฅผ `Dataset.from_dict()`๋ก ๋ง๋ค์ด `eval_dataset`์ ์ ์ฅํ์ธ์.\n\n```python\ndata = {\n 'question': ['LangChain์ด ๋ญ์ผ?'],\n 'answer': ['LangChain์ LLM ์ฑ ํ๋ ์์ํฌ์ ๋๋ค.'],\n 'contexts': [['LangChain์ LLM ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ํ๋ ์์ํฌ์ ๋๋ค.']],\n 'ground_truth': ['LangChain์ LLM ์ฑ ๊ฐ๋ฐ ํ๋ ์์ํฌ์ ๋๋ค.']\n}\n```", | |
| "expected_command": "eval_dataset = Dataset.from_dict(data)", | |
| "grading_conditions": ["Dataset.from_dict", "data"], | |
| "hint": "`from datasets import Dataset` ํ `Dataset.from_dict(๋์ ๋๋ฆฌ)` ํจํด์ ๋๋ค.", | |
| "explanation": "Ragas๋ HuggingFace `datasets` ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ Dataset ํ์์ ์ ๋ ฅ์ผ๋ก ๋ฐ์ต๋๋ค. `question`, `answer`, `contexts`, `ground_truth` 4๊ฐ ํค๊ฐ ๊ธฐ๋ณธ ๊ตฌ์กฐ์ ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "Ragas๋ question, answer, contexts, ground_truth 4๊ฐ ํค๋ฅผ ๊ฐ์ง Dataset์ ์ ๋ ฅ์ผ๋ก ๋ฐ์ต๋๋ค. ์ด ๊ตฌ์กฐ๊ฐ RAG ํ๊ฐ์ ํ์ค ํ์์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-ragas-2", | |
| "title": "[Ragas 2] RAG ํ์ง ์๋ ํ๊ฐ ์คํ", | |
| "task_description": "**์ํฉ**\nํ๊ฐ ๋ฐ์ดํฐ์ ์ ๊ตฌ์ฑํ์ต๋๋ค. ์ด์ ์ค์ ๋ก Faithfulness, Answer Relevancy, Context Precision์ ์๋์ผ๋ก ์ธก์ ํด๋ด ๋๋ค.\n\n**ํด๊ฒฐ**\n`eval_dataset`์ ๋ํด 3๊ฐ์ง ๋ฉํธ๋ฆญ์ผ๋ก Ragas ํ๊ฐ๋ฅผ ์คํํ์ธ์.\n\n- ๋ฉํธ๋ฆญ: `faithfulness`, `answer_relevancy`, `context_precision`\n- ๊ฒฐ๊ณผ๋ฅผ `result`์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "result = evaluate(eval_dataset, metrics=[faithfulness, answer_relevancy, context_precision])", | |
| "grading_conditions": ["evaluate", "eval_dataset", "faithfulness", "answer_relevancy", "context_precision"], | |
| "hint": "`evaluate(dataset, metrics=[...])` ํจํด์ ๋๋ค. ๋ฉํธ๋ฆญ์ ๋ฆฌ์คํธ๋ก ์ ๋ฌํฉ๋๋ค.", | |
| "explanation": "`result`์๋ ๊ฐ ๋ฉํธ๋ฆญ ์ ์๊ฐ ๋ด๊น๋๋ค. `result.to_pandas()`๋ก DataFrame ๋ณํ ํ `mlflow.log_metrics(result.to_pandas().mean().to_dict())`๋ก MLflow์ ๊ธฐ๋กํ๋ฉด ํ๋ผ๋ฏธํฐ๋ณ ํ์ง ๋น๊ต๊ฐ ๊ฐ๋ฅํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "result.to_pandas()๋ก ๋ณํํ ๋ค mlflow.log_metrics()์ ๋๊ธฐ๋ฉด ํ๋ผ๋ฏธํฐ ์คํ๊ณผ ํ์ง ๋ฉํธ๋ฆญ์ ํจ๊ป ์ถ์ ํ ์ ์์ต๋๋ค." | |
| }, | |
| { | |
| "id": "docs-graph-1", | |
| "title": "[LangGraph 1] StateGraph ๊ธฐ๋ณธ ๊ตฌ์กฐ", | |
| "task_description": "**์ํฉ**\nRAG ์ฒด์ธ์ ์ฐ๋ค ๋ณด๋ \"๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด ๋ค์ ์๋\", \"ํ์ง์ด ๋ฎ์ผ๋ฉด ๋ค๋ฅธ ๊ฒฝ๋ก๋ก\" ๊ฐ์ ์กฐ๊ฑด ๋ถ๊ธฐ๊ฐ ํ์ํด์ก์ต๋๋ค. LCEL ์ฒด์ธ์ AโBโC ์ ํ์ด๋ผ ๋ถ๊ธฐ๋ฅผ ํํํ ์ ์์ต๋๋ค.\n\n**๋ฌธ์ **\n๋จ์ ์ฒด์ธ์ผ๋ก๋ ๋ฃจํ๋ ์กฐ๊ฑด ๋ถ๊ธฐ๋ฅผ ๊ตฌํํ๊ธฐ ์ด๋ ต์ต๋๋ค.\n\n**ํด๊ฒฐ**\nLangGraph๋ก 2๊ฐ ๋ ธ๋(`retrieve`, `generate`)๋ฅผ ๊ฐ์ง StateGraph๋ฅผ ๋ง๋์ธ์.\n\n```python\nclass State(TypedDict):\n question: str\n answer: str\n```\n\n- ๋ ธ๋ ์ถ๊ฐ: `retrieve`, `generate`\n- ์ง์ ์ : `retrieve`\n- ์ฃ์ง: `retrieve โ generate โ END`\n- ์ปดํ์ผํด์ `app`์ ์ ์ฅ", | |
| "expected_command": "graph = StateGraph(State)\ngraph.add_node('retrieve', retrieve)\ngraph.add_node('generate', generate)\ngraph.set_entry_point('retrieve')\ngraph.add_edge('retrieve', 'generate')\ngraph.add_edge('generate', END)\napp = graph.compile()", | |
| "grading_conditions": ["StateGraph", "add_node", "set_entry_point", "add_edge", "END", "graph.compile()"], | |
| "hint": "StateGraph โ add_node โ set_entry_point โ add_edge โ compile() ์์์ ๋๋ค.", | |
| "explanation": "`graph.compile()`๋ก ์คํ ๊ฐ๋ฅํ ์ฑ์ ๋ง๋ญ๋๋ค. `app.invoke({'question': '์ง๋ฌธ'})`์ผ๋ก ์คํํ๋ฉด retrieve ๋ ธ๋ โ generate ๋ ธ๋ ์์๋ก State๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "LangGraph๋ compile() ํ invoke()๋ก ์คํํฉ๋๋ค. ์ฒด์ธ์ invoke()์ ๊ฐ์ ์ธํฐํ์ด์ค์ด์ง๋ง, ๋ด๋ถ์ ์ผ๋ก ๊ทธ๋ํ ๊ตฌ์กฐ๋ฅผ ์คํํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-graph-2", | |
| "title": "[LangGraph 2] conditional_edges โ ์กฐ๊ฑด ๋ถ๊ธฐ", | |
| "task_description": "**์ํฉ**\ngenerate ๋ ธ๋์์ LLM์ด '๋ชจ๋ฅด๊ฒ ์ต๋๋ค'๋ฅผ ๋ฐํํ๋ฉด retrieve๋ฅผ ๋ค์ ์๋ํ๊ณ ์ถ์ต๋๋ค. ๊ทธ๋ฐ๋ฐ `add_edge`๋ ํญ์ ๊ณ ์ ๋ ๋ค์ ๋ ธ๋๋ก ์ด๋ํฉ๋๋ค.\n\n**๋ฌธ์ **\n๋ ธ๋ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ๋ค์ ๋ ธ๋๋ฅผ ๋์ ์ผ๋ก ๊ฒฐ์ ํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`judge` ๋ ธ๋์ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ๋ถ๊ธฐํ๋ conditional_edges๋ฅผ ์ถ๊ฐํ์ธ์.\n\n- router ํจ์: `should_retry` (์ด๋ฏธ ์ ์๋จ)\n- `'retry'` โ `'retrieve'` ๋ ธ๋๋ก\n- `'done'` โ `END`๋ก", | |
| "expected_command": "graph.add_conditional_edges('judge', should_retry, {'retry': 'retrieve', 'done': END})", | |
| "grading_conditions": ["add_conditional_edges", "'judge'", "should_retry", "'retry'", "retrieve", "END"], | |
| "hint": "`graph.add_conditional_edges('๋ ธ๋', ๋ผ์ฐํฐํจ์, {'๋ฐํ๊ฐ': '๋ค์๋ ธ๋', ...})` ํจํด์ ๋๋ค.", | |
| "explanation": "`should_retry` ํจ์๊ฐ State๋ฅผ ๋ฐ์ `'retry'` ๋๋ `'done'`์ ๋ฐํํ๋ฉด, ๊ฐ๊ฐ `retrieve` ๋ ธ๋ ๋๋ `END`๋ก ์ด๋ํฉ๋๋ค. ์ด๊ฒ์ด ReAct ์์ด์ ํธ์ \"ํ๋ โ ๊ด์ฐฐ โ ํ๋จ\" ๋ฃจํ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ์ ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "add_conditional_edges๋ก ๋ผ์ฐํฐ ํจ์์ ๋ฐํ๊ฐ์ ๋ฐ๋ผ ๋ค์ ๋ ธ๋๋ฅผ ๋์ ์ผ๋ก ๊ฒฐ์ ํ๋ ๊ฒ์ด LangGraph ์์ด์ ํธ์ ํต์ฌ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-litellm-1", | |
| "title": "[LiteLLM] ๋ชจ๋ธ๋ช ํ๋๋ก Ollama โ OpenAI ์ ํ", | |
| "task_description": "**์ํฉ**\n๋ก์ปฌ์์ Ollama๋ก ๊ฐ๋ฐํ๋๋ฐ, ํ๋ก๋์ ์์๋ OpenAI๋ฅผ ์จ์ผ ํฉ๋๋ค. `OllamaLLM`์ `ChatOpenAI`๋ก ๋ฐ๊พธ๋ ค๋ import, ํด๋์ค๋ช , API ํ์์ด ๋ชจ๋ ๋ฌ๋ผ ์ฝ๋๋ฅผ ๋ง์ด ๊ณ ์ณ์ผ ํฉ๋๋ค.\n\n**๋ฌธ์ **\nLLM ํ๋ก๋ฐ์ด๋๋ง๋ค SDK๊ฐ ๋ฌ๋ผ ๊ต์ฒด ์ ์ฝ๋ ์์ ์ด ๋ง์ต๋๋ค.\n\n**ํด๊ฒฐ**\nLiteLLM์ `completion()`์ผ๋ก Ollama `llama3.2`์ `'์๋ '`์ ๋ณด๋ด๋ ์ฝ๋๋ฅผ ์์ฑํ์ธ์. ๋์ค์ `'gpt-4o-mini'`๋ก ๋ฐ๊ฟ ๋ model ํ๋ผ๋ฏธํฐ ํ๋๋ง ์์ ํ๋ฉด ๋ฉ๋๋ค.", | |
| "expected_command": "response = completion(model='ollama/llama3.2', messages=[{'role': 'user', 'content': '์๋ '}])", | |
| "grading_conditions": ["completion", "model='ollama/llama3.2'", "messages", "role", "user"], | |
| "hint": "`completion(model='ํ๋ก๋ฐ์ด๋/๋ชจ๋ธ๋ช ', messages=[...])` ํจํด์ ๋๋ค.", | |
| "explanation": "์ดํ `model='gpt-4o-mini'`๋ก๋ง ๋ฐ๊พธ๋ฉด OpenAI๋ก ์ ํ๋ฉ๋๋ค. `response.choices[0].message.content`๋ก ์๋ต์ ์ฝ๋ ๋ฐฉ์๋ ๋์ผํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "model='ํ๋ก๋ฐ์ด๋/๋ชจ๋ธ๋ช ' ํ์์ผ๋ก ์ฝ๋ ๋ณ๊ฒฝ ์์ด LLM์ ์ ํํฉ๋๋ค. ์ด๊ฒ์ด LiteLLM์ ํต์ฌ ๊ฐ์น์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-chainlit-1", | |
| "title": "[Chainlit] ์คํธ๋ฆฌ๋ฐ ์ฑ๋ด โ Streamlit๋ณด๋ค ๊ฐ๊ฒฐํ๊ฒ", | |
| "task_description": "**์ํฉ**\nStreamlit ์ฑ๋ด์ ์คํธ๋ฆฌ๋ฐ(ํ์ดํ ํจ๊ณผ)์ ๊ตฌํํ๋๋ฐ, `st.write_stream`, generator ํจ์, `st.empty()` ๋ฑ ์ฌ๋ฌ ๊ฐ๋ ์ด ์ฝํ ์ฝ๋๊ฐ ๋ณต์กํฉ๋๋ค.\n\n**๋ฌธ์ **\nStreamlit์ rerun ๊ธฐ๋ฐ์ด๋ผ ์คํธ๋ฆฌ๋ฐ UI ๊ตฌํ์ด ์ฅํฉํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nChainlit์ผ๋ก ์คํธ๋ฆฌ๋ฐ ์ฑ๋ด์ ๋ง๋์ธ์.\n\n`@cl.on_message` ๋ฐ์ฝ๋ ์ดํฐ๋ก ํจ์๋ฅผ ์ ์ํ๊ณ , `cl.Message(content='')`๋ฅผ ๋ง๋ ๋ค `chain.astream()`์ผ๋ก ํ ํฐ์ ๋ฐ์ `msg.stream_token(chunk)`์ผ๋ก ์ ์กํ์ธ์.", | |
| "expected_command": "@cl.on_message\nasync def main(message: cl.Message):\n msg = cl.Message(content='')\n async for chunk in chain.astream({'question': message.content}):\n await msg.stream_token(chunk)\n await msg.send()", | |
| "grading_conditions": ["@cl.on_message", "cl.Message", "chain.astream", "msg.stream_token", "msg.send()"], | |
| "hint": "`@cl.on_message` โ `cl.Message(content='')` ์์ฑ โ `astream`์ผ๋ก ํ ํฐ ๋ฐ๊ธฐ โ `stream_token` โ `send()` ์์์ ๋๋ค.", | |
| "explanation": "Chainlit์์ `astream()`๊ณผ `stream_token()`์ ์กฐํฉํ๋ฉด ํ ํฐ ๋จ์ ์คํธ๋ฆฌ๋ฐ์ด ๋ฉ๋๋ค. Streamlit๋ณด๋ค ํจ์ฌ ์ ์ ์ฝ๋๋ก ๋์ผ ํจ๊ณผ๋ฅผ ๊ตฌํํฉ๋๋ค. `chainlit run app.py`๋ก ์คํํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "astream()์ผ๋ก ํ ํฐ์ ๋ฐ์ stream_token()์ผ๋ก ์ ์กํ๋ ํจํด์ด Chainlit ์คํธ๋ฆฌ๋ฐ์ ์ ๋ถ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-redis-1", | |
| "title": "[Redis] ๋ํ ํ์คํ ๋ฆฌ ์์ํ โ ์ฌ์์ํด๋ ๊ธฐ์ต", | |
| "task_description": "**์ํฉ**\n```python\nstore = {} # ์ธ๋ฉ๋ชจ๋ฆฌ\ndef get_session_history(session_id):\n if session_id not in store:\n store[session_id] = ChatMessageHistory()\n return store[session_id]\n```\n์๋ฒ๋ฅผ ์ฌ์์ํ๋ ๋ชจ๋ ์ฌ์ฉ์์ ๋ํ ํ์คํ ๋ฆฌ๊ฐ ์ฌ๋ผ์ก์ต๋๋ค. `store` ๋์ ๋๋ฆฌ๊ฐ ๋ฉ๋ชจ๋ฆฌ์๋ง ์์๊ธฐ ๋๋ฌธ์ ๋๋ค.\n\n**๋ฌธ์ **\n์ธ๋ฉ๋ชจ๋ฆฌ ํ์คํ ๋ฆฌ๋ ํ๋ก์ธ์ค ์ข ๋ฃ ์ ์๋ฉธํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`get_session_history` ํจ์๋ฅผ `RedisChatMessageHistory`๋ฅผ ์ฌ์ฉํ๋๋ก ์์ ํ์ธ์.\n- Redis URL: `'redis://localhost:6379'`", | |
| "expected_command": "def get_session_history(session_id: str):\n return RedisChatMessageHistory(session_id=session_id, url='redis://localhost:6379')", | |
| "grading_conditions": ["RedisChatMessageHistory", "session_id=session_id", "url='redis://localhost:6379'"], | |
| "hint": "`RedisChatMessageHistory(session_id=session_id, url='redis://...')` ํจํด์ ๋๋ค.", | |
| "explanation": "ํจ์ ๋ด๋ถ๋ฅผ `RedisChatMessageHistory`๋ก ๊ต์ฒดํ๋ฉด ๋ฉ๋๋ค. `RunnableWithMessageHistory`๋ ๊ทธ๋๋ก ๋๊ณ ์ด ํจ์๋ง ๋ฐ๊พธ๋ฉด ์์์ฑ์ ์ป์ต๋๋ค. Redis ์๋ฒ๋ `docker run -d -p 6379:6379 redis`๋ก ์คํํ์ธ์.", | |
| "difficulty": "easy", | |
| "takeaway": "get_session_history ํจ์ ๋ด๋ถ๋ฅผ RedisChatMessageHistory๋ก ๊ต์ฒดํ๊ธฐ๋ง ํ๋ฉด ๋ํ ํ์คํ ๋ฆฌ๊ฐ ์ฌ์์์๋ ์ ์ง๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-unstructured-1", | |
| "title": "[Unstructured] PDFยทWord ๋ฑ ์ค๋ฌด ๋ฌธ์ ํ์ฑ", | |
| "task_description": "**์ํฉ**\nDirectoryLoader๋ก `./docs` ํด๋๋ฅผ ๋ก๋ํ๋ ค๋๋ฐ, `.md` ํ์ผ์ ์ ๋์ง๋ง ํ์์ ๊ณต์ ๋ฐ์ ๋ณด๊ณ ์๋ `.pdf`, `.docx`, `.pptx`๊ฐ ์์ฌ ์์ต๋๋ค. ํ์ฅ์๋ง๋ค ๋ค๋ฅธ ๋ก๋๋ฅผ ์จ์ผ ํด ์ฝ๋๊ฐ ๋ณต์กํด์ก์ต๋๋ค.\n\n**๋ฌธ์ **\nํ์ผ ํ์๋ง๋ค ๋ค๋ฅธ ๋ก๋๋ฅผ ์ฐ๋ฉด ์ฝ๋๊ฐ ๋ถ๊ธฐ๋ฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`UnstructuredFileLoader`๋ฅผ `loader_cls`๋ก ์ง์ ํด `./docs` ํด๋์ ๋ชจ๋ ํ์ผ์ ํ์์ ์๊ด์์ด ๋ก๋ํ์ธ์. ๊ฒฐ๊ณผ๋ฅผ `docs` ๋ณ์์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "loader = DirectoryLoader('./docs', glob='**/*', loader_cls=UnstructuredFileLoader)\ndocs = loader.load()", | |
| "grading_conditions": ["DirectoryLoader", "./docs", "loader_cls=UnstructuredFileLoader", "loader.load()"], | |
| "hint": "`DirectoryLoader('ํด๋', glob='**/*', loader_cls=UnstructuredFileLoader)` ํจํด์ ๋๋ค.", | |
| "explanation": "`UnstructuredFileLoader`๋ ํ์ฅ์๋ฅผ ์๋ ๊ฐ์งํด ์ ์ ํ ํ์๋ฅผ ์ ํํฉ๋๋ค. `.pdf`, `.docx`, `.pptx`, `.jpg` ๋ฑ์ ๋์ผ ์ฝ๋๋ก ์ฒ๋ฆฌํฉ๋๋ค. PDF์๋ `pip install unstructured[pdf]`๊ฐ ํ์ํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "loader_cls=UnstructuredFileLoader ํ๋๋ก PDFยทWordยทPowerPoint ๋ฑ ๋ชจ๋ ๋ฌธ์ ํ์์ ๋์ผํ ์ฝ๋๋ก ์ฒ๋ฆฌํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-news-1", | |
| "title": "[๋ด์ค ์์ฝ๊ธฐ 1] feedparser๋ก RSS ํ์ฑ", | |
| "task_description": "**ํ๋ก์ ํธ: ๋ด์ค ์๋ ์์ฝ๊ธฐ**\n๋งค์ผ ์์นจ ๋ด์ค ์ฌ์ดํธ 5๊ฐ๋ฅผ ์ง์ ์ด์ด ์ฝ์ต๋๋ค. ์๋ํํ๊ณ ์ถ์๋ฐ ์ฌ์ดํธ๋ง๋ค HTML ๊ตฌ์กฐ๊ฐ ๋ฌ๋ผ ํฌ๋กค๋ง ์ฝ๋๊ฐ ๊ฐ๊ฐ ํ์ํฉ๋๋ค.\n\n**๋ฌธ์ **\nHTML ํฌ๋กค๋ง์ ์ฌ์ดํธ ๊ตฌ์กฐ๊ฐ ๋ฐ๋๋ฉด ์ฝ๋๊ฐ ๊นจ์ง๋๋ค. ๋ด์ค ์ฌ์ดํธ ๋๋ถ๋ถ์ด RSS๋ผ๋ ํ์ค ํ์์ ์ ๊ณตํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`feedparser`๋ก BBC ๋ด์ค RSS๋ฅผ ํ์ฑํ๊ณ , ์ต์ ๊ธฐ์ฌ 5๊ฐ์ ์์ฝ์ `articles` ๋ฆฌ์คํธ์ ์ ์ฅํ์ธ์.\n\nFeed URL: `https://feeds.bbci.co.uk/news/rss.xml`", | |
| "expected_command": "import feedparser\nfeed = feedparser.parse('https://feeds.bbci.co.uk/news/rss.xml')\narticles = [entry.summary for entry in feed.entries[:5]]", | |
| "grading_conditions": ["feedparser.parse", "feed.entries", "entry.summary", "articles"], | |
| "hint": "`feedparser.parse(url)`๋ก ํ์ฑํ๋ฉด `feed.entries`์ ๊ธฐ์ฌ ๋ชฉ๋ก์ด ๋ค์ด์์ต๋๋ค.", | |
| "explanation": "RSS๋ XML ๊ธฐ๋ฐ ํ์ค ํ์์ด๋ผ ์ด๋ ์ฌ์ดํธ๋ `feed.entries`๋ก ๋์ผํ๊ฒ ์ ๊ทผํฉ๋๋ค. HTML ๊ตฌ์กฐ ๋ณ๊ฒฝ์ ์ํฅ์ ๋ฐ์ง ์์ต๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "RSS๋ ์ฌ์ดํธ ๊ตฌ์กฐ์ ๊ด๊ณ์์ด feed.entries๋ก ๋์ผํ๊ฒ ์ ๊ทผํ๋ ํ์ค ํ์์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-news-2", | |
| "title": "[๋ด์ค ์์ฝ๊ธฐ 2] map_reduce โ ํ ํฐ ์ด๊ณผ ์์ด ์์ฝ", | |
| "task_description": "**์ํฉ**\n๊ธฐ์ฌ 10๊ฐ๋ฅผ ํ๊บผ๋ฒ์ ์์ฝํ๋ ค๊ณ `stuff` ๋ฐฉ์์ ์ผ๋๋:\n```\nError: This model's maximum context length is 128000 tokens.\nYour message resulted in 180000 tokens.\n```\n\n**๋ฌธ์ **\n`stuff`๋ ๋ชจ๋ ๋ฌธ์๋ฅผ ํ๋์ ํ๋กฌํํธ์ ๋ฃ์ด์ ๋ฌธ์๊ฐ ๋ง์ผ๋ฉด ํ ํฐ์ ์ด๊ณผํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n`map_reduce`๋ฅผ ์ฌ์ฉํ์ธ์. ๊ฐ ๊ธฐ์ฌ๋ฅผ ๊ฐ๋ณ ์์ฝํ ๋ค ํฉ์นฉ๋๋ค.\n\n`articles` โ `Document` ๋ฆฌ์คํธ๋ก ๋ณํ ํ `map_reduce` ์ฒด์ธ์ผ๋ก ์์ฝํ๋ ์ฝ๋์ `???` ๋ถ๋ถ์ ์ฑ์ฐ์ธ์.\n\n```python\ndocs = [Document(page_content=a) for a in articles]\nchain = ???\nresult = chain.invoke(docs)\n```", | |
| "expected_command": "chain = load_summarize_chain(llm, chain_type='map_reduce')", | |
| "grading_conditions": ["load_summarize_chain", "llm", "chain_type='map_reduce'"], | |
| "hint": "`load_summarize_chain(llm, chain_type='...')` ํจํด์ ๋๋ค.", | |
| "explanation": "`map_reduce`๋ ๊ฐ ๊ธฐ์ฌ๋ฅผ ๊ฐ๋ณ ์์ฝ(map)ํ๊ณ , ๊ฐ๋ณ ์์ฝ๋ค์ ๋ค์ ํฉ์ณ ์ต์ข ์์ฝ(reduce)์ ๋ง๋ญ๋๋ค. ์๋ฌด๋ฆฌ ๊ธฐ์ฌ๊ฐ ๋ง์๋ ํ ํฐ ์ด๊ณผ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "map_reduce๋ ๊ฐ ๋ฌธ์๋ฅผ ๊ฐ๋ณ ์์ฝ(map)ํ ๋ค ํฉ์น๋(reduce) ๋ฐฉ์์ผ๋ก ํ ํฐ ์ ํ ์์ด ์ฌ๋ฌ ๋ฌธ์๋ฅผ ์์ฝํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-news-3", | |
| "title": "[๋ด์ค ์์ฝ๊ธฐ 3] Airflow โ ์๋ ๋์๋ ์๋ ์คํ", | |
| "task_description": "**์ํฉ**\nfeedparser + LangChain ์์ฝ ์คํฌ๋ฆฝํธ๊ฐ ์ ๋์ํ๋๋ฐ, ๋งค์ผ ์์นจ ์ง์ ํฐ๋ฏธ๋์ `python news_summary.py`๋ฅผ ์ ๋ ฅํด์ผ ํฉ๋๋ค. ์ฃผ๋ง์ ๊น๋นกํ๋ฉด ๊ทธ๋ ์์ฝ์ด ์์ต๋๋ค.\n\n**๋ฌธ์ **\n์๋ ์คํ์ ๋ฐ๋์ ๋น ๋จ๋ฆฌ๋ ๋ ์ด ์๊น๋๋ค.\n\n**ํด๊ฒฐ**\n`run_news_summary` ํจ์๋ฅผ ๋งค์ผ ์๋ ์คํํ๋ Airflow DAG๋ฅผ ์ ์ํ์ธ์.\n\n- DAG ID: `daily_news_summary`, ์ค์ผ์ค: `@daily`\n- task_id: `'summarize'`, python_callable: `run_news_summary`", | |
| "expected_command": "with DAG('daily_news_summary', schedule='@daily', start_date=datetime(2024, 1, 1)) as dag:\n task = PythonOperator(task_id='summarize', python_callable=run_news_summary)", | |
| "grading_conditions": ["daily_news_summary", "@daily", "PythonOperator", "task_id='summarize'", "python_callable=run_news_summary"], | |
| "hint": "`with DAG(...) as dag:` ๋ธ๋ก ์์ PythonOperator๋ฅผ ์ ์ํฉ๋๋ค.", | |
| "explanation": "์ด DAG๋ ๋งค์ผ ์์ ์ `run_news_summary`๋ฅผ ์๋ ์คํํฉ๋๋ค. Airflow ์ค์ผ์ค๋ฌ๊ฐ ์คํํ๋ฏ๋ก ๋ด๋น์๊ฐ ์์ด๋, ์๋ ์ค์๋ ๋์ํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "RAG ์ฑ๋ด์์ ๋ฐฐ์ด Airflow DAG + PythonOperator ํจํด์ด ๋ด์ค ์์ฝ๊ธฐ ์๋ํ์ ๊ทธ๋๋ก ์ฌ์ฌ์ฉ๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-news-4", | |
| "title": "[๋ด์ค ์์ฝ๊ธฐ 4] st.expander โ ๊ธด ๋ชฉ๋ก์ ๊น๋ํ๊ฒ", | |
| "task_description": "**์ํฉ**\n์์ฝ๋ ๊ธฐ์ฌ 10๊ฐ๋ฅผ `st.write()`๋ก ๋์ดํ๋๋ ํ์ด์ง ์คํฌ๋กค์ด ๋์ด ์๊ณ , ์ํ๋ ๊ธฐ์ฌ๋ฅผ ์ฐพ๊ธฐ ์ด๋ ต์ต๋๋ค.\n\n**๋ฌธ์ **\n๋ชจ๋ ๋ด์ฉ์ ํผ์ณ๋์ผ๋ฉด ํ๋์ ๋ณด๊ธฐ ์ด๋ ต์ต๋๋ค.\n\n**ํด๊ฒฐ**\n`summaries` ๋ฆฌ์คํธ(`{'title': ..., 'content': ...}`)๋ฅผ `st.expander`๋ก ๋ ๋๋งํ์ธ์. ์ ๋ชฉ๋ง ๋ณด์ด๊ณ ํด๋ฆญ ์ ์์ฝ์ด ํผ์ณ์ง๋๋ก.", | |
| "expected_command": "for s in summaries:\n with st.expander(s['title']):\n st.write(s['content'])", | |
| "grading_conditions": ["st.expander", "s['title']", "st.write", "s['content']"], | |
| "hint": "`with st.expander(์ ๋ชฉ):` ๋ธ๋ก ์์ ๋ด์ฉ์ ๋ฃ์ผ๋ฉด ํด๋ฆญ ์ ํผ์ณ์ง๋๋ค.", | |
| "explanation": "`st.expander`๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ํ ์๊ณ ํด๋ฆญํ๋ฉด ๋ด์ฉ์ด ๋์ต๋๋ค. ํญ๋ชฉ์ด ๋ง์ ๋ ํ๋ฉด์ ๊น๋ํ๊ฒ ์ ์งํ๋ฉด์๋ ์์ธ ๋ด์ฉ์ ๋ณผ ์ ์๊ฒ ํฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "st.expander๋ ํญ๋ชฉ์ด ๋ง์ ๋ ์ ๋ชฉ๋ง ๋ณด์ฌ์ฃผ๊ณ ํด๋ฆญ ์ ๋ด์ฉ์ ํผ์น๋ Streamlit์ ํต์ฌ UI ์ปดํฌ๋ํธ์ ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-sql-1", | |
| "title": "[Text-to-SQL 1] SQLDatabase โ DB ์คํค๋ง๋ฅผ LLM์ ์๋ ค์ฃผ๊ธฐ", | |
| "task_description": "**ํ๋ก์ ํธ: Text-to-SQL ์์ฑ๊ธฐ**\nํ์๋ค์ด \"์ด ๋ฌ ๋งค์ถ ์์ 5๊ฐ ์ํ์ด ๋ญ์์?\"๋ฅผ ๋ฌผ์ด๋ณผ ๋๋ง๋ค SQL์ ๋์ ์จ์ค์ผ ํฉ๋๋ค. ํ๋ฃจ์๋ ์์ญ ๋ฒ์ ๋๋ค.\n\n---\n\n**๋จ๊ณ 1**: LLM์๊ฒ DB ๊ตฌ์กฐ๋ฅผ ์๋ ค์ค์ผ ์ฌ๋ฐ๋ฅธ SQL์ด ๋์ต๋๋ค.\n`SQLDatabase`๋ก `./shop.db`์ ์ฐ๊ฒฐํ์ธ์. (์คํค๋ง๋ฅผ ์๋์ผ๋ก ์ฝ์ด LLM ํ๋กฌํํธ์ ์ฃผ์ ํฉ๋๋ค)\n\n๊ฒฐ๊ณผ๋ฅผ `db` ๋ณ์์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "db = SQLDatabase.from_uri('sqlite:///./shop.db')", | |
| "grading_conditions": ["SQLDatabase.from_uri", "sqlite", "shop.db"], | |
| "hint": "`SQLDatabase.from_uri('sqlite:///๊ฒฝ๋ก')` ํจํด์ ๋๋ค. ์ฌ๋์๊ฐ 3๊ฐ์ ๋๋ค.", | |
| "explanation": "`SQLDatabase.from_uri()`๋ ์ฐ๊ฒฐ๊ณผ ๋์์ ํ ์ด๋ธ๋ช ยท์ปฌ๋ผ ์คํค๋ง๋ฅผ ์ฝ์ด LLM ํ๋กฌํํธ์ ์๋ ์ฃผ์ ํ ์ค๋น๋ฅผ ํฉ๋๋ค. LLM์ด ์กด์ฌํ์ง ์๋ ์ปฌ๋ผ์ ์ฐ๋ ์ค์๋ฅผ ์ค์ฌ์ค๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "SQLDatabase.from_uri()๊ฐ DB ์คํค๋ง๋ฅผ ์๋์ผ๋ก ์ฝ์ด LLM์ด ์กด์ฌํ๋ ํ ์ด๋ธยท์ปฌ๋ผ๋ง ์ฐธ์กฐํ๋๋ก ์ ๋ํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-sql-2", | |
| "title": "[Text-to-SQL 2] create_sql_query_chain โ ์์ฐ์ด โ SQL", | |
| "task_description": "**์ํฉ**\nLLM์๊ฒ ์ง์ \"SQL ์จ์ค\"๋ผ๊ณ ํ๋๋:\n```sql\nSELECT * FROM sales ORDER BY revenue DESC LIMIT 5\n```\n`sales` ํ ์ด๋ธ์ด ์๊ณ `orders`๊ฐ ๋ง๋๋ฐ, LLM์ด DB ์คํค๋ง๋ฅผ ๋ชฐ๋ผ์ ํ๋ฆฐ ํ ์ด๋ธ๋ช ์ ์ผ์ต๋๋ค.\n\n**๋ฌธ์ **\nLLM์ด ์ค์ DB ๊ตฌ์กฐ๋ฅผ ๋ชจ๋ฅด๋ฉด ์กด์ฌํ์ง ์๋ ํ ์ด๋ธ/์ปฌ๋ผ์ ์๋๋ค.\n\n**ํด๊ฒฐ**\n`create_sql_query_chain`์ `db` ์คํค๋ง๋ฅผ ์๋ ์ฃผ์ ํฉ๋๋ค.\n`'๊ฐ์ฅ ๋ง์ด ํ๋ฆฐ ์ํ 5๊ฐ๋?'`์ผ๋ก ์คํํด `sql` ๋ณ์์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "chain = create_sql_query_chain(llm, db)\nsql = chain.invoke({'question': '๊ฐ์ฅ ๋ง์ด ํ๋ฆฐ ์ํ 5๊ฐ๋?'})", | |
| "grading_conditions": ["create_sql_query_chain", "llm", "db", "chain.invoke", "question"], | |
| "hint": "`create_sql_query_chain(llm, db)`๋ก ์ฒด์ธ ์์ฑ ํ, `{'question': '...'}` ๋์ ๋๋ฆฌ๋ก ์คํํฉ๋๋ค.", | |
| "explanation": "chain ๋ด๋ถ์ ์ผ๋ก DB์ ํ ์ด๋ธยท์ปฌ๋ผ ์ ๋ณด๋ฅผ ํ๋กฌํํธ์ ํฌํจํด LLM์ด ์ฌ๋ฐ๋ฅธ ์คํค๋ง๋ง ์ฐธ์กฐํ๋๋ก ์ ๋ํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "create_sql_query_chain์ SQLDatabase์์ ์คํค๋ง๋ฅผ ์๋ ์ฃผ์ ํด LLM์ด ์ฌ๋ฐ๋ฅธ SQL์ ์์ฑํ๋๋ก ํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-sql-3", | |
| "title": "[Text-to-SQL 3] ValidSQL Guard โ ์๋ชป๋ SQL ์คํ ์ ์ฐจ๋จ", | |
| "task_description": "**์ํฉ**\nLLM์ด ์์ฑํ SQL์ ๋ฐ๋ก `db.run()`์ ๋๊ฒผ๋๋:\n```\nOperationalError: no such column: product_name\n```\n๋ฌธ๋ฒ ์ค๋ฅ๊ฐ ์๋ SQL์ด ์ค์ DB์ ์คํ๋์ต๋๋ค.\n\n**๋ฌธ์ **\nLLM์ ๊ฐ๋ ๋ฌธ๋ฒ ์ค๋ฅ๊ฐ ์๋ SQL์ ์์ฑํฉ๋๋ค. ์คํ ์ ์ ๊ฑธ๋ฌ์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nGuardrails์ `ValidSQL`๋ก SQL ์ ํจ์ฑ ๊ฒ์ฆ Guard๋ฅผ ๋ง๋์ธ์.\n- ์คํจ ์: `'reask'` (์ฌ๋ฐ๋ฅธ SQL์ ๋ค์ ์์ฒญ)", | |
| "expected_command": "guard = Guard().use(ValidSQL(on_fail='reask'))", | |
| "grading_conditions": ["Guard()", "ValidSQL", "on_fail='reask'"], | |
| "hint": "`Guard().use(ValidSQL(on_fail='...'))` ํจํด์ ๋๋ค.", | |
| "explanation": "`on_fail='reask'`๋ SQL ๊ฒ์ฆ ์คํจ ์ LLM์๊ฒ ์๋์ผ๋ก ์ฌ์์ฑ์ ์์ฒญํฉ๋๋ค. `exception`๊ณผ ๋ฌ๋ฆฌ ์ฌ์ฉ์๋ ์ค๋ฅ๋ฅผ ๋ณด์ง ๋ชปํ๊ณ ์๋์ผ๋ก ์์ ๋ฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "RAG ์ฑ๋ด์์ ๋ฐฐ์ด Guard().use() ํจํด์ด SQL ๊ฒ์ฆ์๋ ๊ทธ๋๋ก ์ ์ฉ๋ฉ๋๋ค. on_fail='reask'๋ก ์ฌ์ฉ์ ๋ ธ์ถ ์์ด ์๋ ์์ ํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-sql-4", | |
| "title": "[Text-to-SQL 4] FastAPI โ ํ ์ ์ฒด๊ฐ ์์ฐ์ด๋ก DB ์กฐํ", | |
| "task_description": "**์ํฉ**\n์คํฌ๋ฆฝํธ๋ก ๋๋ฆฌ๋ฉด ๋๋ง ์ธ ์ ์์ต๋๋ค. ํ์๋ค๋ ์์ฐ์ด๋ก DB๋ฅผ ์กฐํํ ์ ์๊ฒ API๋ก ๋ ธ์ถํ๊ณ ์ถ์ต๋๋ค.\n\n**๋ฌธ์ **\n์คํฌ๋ฆฝํธ๋ ๊ณต์ ๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค. API ์๋ํฌ์ธํธ๋ก ๋ ธ์ถํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\n์๋ `???`๋ฅผ ์ฑ์ POST `/sql` ์๋ํฌ์ธํธ๋ฅผ ์์ฑํ์ธ์.\n\n```python\n@app.post('/sql')\nasync def generate_sql(q: Query):\n sql = ???\n result = db.run(sql)\n return {'sql': sql, 'result': result}\n```", | |
| "expected_command": "sql = chain.invoke({'question': q.question})", | |
| "grading_conditions": ["chain.invoke", "question", "q.question"], | |
| "hint": "Pydantic ๋ชจ๋ธ `q`์ `.question` ์์ฑ์ผ๋ก ์์ฐ์ด ์ง๋ฌธ์ ๊ฐ์ ธ์ต๋๋ค.", | |
| "explanation": "`q.question`์ผ๋ก ์์ฐ์ด ์ง๋ฌธ์ ๋ฐ์ ์ฒด์ธ์ ๋๊น๋๋ค. `db.run(sql)`๋ก SQL ์คํ ๊ฒฐ๊ณผ๋ ํจ๊ป ๋ฐํํด ํ๋ก ํธ์๋์์ ๋ฐ๋ก ํ ํํ๋ก ๋ณด์ฌ์ค ์ ์์ต๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "RAG ์ฑ๋ด FastAPI ํจํด๊ณผ ๋์ผํฉ๋๋ค. q.question์ผ๋ก ์ง๋ฌธ์ ๋ฐ์ chain.invoke()์ ๋๊ธฐ๋ ๊ตฌ์กฐ๊ฐ ์ฌ์ฌ์ฉ๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-brain-1", | |
| "title": "[Second Brain 1] DirectoryLoader โ ๋ ธํธ 300๊ฐ๋ฅผ ํ ๋ฒ์", | |
| "task_description": "**ํ๋ก์ ํธ: ๊ฐ์ธ Second Brain**\n๋ด Obsidian ๋ ธํธ๊ฐ 300๊ฐ๊ฐ ๋์ต๋๋ค. \"3๊ฐ์ ์ ์ ๊ณต๋ถํ๋ ๊ทธ ๋ด์ฉ์ด ์ด๋ ํ์ผ์ด์๋๋ผ?\"๋ฅผ ์ฐพ์ผ๋ ค๋ฉด ํ์ผ์ ํ๋์ฉ ์ด์ด๋ด์ผ ํฉ๋๋ค.\n\n---\n\n**๋จ๊ณ 1**: 300๊ฐ ํ์ผ์ ํ๋์ฉ ๋ณต์ฌํด RAG์ ๋ฃ์ ์ ์์ต๋๋ค.\n`DirectoryLoader`๋ก `./notes` ํด๋ ์์ ๋ชจ๋ `.md` ํ์ผ(ํ์ ํด๋ ํฌํจ)์ ํ ๋ฒ์ ๋ก๋ํ์ธ์.\n\n๊ฒฐ๊ณผ๋ฅผ `docs` ๋ณ์์ ์ ์ฅํ์ธ์.", | |
| "expected_command": "loader = DirectoryLoader('./notes', glob='**/*.md')\ndocs = loader.load()", | |
| "grading_conditions": ["DirectoryLoader", "./notes", "glob='**/*.md'", "loader.load()"], | |
| "hint": "`DirectoryLoader('ํด๋', glob='ํจํด')`์ผ๋ก ๋ก๋๋ฅผ ๋ง๋ค๊ณ `.load()`๋ก ์คํํฉ๋๋ค.", | |
| "explanation": "`**/*.md`๋ `./notes`์ ๋ชจ๋ ํ์ ํด๋์์ `.md` ํ์ผ์ ์ฌ๊ท ๊ฒ์ํฉ๋๋ค. 300๊ฐ ํ์ผ์ด ํ ๋ฒ์ `loader.load()` ํธ์ถ๋ก ๋ชจ๋ `Document` ๊ฐ์ฒด๋ก ๋ณํ๋ฉ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "RAG ์ฑ๋ด์์ ๋ฐฐ์ด DirectoryLoader + glob ํจํด์ด Second Brain ๋ ธํธ ๋ก๋์ ๊ทธ๋๋ก ์ฌ์ฌ์ฉ๋ฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-brain-2", | |
| "title": "[Second Brain 2] metadata['source'] โ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์ด๋ ํ์ผ์ธ์ง", | |
| "task_description": "**์ํฉ**\nRAG ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ๋์๋๋ฐ \"์ด๊ฒ ์ด๋ ๋ ธํธ์์ ์์ง?\" ์๋ณธ ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค. ๋ด์ฉ๋ง ๋ณด์ด๊ณ ์ถ์ฒ๊ฐ ์์ผ๋ ์๋ณธ์ ์ด์ด ๋งฅ๋ฝ์ ๋ ๋ณผ ์๊ฐ ์์ต๋๋ค.\n\n**๋ฌธ์ **\nDocument ๊ฐ์ฒด์ ์ถ์ฒ ์ ๋ณด๊ฐ ์๋๋ฐ ์ฌ์ฉํ์ง ์๊ณ ์์ต๋๋ค.\n\n**ํด๊ฒฐ**\n`docs` ๋ฆฌ์คํธ๋ฅผ ์ํํ๋ฉฐ ๊ฐ ๋ฌธ์์ ํ์ผ ๊ฒฝ๋ก(์ถ์ฒ)์ ๋ด์ฉ ๊ธธ์ด๋ฅผ ์ถ๋ ฅํ๋ ์ฝ๋๋ฅผ ์์ฑํ์ธ์.", | |
| "expected_command": "for doc in docs:\n print(doc.metadata['source'], len(doc.page_content))", | |
| "grading_conditions": ["doc.metadata['source']", "len(doc.page_content)"], | |
| "hint": "ํ์ผ ๊ฒฝ๋ก๋ `doc.metadata['source']`, ๋ด์ฉ์ `doc.page_content`์ ์์ต๋๋ค.", | |
| "explanation": "`doc.metadata['source']`์ ํ์ผ ๊ฒฝ๋ก๊ฐ ์ ์ฅ๋ฉ๋๋ค. ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค ๋ ์ด ๊ฐ์ ํจ๊ป ํ์ํ๋ฉด ์ฌ์ฉ์๊ฐ ์๋ณธ ํ์ผ๋ก ์ด๋ํ ์ ์์ต๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "doc.metadata['source']์ ์๋ณธ ํ์ผ ๊ฒฝ๋ก๊ฐ ์ ์ฅ๋ฉ๋๋ค. RAG ๊ฒ์ ๊ฒฐ๊ณผ์ ์ถ์ฒ๋ฅผ ํ์ํ ๋ ํญ์ ์ด ๊ฐ์ ํ์ฉํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-brain-3", | |
| "title": "[Second Brain 3] FileSensor โ ์ ๋ ธํธ ์ถ๊ฐ ์ ์๋ ์ฌ์ธ๋ฑ์ฑ", | |
| "task_description": "**์ํฉ**\n์ ๋ ธํธ๋ฅผ ์ถ๊ฐํ๋๋ฐ ๊ฒ์์ด ์ ๋ฉ๋๋ค. `python reindex.py`๋ฅผ ๋งค๋ฒ ์๋์ผ๋ก ์คํํด์ผ ํฉ๋๋ค. ๋ ธํธ๋ฅผ ์ถ๊ฐํ๊ณ ์ฌ์ธ๋ฑ์ฑ์ ์์ด๋ฒ๋ฆฌ๋ ๋ ์ด ์๊น๋๋ค.\n\n**๋ฌธ์ **\n์ ํ์ผ ์ถ๊ฐ๋ฅผ ๊ฐ์งํด ์๋์ผ๋ก ์ฌ์ธ๋ฑ์ฑ์ ํธ๋ฆฌ๊ฑฐํด์ผ ํฉ๋๋ค.\n\n**ํด๊ฒฐ**\nAirflow `FileSensor`๋ก `./notes` ํด๋์ ์ `.md` ํ์ผ์ด ์๊ธฐ๋ฉด ๊ฐ์งํ๋ ํ์คํฌ๋ฅผ ๋ง๋์ธ์.\n- task_id: `'wait_for_new_note'`\n- filepath: `'./notes/*.md'`\n- poke_interval: `300` (5๋ถ๋ง๋ค ํ์ธ)", | |
| "expected_command": "wait_for_new_note = FileSensor(\n task_id='wait_for_new_note',\n filepath='./notes/*.md',\n poke_interval=300\n)", | |
| "grading_conditions": ["FileSensor", "task_id='wait_for_new_note'", "filepath", "poke_interval=300"], | |
| "hint": "`FileSensor(task_id='...', filepath='...', poke_interval=์ด)` ํจํด์ ๋๋ค.", | |
| "explanation": "`FileSensor`๋ ์ง์ ํ ํ์ผ์ด ์กด์ฌํ ๋๊น์ง ์ฃผ๊ธฐ์ ์ผ๋ก ํ์ธํฉ๋๋ค. ํ์ผ์ด ๊ฐ์ง๋๋ฉด ๋ค์ ํ์คํฌ(์ฌ์ธ๋ฑ์ฑ)๊ฐ ์๋ ์คํ๋ฉ๋๋ค. `poke_interval=300`์ 5๋ถ๋ง๋ค ํ์ธํฉ๋๋ค.", | |
| "difficulty": "medium", | |
| "takeaway": "FileSensor๋ ํ์ผ ์ถ๊ฐ๋ฅผ ๊ฐ์งํด ํ์ Airflow ํ์คํฌ๋ฅผ ํธ๋ฆฌ๊ฑฐํฉ๋๋ค. @daily์ ๋ฌ๋ฆฌ ์ด๋ฒคํธ ๊ธฐ๋ฐ์ผ๋ก ๋์ํฉ๋๋ค." | |
| }, | |
| { | |
| "id": "docs-brain-4", | |
| "title": "[Second Brain 4] Streamlit โ ๋ ธํธ ๊ฒ์ UI", | |
| "task_description": "**์ํฉ**\n์ง๊ธ์ ํฐ๋ฏธ๋์์ `retriever.invoke('๊ฒ์์ด')`๋ฅผ ์ง์ ์ ๋ ฅํด์ผ ํฉ๋๋ค. ํ์๋ค์ด ์ธ ์ ์์ต๋๋ค.\n\n**๋ฌธ์ **\nCLI๋ ๊ฐ๋ฐ์๋ง ์ธ ์ ์์ต๋๋ค.\n\n**ํด๊ฒฐ**\nStreamlit์ผ๋ก ๊ฒ์ UI๋ฅผ ๋ง๋์ธ์.\n- ํด๋ ๊ฒฝ๋ก ํ ์คํธ ์ ๋ ฅ (`folder`, ๊ธฐ๋ณธ๊ฐ `'./notes'`)\n- ๊ฒ์์ด ํ ์คํธ ์ ๋ ฅ (`query`)\n- '๊ฒ์' ๋ฒํผ ํด๋ฆญ ์ `retriever.invoke(query)` ์คํ โ `results`์ ์ ์ฅ", | |
| "expected_command": "folder = st.text_input('๋ ธํธ ํด๋ ๊ฒฝ๋ก', value='./notes')\nquery = st.text_input('๊ฒ์ํ ๋ด์ฉ')\nif st.button('๊ฒ์'):\n results = retriever.invoke(query)", | |
| "grading_conditions": ["st.text_input", "st.button('๊ฒ์')", "retriever.invoke(query)"], | |
| "hint": "`st.text_input`, `st.button`, `retriever.invoke` ์ธ ๊ฐ์ง๋ฅผ ์์๋๋ก ์๋๋ค.", | |
| "explanation": "`if st.button('๊ฒ์'):` ๋ธ๋ก ์์ ๋ฌด๊ฑฐ์ด ์์ ์ ๋ฃ์ผ๋ฉด ๋ฒํผ์ ๋๋ ์ ๋๋ง ์คํ๋ฉ๋๋ค. ํด๋ ๊ฒฝ๋ก๊ฐ ๋ฐ๋๋ฉด `st.cache_resource`๋ฅผ clearํด retriever๋ฅผ ์ฌ์ด๊ธฐํํ๋ ๋ก์ง์ ์ถ๊ฐํ๋ฉด ์์ฑ์ ๋๋ค.", | |
| "difficulty": "easy", | |
| "takeaway": "st.text_input + st.button + retriever.invoke ์กฐํฉ์ RAG ์ฑ๋ด์์ ๋ฐฐ์ด Streamlit ํจํด์ด Second Brain UI์ ๊ทธ๋๋ก ์ ์ฉ๋ ์์ ๋๋ค." | |
| } | |
| ] | |