성능 좋은 AI 언어 모델은 이미 많습니다. 그러나 대부분은 상용 서비스로 비용을 지불하고 사용해야 합니다. 개인 개발자가 API 사용료를 지불하는 것이 크게 부담스러운 금액은 아닐 수 있지만, 심리적 장벽은 분명히 존재합니다. 카드를 등록하는 순간 “실수로 API를 과도하게 호출해 통장 잔고가 바닥나면 어쩌지?”라는 걱정이 따라옵니다. AWS를 사용해 본 개발자라면 누구나 공감할 겁니다. 사용량 기반 비용 모델은 개발자의 작은 실수 하나가 예상치 못한 비용 폭탄으로 돌아올 수 있으니까요. 예를 들어, GPT-4o는 10줄 정도의 입력값에 평균 30~50원의 비용이 발생합니다. 하지만 실제 서비스에서는 더 많은 입력값이 필요하고, 검색 결과까지 LLM에 전달하게 되면 비용은 눈덩이처럼 불어납니다. Claude 3.7 Sonnet, Anthropic의 o1, o1-pro는 더욱 비쌉니다. o1은 GPT-4o에 비해 10배, o1-pro는 o1보다 150배나 비싼 모델입니다. 이처럼 상용 LLM은 개인 개발자나 소규모 프로젝트에 부담스러운 가격대를 형성하고 있습니다. 이러한 이유로 오픈소스로 로컬에서 구동할 수 있는 LLM에 대한 수요가 생겨났습니다. 이는 기업뿐만 아니라 개인 개발자의 입장에서도 그렇습니다. 그런데 한국어를 지원하면서 대답을 잘하는 오픈소스 모델을 찾기는 쉽지 않습니다. 일반적으로 한국어처럼 영어 외 언어를 잘 지원하는 LLM을 원한다면, 일반적으로 더 큰 모델이 필요했습니다. 지금까지 오픈소스로 공개된 모델들 중 일부는 수천억 개의 파라미터(600~700B)를 가지고 있지만, 이러한 대형 모델들은 ‘오픈소스’라는 이름이 무색하게 일반 개발자의 컴퓨터로는 도저히 구동할 수 없는 수준의 하드웨어를 요구합니다. 적은 수의 파라미터 모델들은 한국어 답변에 어려움을 겪었고, 높은 수준의 응답을 생성하려면 더 많은 파라미터 모델이 필요했죠. 개인 개발자가 로컬에서 활용하기에는 큰 장벽이었습니다. 14B의 작은 몸집, 200B의 실력: 딥시크의 등장다행히 최근에는 파라미터 수가 줄어도 준수한 성능을 보이는 LLM 모델이 오픈소스로 공개되고 있습니다. 예를 들어, 마이크로소프트의 Phi-4 모델은 맥 미니에서도 구동되면서 한국어 답변이 가능합니다. 이러한 흐름 속에서 최근 큰 파장을 일으킨 딥시크(DeepSeek)가 AI 커뮤니티의 주목을 받았죠. 딥시크는 671B 수준의 대형 모델도 있지만, 주목할 만한 것은 deepseek-r1 14b 크기의 모델입니다. 이 모델은 일반적인 게이밍 컴퓨터(RTX 3070급 그래픽카드)에서도 충분히 구동됩니다. 양자화된 버전을 사용하면 맥북에서도 무리 없이 작동하죠. deepseek-r1의 가장 큰 특징은 저사양 환경에서도 구동 가능한 모델을 오픈소스로 제공했다는 점과, 파라미터 수를 고려했을 때 놀라운 성능을 보여준다는 점입니다. 특히 Claude의 o1과 같은 ‘추론 모델’이라는 특성은 딥시크가 단순한 크기 축소가 아닌 질적 혁신을 이룸을 보여줍니다. 이 모델은 자신의 출력 토큰을 다시 입력 토큰으로 주입하는 일종의 되새김질을 통해, Chain of Thought(COT)를 구현했습니다. 추론 모델은 일반적인 트랜스포머 모델보다 응답 속도는 다소 느리지만, 특히 논리적 문제 해결에서 압도적으로 높은 성능을 보여주고 있습니다. 미래의 모든 웹페이지는 챗봇으로 대체될지도 모릅니다많은 분들이 LLM 하면 ‘챗봇’을 떠올릴 겁니다. 저는 웹이 새로운 인터페이스의 시대로 진입하고 있다고 믿습니다. 기존의 웹은 복잡한 버튼, 메뉴, 탐색 경로로 가득 찼습니다. 사용자는 원하는 정보를 찾기 위해 여러 페이지를 건너뛰고, 복잡한 UI를 이해해야 했습니다. 하지만 챗봇 인터페이스는 이 모든 복잡성을 단순한 대화로 바꿉니다. “내일 서울 날씨 어때?”라고 물으면 복잡한 웹사이트 탐색 없이 바로 답을 얻을 수 있는 세상이 오고 있습니다. 이는 UI의 혁명적 단순화를 의미하며, 미래에는 많은 웹사이트가 챗봇 인터페이스로 재탄생할 수 있습니다. 나만의 AI 비서를 상상해 보세요. 내 문서를 이해하고, 내 생각을 정리하며, 프로젝트 계획까지 도와주는 개인 맞춤형 AI가 곁에 있다면 얼마나 편리할까요? 미래의 개인 생산성 도구는 단순한 비서가 아닌 ‘생각의 확장’입니다. 회의 내용을 자동으로 요약하고 복잡한 정보를 체계화하며, 내 사고방식과 작업 패턴을 학습해 더 나은 결과물을 만들어내는 조력자가 되어 줄 겁니다. 이번 글에서는 프론트엔드 개발자로서 딥시크를 활용한 실제 경험과 챗봇을 구현해 본 인사이트를 공유하려고 합니다. 딥시크 모델을 실제로 사용하는 과정에서 마주친 문제들, 효과적인 챗봇을 설계하기 위한 고민들, 그리고 스트리밍 UI를 구현하면서 배운 교훈을 담았습니다. 딥시크에 대한 기대와 겪었던 한계, 그리고 LLM을 사용하기 위해서 어떤 구현이 필요한지, 이 글을 읽고 도움이 되면 좋겠습니다. 게이밍 컴퓨터에서 딥시크 사용하기회사에서는 이미 딥시크 모델을 제공하고 있습니다. ‘모델스토어’를 이용하면 오픈라우터처럼 사용자가 여러 모델을 쉽게 전환하면서 사용할 수 있는데요. 여기서 회사 서버를 킬 때 제 로컬에서 딥시크를 호스팅하게 해봤습니다. 회사 코드의 챗봇 코드와 LLM 호출 부분을 그대로 이용하되, 호스팅 주체만 제 컴퓨터로 바꿨을 뿐이죠. 일반적인 LLM 사용 구조와 로컬 호스팅 구조의 차이를 간단히 도식화하면 다음과 같습니다. <출처: 작가> 여기서 Ollama라는 도구를 사용하면 로컬 환경에서 LLM을 쉽게 구동할 수 있습니다. Mac 사용자라면 터미널에서 간단한 명령어로 설치가 가능합니다. bash # Ollama 설치 (Mac 기준) curl -fsSL https://ollama.com/install.sh | sh # 딥시크 모델 다운로드 (R1 14B 버전) ollama pull deepseek-r1:14b # 모델 실행 ollama run deepseek-r1:14b Windows 사용자라면 Ollama 공식 웹사이트에서 설치 프로그램을 다운로드하여 실행한 후, 명령 프롬프트나 PowerShell에서 동일한 명령으로 모델을 다운로드할 수 있습니다. 경험적으로 14B 정도는 되어야 한국어 응답을 나쁘지 않게 하더라고요. 1.5B나 7B 모델은 한국어 성능이 크게 떨어져 실제 사용이 어려웠습니다. 1.5B 모델은 거의 사용하기 어려운 수준이라, 챗봇이라기보다는 전처리 느낌으로 사용하는 용도에 가까웠습니다. 그리고 14B 모델도 한국어 수준이 완벽하진 않아, 시스템 프롬프트 같은 경우는 영어로 작성하는 것이 훨씬 효과적이었습니다. RTX 3070 그래픽카드가 있는 제 데스크에서 테스트했는데, 14B 모델을 무리 없이 구동할 수 있었습니다. 이 모델은 약 9GB의 메모리를 사용했지만, 일반적인 게이밍 PC 환경에서도 충분히 작동했습니다. 딥시크는 1.5B부터 671B까지 다양한 크기의 모델이 공개되어 있는데, 하드웨어 상황에 맞게 선택할 수 있다는 점이 좋았습니다. LLM 모델들은 각기 다른 인터페이스를 가지고 있어서 호출할 때 통일하도록 어댑터 클래스를 만들어주는 것이 편리합니다. 다행히 OpenAI SDK를 사용하면 딥시크 모델도 쉽게 호출할 수 있습니다. const OpenAI = require("openai"); const openai = new OpenAI({ baseURL: "http://192.168.0.123:11434/v1", // 내 컴퓨터 IP와 Ollama 포트 apiKey: "ollama", // OpenAI SDK에서 필요하지만 Ollama는 검증하지 않음 }); async function chatWithDeepSeek(prompt) { try { const response = await openai.chat.completions.create({ model: "deepseek-r1:14b", // 실행 중인 모델명 messages: [{ role: "user", content: prompt }], }); console.log(response.choices[0].message.content); } catch (error) { console.error("Error:", error.message); } } 실제 서비스에서는 다양한 LLM 모델을 쉽게 전환할 수 있도록 추상화 레이어를 구현하는 것이 좋습니다. 이렇게 하면 새로운 모델이 출시되거나 기존 모델을 변경할 때, 코드 분기를 추가하지 않고도 유연하게 대응할 수 있습니다. // 회사 코드 예시 // 모델 설정을 추상화하는 객체 const LLMModels = { // {...다른 모델...} "deepseek-r1": { model: "deepseek-r1:14b", supportsTools: true, createClient: createDeepSeek, getStream: Stream.OpenAI, }, "deepseek-v3": { model: "deepseek-v3", supportsTools: true, createClient: createDeepSeek, getStream: Stream.OpenAI, }, } // 딥시크 클라이언트 생성 함수 export const createDeepSeek = () => new OpenAI({ apiKey: "ollama", baseURL: "http://192.168.0.123:11434/v1", // 내 컴퓨터 IP }); 이러한 추상화 패턴을 사용하면, 모델이 추가돼도 코드 분기 부분이 추가되지 않아 유지보수가 쉬워집니다. 경험상 딥시크의 펑션 콜은 체감적으로 잘 호출되지 않았습니다. 간단한 함수 호출은 가능했지만, 복잡한 구조의 펑션 콜에서는 불안정한 모습을 보였습니다. 이 부분은 상용 API 모델들에 비해 아직 개선이 필요해 보입니다. 그럼에도 딥시크 R1 14B 모델은 크기 대비 놀라운 성능을 보여주었고, 특히 논리적 추론 능력이 뛰어났습니다. 비용 걱정 없이 마음껏 실험할 수 있다는 점에서, 개인 개발자에게 딥시크 모델은 매력적인 선택지가 될 수 있습니다. LLM은 아무것도 기억하지 않는다LLM으로 챗봇을 개발할 때 가장 먼저 마주치는 오해는 “LLM이 자체적으로 대화를 기억한다”라는 것입니다. ChatGPT나 Claude와 같은 서비스를 사용해 본 경험이 있다면, 마치 AI가 우리의 대화를 모두 기억하는 것처럼 느껴집니다. 하지만 실제로 챗봇을 직접 개발해 보면 놀라운 사실을 발견하게 됩니다. LLM은 아무것도 기억하지 않으며, 대화 기억 기능은 직접 구현해야 합니다. 대부분의 LLM 서비스는 소켓 연결이 아닌 HTTP 통신 형태로 제공됩니다. 이는 마치 REST API처럼 작동하는 구조입니다. 입력(input)을 보내면 출력(output)이 돌아오는 단방향 통신 방식이죠. 단, 일반적인 REST API와 달리 같은 입력이라도 항상 동일한 출력이 나오지는 않습니다. [대화 세션의 실제 작동 방식] 사용자 → 메시지 전송 → LLM 서버 → 응답 생성 → 사용자 (연결 종료) (새로운 요청) 사용자 → 새 메시지 전송 → LLM 서버 → 응답 생성 → 사용자 (연결 종료) 그렇다면 우리가 ChatGPT나 Claude와 대화할 때 이전 내용을 기억하는 것처럼 보이는 이유는 무엇일까요? 바로 ‘대화 기록(conversation history)’을 매번 함께 전송하기 때문입니다. 개발자가 이전 대화 내용을 저장해두었다가, 새로운 메시지와 함께 모든 대화 기록을 다시 보내는 것이죠. [챗봇이 맥락을 이해하는 방식] 1차 대화: 사용자 → "안녕?" → LLM → "안녕하세요!" → 사용자 (대화 기록 저장) 2차 대화: 사용자 → "이름이 뭐야?" → [백엔드에서 처리: "안녕?" + "안녕하세요!" + "이름이 뭐야?"] → LLM → "저는 AI 어시스턴트입니다" → 사용자 (대화 기록 추가 저장) 이러한 방식은 LLM이 마치 우리와의 대화를 기억하는 것처럼 보이게 만들지만, 실제로는 매번 처음부터 대화를 다시 읽고 맥락을 이해하는 것입니다. 즉, 챗봇 개발자가 대화 기록을 관리하는 코드를 직접 작성해야만 사용자에게 연속적인 대화 경험을 제공할 수 있습니다. 대화 맥락 관리하기: 슬라이딩 윈도우 기법대화가 길어지면 문제가 발생합니다. LLM에는 한 번에 처리할 수 있는 ‘컨텍스트 창(context window)’이라는 입력 한계가 있기 때문입니다. 딥시크 R1 14B 모델은 대략 8K 토큰(약 6,000단어) 정도의 컨텍스트 창을 가지고 있습니다. 이를 초과하면 더 이상 새로운 정보를 넣을 수 없고, 에러가 발생하거나 응답 품질이 크게 저하됩니다. 이 문제를 해결하기 위해 가장 기본적인 방법으로 ‘슬라이딩 윈도우(sliding window)’ 기법을 사용합니다. 이는 가장 최근의 대화만을 포함시키는 방식입니다. [슬라이딩 윈도우 적용 예시] 컨텍스트 창 크기가 10개 메시지라고 가정 대화 1~12번까지 진행된 상태에서 새 메시지: → 메시지 4~12 + 새 메시지(13)만 LLM에 전송 → 메시지 1~3은 버려짐 물론 단순히 오래된 메시지를 버리는 것보다 더 똑똑한 방법도 있습니다. 최근 대화는 모두 포함하되, 사용자의 현재 질문과 관련성이 높은 과거 메시지만 선별적으로 포함하는 방식입니다. 이를 위해 각 메시지를 임베딩(벡터화)하여 저장해두고, 새 질문과 유사한 과거 메시지만 선택적으로 포함하는 것이죠. 더 진보된 방식으로는 GraphRAG나 Contexted RAG 같은 기술도 있습니다. 이는 마치 인간의 기억이 계층화되어 부호화되고 인출되는 방식을 모방한 것입니다. 대화에서 나온 생략된 주어나 목적어를 보강하여 저장하면, 나중에 필요할 때 더 정확하게 관련 정보를 불러올 수 있습니다. 실제 구현에서는 이러한 기법들을 조합해서 사용할 수도 있습니다. 예를 들어, 최신 대화 5개는 항상 포함하고, 그 외에 추가로 현재 질문과 관련된 과거 대화 3개를 선택적으로 포함하는 식으로요. 효과적인 시스템 프롬프트 작성하기시스템 프롬프트(system prompt)는 LLM에 주는 일종의 설정입니다. 많은 사람들이 이를 한 번 설정하면 영구적으로 적용되는 것으로 오해하지만, 실제로는 매번 요청할 때마다 함께 전송되는 특별한 메시지에 불과합니다. 앞서 설명한 대화 맥락의 연장선상에서, 시스템 프롬프트도 매번 전송되는 것입니다. [실제 LLM 요청 구조] { "model": "deepseek-r1:14b", "messages": [ {"role": "system", "content": "당신은 친절한 AI 어시스턴트입니다..."}, {"role": "user", "content": "안녕?"}, {"role": "assistant", "content": "안녕하세요!"}, {"role": "user", "content": "이름이 뭐야?"} ] } 시스템 프롬프트는 우리가 LLM에 맥락을 미리 제공하는 강력한 도구입니다. 인간의 대화는 굉장히 맥락 의존적입니다. 모든 말에 그 대화를 이해하기 위한 모든 정보가 담겨있지 않고, 암묵적인 규칙이나 상황에 의존하는 경우가 많죠. 시스템 프롬프트는 이런 암묵적인 규칙을 LLM에 미리 알려주는 역할을 합니다. 예를 들어, 다음과 같은 상황을 생각해 볼게요. [시스템 프롬프트 없이] 사용자: 이거 어때? LLM: 무엇을 말씀하시는지 더 자세히 설명해 주실 수 있을까요? 어떤 것에 대한 의견을 묻고 계신지 알려주시면 도움을 드리겠습니다. [시스템 프롬프트 있음] 시스템: 당신은 온라인 쇼핑몰의 상품 추천 AI입니다. 사용자는 지금 청바지를 보고 있으며, “이거 어때?”라고 물으면 해당 청바지에 대한 스타일 조언과 코디 팁을 제공해야 합니다. 사용자: 이거 어때? LLM: 이 청바지는 클래식한 스트레이트 핏으로 다양한 스타일에 잘 어울립니다. 캐주얼하게 흰색 티셔츠와 스니커즈를 매치하거나, 셔츠와 함께 세미캐주얼 룩으로 연출하기도 좋습니다. 신축성 있는 소재라 활동성도 좋은 편이에요. 이처럼 시스템 프롬프트는 사용자가 모든 것을 명시적으로 설명하지 않아도, LLM이 올바른 맥락에서 대화를 이해하도록 도와줍니다. 모델에 따라 시스템 프롬프트의 언어 선택도 중요합니다. 제 경험상 GPT-4o나 Claude 3.7 sonnet 같은 상용 모델은 한국어 시스템 프롬프트로도 충분히 좋은 성능을 보여, 개발 가독성을 위해 한글로 작성했습니다. 하지만 DeepSeek R1 14B와 같은 오픈소스 모델은 시스템 프롬프트를 영어로 작성했을 때와 한국어로 작성했을 때의 성능 차이가 있어, 영어로 작성하는 것이 더 효과적이었습니다. 이는 대부분의 오픈소스 모델이 영어 데이터로 더 많이 훈련된 것 때문으로 보입니다. 필요에 따라 시스템 프롬프트는 영어로 작성할 수도 있겠네요. 스트리밍 UI로 챗봇 사용성 높이기LLM 챗봇을 구현한다면 역시 스트리밍 UI는 빠질 수 없겠죠? LLM은 모델에 따라 다르지만, 응답을 스트리밍으로 내려줄 수 있습니다. 덕분에 사용자는 응답이 다 나올 때까지 빈 메시지창을 기다리는 게 아니라, 메시지가 나오는 대로 읽어 내려갈 수 있습니다. 이렇게 하면 사용자의 인내심을 덜 요구하게 되죠. 이 부분은 생각보다 중요합니다. 어차피 챗봇은 사람이 쓰는 것이기 때문에 결국 이러한 불편함을 최대한 줄이는 게 핵심입니다. 제가 사용한 DeepSeek R1도 스트리밍 모드를 지원하기 때문에 당연히 stream 옵션을 켰습니다. 스트리밍의 중요성개발자로서 처음 LLM API를 사용할 때 마주치는 중요한 옵션 중 하나가 바로 ‘stream’ 파라미터입니다. 이 옵션을 활성화하면 LLM이 전체 응답을 한 번에 반환하는 대신, 생성되는 대로 작은 ‘청크(chunk)’로 나누어 실시간으로 전송합니다. // OpenAI SDK를 사용한 딥시크 스트리밍 예시 const response = await openai.chat.completions.create({ model: "deepseek-r1:14b", messages: [{ role: "user", content: "한국의 대표적인 음식 3가지를 알려줘" }], stream: true // 스트리밍 활성화 }); // 스트리밍 응답 처리 for await (const chunk of response) { const content = chunk.choices[0]?.delta?.content || ""; // 화면에 content 표시 document.getElementById('response').textContent += content; } 스트리밍의 가치는 경험해 보면 확실히 알 수 있습니다. 3~5초가 걸리는 응답이라도 글자가 흘러나오는 것을 보면 사용자는 대기한다는 느낌보다, 대화 중이라는 느낌을 받습니다. 특히 딥시크와 같은 추론 모델에서는 사고 과정이 드러나는 것을 볼 수 있어, 더욱 몰입감 있는 경험을 제공합니다. 스트리밍 UI의 도전 과제하지만 스트리밍 구현에는 예상보다 까다로운 문제가 숨어 있습니다. API에서 전송되는 텍스트 청크는 균일한 크기나 간격으로 오지 않기 때문입니다. 불규칙한 청크 크기: 몇 글자부터 여러 단어까지 다양함불규칙한 도착 간격: 100ms에서 1,000ms까지 간격이 천차만별변동 요인: 모델 종류, 입력 토큰 양, 서버 상태 등에 따라 패턴이 달라짐 실제 프로젝트에서 경험한 청크 도착 패턴은 다음과 같았습니다. 시간(ms) |--100--|--100--|--800--|--50---|--400--| 청크크기 [ㅁㅁㅁ] [ㅁㅁ] [ㅁㅁㅁㅁ][ㅁ] [ㅁㅁㅁ] 실제응답 “안녕하” “세요!” “열심히” “코” “딩해요” 이런 불규칙한 패턴을 그대로 화면에 표시하면 텍스트가 뚝뚝 끊기면서 나타나고, 이는 전문적이지 못한 인상을 줍니다. 처음 시도했던 접근법들처음에는 “청크가 오는 대로 표시하거나, 아니면 일정 속도로 한 글자씩 표시하면 되지 않을까?”라고 단순하게 생각했습니다. 1. 청크 즉시 표시 방식서버에서 청크가 오는 대로 바로 화면에 표시하는 방식입니다. 시간(ms) |--100--|--100--|--800--|--50---|--400--| 청크도착 “안녕하” “세요!” “열심히” “코” “딩해요” 화면갱신 뚝! 뚝! 뚝! 뚝! 뚝! 체감효과 ....불규칙한 끊김.... ....답답한 기다림.... 결과는 기대 이하였습니다. 사용자 입장에서는 인터넷 연결이 불안정한 것처럼 느껴지고, 특히 긴 빈 공백 시간에는 응답이 멈췄나?하는 의문까지 들게 했습니다. 2. 글자 단위 일정 속도 출력 방식두 번째 시도는 requestAnimationFrame을 사용해 일정한 속도로 한 글자씩 표시하는 방식이었습니다. 시간(ms) |--16--|--16--|--16--|--16--|--16--|--16--| 실제응답 “안녕하세요!”(이미 서버에서 받음) 화면갱신 “안” “녕” “하” “세” “요” “!” 프레임 RAF RAF RAF RAF RAF RAF 체감효과 ...서버 응답은 왔는데 화면에는 왜 이렇게 천천히... 이번에는 반대로 너무 느린 문제가 있었습니다. 서버에서 응답은 이미 다 받았는데도, 브라우저의 프레임 간격(약 16.67ms)에 맞춰 한 글자씩 출력하다 보니 불필요한 지연이 발생했습니다. 최적의 솔루션: 동적 스케줄링 시스템이러한 시행착오 끝에 개발한 것이 ‘동적 스케줄링 시스템’입니다. 이 시스템은 청크 도착 패턴을 실시간으로 분석하고, 상황에 맞게 최적의 출력 전략을 선택합니다. 기본 원칙은 다음과 같습니다. 1. 기본적으로 부드러움 우선: 가능한 한 글자씩 표시하여 자연스러운 흐름 유지2. 제한적 배치 처리: 시간이 부족할 것으로 예상될 때만 소수의 글자를 배치 처리3. 패턴 적응: 도착 패턴이 변하면 전략도 자동으로 조정 시스템의 핵심은 두 가지 컴포넌트로 구성됩니다. 패턴 분석기 (StreamPatternAnalyzer)청크 도착 패턴을 분석하고 다음 청크가 언제 도착할지 예측합니다. class StreamPatternAnalyzer { // 최근 8개 청크만 기록해 현재 패턴에 집중 private chunks: Array<{ timestamp: number; queueSize: number }> = []; private readonly MAX_HISTORY = 8; // 패턴 분석 핵심 로직 public analyzePattern(): StreamPattern { // 충분한 데이터가 없으면 보수적 초기값 반환 if (this.chunks.length < 3) { return { avgInterval: 350, avgChunkSize: 4, confidence: 0 }; } // 청크 간격의 평균과 변동성 계산 const intervals = this.chunks .slice(1) .map((chunk, i) => chunk.timestamp - this.chunks[i].timestamp); const avgInterval = intervals.reduce((a, b) => a + b) / intervals.length; // 패턴의 안정성(신뢰도) 계산 const variance = intervals.reduce( (sum, interval) => sum + Math.pow(interval - avgInterval, 2), 0 ) / intervals.length; const stdDev = Math.sqrt(variance); const confidence = Math.min(1.0, (this.chunks.length / 5) * (1 - stdDev / avgInterval) ); return { // 보수적으로 25% 여유 시간 추가 avgInterval: avgInterval * 1.25, avgChunkSize: Math.ceil( this.chunks.reduce((sum, chunk) => sum + chunk.queueSize, 0) / this.chunks.length ), confidence: confidence }; } } 스케줄러 (StreamScheduler)분석된 패턴을 바탕으로 최적의 출력 전략을 결정합니다. private analyzeTimeConstraints(pattern: StreamPattern): { needsBatch: boolean; batchSize: number; } { const queueSize = this.updateQueue.length; // 패턴이 불확실하면 보수적 접근 if (pattern.confidence < 0.8) { return { needsBatch: queueSize > 8, batchSize: 2 }; } // 다음 청크 도착 전까지 처리 가능한지 계산 const expectedNextChunkTime = pattern.lastChunkTime + pattern.avgInterval; const timeUntilNextChunk = expectedNextChunkTime - performance.now(); // 현재 큐 처리에 필요한 예상 시간 const estimatedProcessingTime = queueSize * (StreamScheduler.getDisplayInterval() * 1.05); // 시간이 빠듯할 때만 배치 처리 고려 if ( estimatedProcessingTime > timeUntilNextChunk * 0.3 && timeUntilNextChunk < pattern.avgInterval * 0.45 && queueSize > Math.max(2, pattern.avgChunkSize) ) { // 큐의 일부만 배치 처리 (최대 5개) const portionToBatch = Math.ceil(queueSize * 0.35); return { needsBatch: true, batchSize: Math.max(2, Math.min(portionToBatch, this.MAX_BATCH_SIZE)) }; } // 기본: 한 글자씩 부드럽게 처리 return { needsBatch: false, batchSize: 1 }; } 이 알고리즘은 실제 프로덕션 환경에서 검증된 코드로, 현재 회사의 챗봇 서비스에 적용되어 있습니다. 전체 코드는 여기에서 확인하실 수 있습니다. 필요한 분들은 자유롭게 활용하길 바랍니다. 이 최적화가 왜 필요할까?스트리밍 UI는 사실 작은 디테일이지만, 사용자의 전체 경험을 크게 바꾸는 요소입니다. 모델이 아무리 똑똑해도 답답한 인터페이스면 사용자는 금방 떠나갑니다. 챗봇 만들 때 이 부분을 꼭 신경 쓰시길 추천합니다. 첫 번째는 그대로 출력, 두 번째는 한 글자마다 렌더링, 세 번째는 동적 스케줄링 <출처: 작가> 나만의 로컬 챗봇, 한번 만들어보세요지금까지 로컬 컴퓨터에서 딥시크 모델을 활용한 챗봇 만들기에 대해 살펴봤습니다. 개인 개발자에게 가장 큰 장벽이었던 비용과 하드웨어 문제가 오픈소스 LLM의 발전으로 많이 해소되었습니다. 특히 딥시크 R1 14B 모델은 일반 게이밍 PC에서도 충분히 구동되며, 한국어 응답도 나쁘지 않은 성능을 보여줍니다. 오픈소스 LLM은 지금도 계속 발전하고 있습니다. 14B 크기의 모델이 이 정도 성능을 보여준다면, 앞으로는 더 작은 모델도 충분히 활용 가능한 수준에 도달할 것입니다. 특히 한국어와 같은 비영어권 언어 지원도 점점 개선되고 있어, 국내 개발자들에게는 좋은 소식입니다. 여러분도 이제 로컬 컴퓨터에서 챗봇을 구현해 보세요. Ollama나 LLM Studio 같은 도구를 활용하면 복잡한 설정 없이도 쉽게 시작할 수 있습니다. 비용 부담 없이 다양한 실험을 해볼 수 있고, 자신만의 개인 비서를 만들어 생산성을 높이는 경험도 해볼 수 있을 겁니다. ©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.