Ollama의 최신 업데이트로 스트리밍과 툴 콜링이 동시에 가능해졌습니다. 이제 macOS에서 완전히 로컬 환경으로 실시간 대화형 AI 에이전트를 구축할 수 있게 되었습니다. 이번 글에서는 새로운 기능들을 살펴보고, 실제 개발 환경을 구축하는 방법을 단계별로 안내하겠습니다.

1. 기능 업데이트 한눈에 보기

릴리스 일시

2025-05-28

주요 변화

  1. 스트리밍 + 툴 콜링: 오프닝 토큰부터 실시간으로 화면에 내보내면서도, 중간에 등장하는 JSON 툴 콜을 즉시 분리해 처리합니다. 더 이상 “응답 끝까지 대기 → JSON 파싱 → 함수 실행” 같은 지연이 없습니다. (Ollama Blog)

  2. 증분 파서(incremental parser): 모델이 툴 전용 토큰을 갖고 있든 없든, 부분 접두사·순수 JSON 등 여러 패턴을 인식해 실행 가능한 단일 툴 콜로 재구성합니다. 덕분에 스트리밍과 툴 콜이 둘 다 깨지지 않습니다. (Apidog Guide)

  3. 지원 모델: Qwen 3, Devstral, Qwen 2.5 (및 coder), Llama 3.1, Llama 4 등—툴 카테고리로 배포되는 모델은 즉시 사용 가능합니다.

  4. 대용량 컨텍스트 num_ctx: 32k까지 실험 지원—대신 메모리 사용량이 비례해 증가합니다.


2. macOS (M-시리즈 MacBook) 로컬 환경 구축

단계 명령 / 작업 참고 링크 비고
설치 brew install ollama Homebrew Formulae Homebrew 병 패키지, Apple Silicon/Intel 모두 지원
서비스 시작 brew services start ollama Chenzhang Guide 로그인 시 자동 실행 가능
모델 받기 ollama pull llama3.1 첫 실행 시 자동 pull 가능
API 사용 http://localhost:11434/api/chat Apidog Guide OpenAI 호환 엔드포인트 제공

하드웨어 가속: Apple Metal API가 기본 활성화되어 M1~M4 GPU·Neural Engine을 자동 활용합니다—추가 설정 불필요. (Hugging Face Guide)


3. “로컬 툴 콜링”으로 할 수 있는 일

시나리오 요약
CLI/시스템 자동화 로컬 Python 함수 또는 shell script를 툴로 노출 → 모델이 파일 정리·Git 커밋·홈 오토메이션 같은 명령을 직접 실행
VS Code 확장 Ollama API를 익스텐션에서 호출해 LLM-코파일럿 구현 + 로컬 함수(예: AST 리팩터) 연동
프라이빗 RAG MacBook SSD에 있는 PDF/CSV를 Python 툴로 로드 → LLM이 즉시 검색·요약—클라우드 업로드 無
웹/모바일 프로토타입 Next.js·Electron 앱에서 WebSocket + 툴 콜 사용 → 사용자에게 실시간 토큰 스트림 & 부분 UI 즉시 업데이트
에이전트 워크플로 다중 툴(웹 스크래핑, 계산기, DB 쿼리)을 등록 → 모델이 계획-실행-피드백 루프를 전부 로컬에서 수행

4. 개발자 관점 주요 이득

항목 변화 전 변화 후
응답 지연 툴 콜 포함 메시지는 전체 JSON 파싱 완료까지 블로킹 토큰 스트림 즉시 출력, 툴 콜만 분리되어 별도 트리거
상태 관리 “툴 콜인지, 일반 텍스트인지” 후처리 필요 파서가 tool_calls 필드로 반환 → 코드 단순화
모델 호환성 툴 콜 미학습 모델은 실패하거나 JSON 깨짐 파서가 JSON만 보고도 인식 → 더 많은 오픈모델 사용
맥북 리소스 긴 컨텍스트 사용 제약 num_ctx=32000 가능(메모리↑) → RAG 긴 문서 처리

5. 실무 TIP

1. 메모리 최적화

32k 컨텍스트는 7B 모델도 12GB+ RAM을 요구합니다. 필요 시 16k나 8k로 조정하세요.

2. 툴 버전 관리

Python/JS 함수 서명을 버전 컨트롤에 저장해 모델 업그레이드 시 호환성을 체크하세요.

3. 보안

로컬 함수가 파일 삭제·네트워크 호출을 할 수 있으므로, 인수 검증·권한 격리가 필수입니다.

4. 멀티-GPU/Metal

Docker에서는 Metal 패스-스루가 제한적—네이티브 실행을 권장합니다. (Chariot Solutions)


6. 앞으로 기대할 점 (관측·추정)

  • 툴 마켓플레이스: 명령어-서명만 공유해 재사용 가능한 “로컬 플러그인” 생태계가 빠르게 확대될 가능성
  • 스트리밍 UI 컴포넌트: React/SwiftUI에서 토큰 단위 스트림을 표시하는 OSS 컴포넌트가 표준화될 전망
  • Agent 프레임워크: LangChain-JS, CrewAI 등이 ollama:// 프로토콜을 지원하여 완전 오프라인 에이전트 구축이 쉬워질 것

실전 예제: 10분 만에 완성하는 스트리밍 AI 에이전트

아래 예시는 “MacBook 하나만으로 실시간 대화 + 실시간 함수 호출”을 테스트할 수 있는 가장 실전-지향적인 최소 예제입니다. 10분 내에 로컬에서 돌아가는 풀스택 LLM 실험 환경이 완성됩니다.

1. 사전 준비

# 1) Ollama 설치 (Homebrew)
brew install ollama
brew services start ollama     # 백그라운드 서비스 등록

# 2) Python 패키지
python -m venv .venv && source .venv/bin/activate
pip install -U ollama rich

모델 다운로드

ollama pull qwen3     # 예시; llama3.1, devstral 등 지원 모델이면 아무거나 가능

2. “개발자에게 가장 유용한” 파이썬 툴: repo_stats

  • 무엇을 하나요? 지정한 디렉터리(기본: 현재 Git 리포지토리)를 스캔해 언어별 코드 라인 수 통계를 반환합니다.
  • 왜 유용한가요? LLM이 내 프로젝트 규모·언어 비중을 실시간 파악→컨벤션 추천, 빌드 스크립트 생성, 리팩터링 계획 등 의사결정에 바로 활용.

3. 전체 코드 (llm_agent.py)

#!/usr/bin/env python
"""
실시간 스트리밍 + 툴 콜링 데모 (MacBook 전용, Ollama + Python SDK)
"""
import os, pathlib, subprocess, json, sys
from typing import Dict
from ollama import chat, ChatResponse
from rich import print

# --------------------------- 툴 정의 --------------------------- #
def repo_stats(path: str = ".") -> Dict[str, int]:
    """
    Scan a directory recurisvely and return lines-of-code per extension.

    Args:
        path (str): directory to scan, default '.'

    Returns:
        dict: {'.py': 1234, '.js': 567, ...}
    """
    path = pathlib.Path(path).expanduser().resolve()
    if not path.is_dir():
        raise FileNotFoundError(f"{path} is not a directory")

    stats: Dict[str, int] = {}
    for p in path.rglob("*"):
        if p.is_file():
            ext = p.suffix.lower()
            try:
                lines = sum(1 for _ in p.open("r", errors="ignore"))
            except Exception:
                continue
            stats[ext] = stats.get(ext, 0) + lines
    return stats

# ------------------------ 대화 루프 --------------------------- #
SYSTEM = "You are a senior software engineer. You call tools when helpful."

def main():
    print("[bold green]🦙  LLM + Tool streaming demo (Ctrl-C to quit)[/bold green]")
    messages = [{"role": "system", "content": SYSTEM}]

    while True:
        user_input = input("\n[USER] ›  ")
        messages.append({"role": "user", "content": user_input})

        # 스트리밍 호출
        response: ChatResponse = chat(
            model="qwen3",
            messages=messages,
            tools=[repo_stats],   # 함수 객체 그대로 전달
            stream=True,
        )

        tool_results = {}
        for chunk in response:
            # ① 모델이 텍스트 생성 중이면 바로 출력
            if chunk.message.content:
                print(chunk.message.content, end="", flush=True)

            # ② tool_calls 필드가 있으면 즉시 실행
            for call in chunk.message.tool_calls or []:
                func_name = call.function.name
                args = call.function.arguments or {}
                if func_name == "repo_stats":
                    try:
                        result = repo_stats(**args)
                        # 결과를 LLM에게 피드백
                        messages.append({
                            "role": "tool",
                            "name": func_name,
                            "content": json.dumps(result)
                        })
                        tool_results[func_name] = result
                    except Exception as e:
                        messages.append({
                            "role": "tool",
                            "name": func_name,
                            "content": str(e)
                        })
        print()  # 줄바꿈
        if tool_results:
            print(f"[italic]↳ repo_stats returned:[/italic] {tool_results['repo_stats']}")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n[bold red]Session ended.[/bold red]")

실행

python llm_agent.py
  1. 프롬프트 입력: 우리 코드베이스 얼마나 큰지 알려줘. 언어별 통계를 보고 싶어.
  2. 스트림:
    • 모델 토큰이 즉시 콘솔에 흐르고
    • 중간에 [ToolCall] repo_stats {"path": "."} 가 감지되면 파이썬 함수 즉시 실행
  3. 결과 피드백:
    • repo_stats 결과 JSON을 다시 모델에게 넘겨 다음 응답에 활용
    • 모델이 “Python 68%, JavaScript 20%, 기타 12%…” 식으로 해석해 안내

확장 아이디어

기능 간단 방법
CI 자동화 run_tests()build_docker() 함수를 툴로 노출
프라이빗 RAG lookup_file(path:str, query:str) 로 PDF/MD 검색·요약
코드 변환·리팩터링 apply_patch(file:str, diff:str) 로 모델이 직접 git apply
GUI/Web FastAPI + async for chunk in chat(stream=True) → SSE/WebSocket

성능·보안 팁

1. 메모리 조절

OLLAMA_NUM_CTX 환경변수(또는 options={"num_ctx": 16000})로 컨텍스트 길이 튜닝.

2. 권한 격리

위험 명령은 wrapper 함수에서 화이트리스트만 허용.

3. 메탈 가속 확인

ollama --version 출력에 metal: enabled 표시 → GPU·Neural Engine 사용 중.

4. 패키지 관리

툴 함수는 버전 관리(git) & 타이핑 필수—LLM이 시그니처를 신뢰.


마무리

요약 — 이번 업데이트로 MacBook 하나만으로도 “실시간 대화 + 실시간 함수 호출”이 가능한 풀스택 LLM 실험 환경이 완성되었습니다. Homebrew 한 줄로 설치하고, Python 혹은 JavaScript에서 함수만 정의하면 곧바로 스트리밍 대화형 에이전트를 로컬에서 구동할 수 있습니다.

llm_agent.py 하나면 “토큰 스트리밍 ↔ 파이썬 함수 호출 ↔ 결과 피드백” 전체 사이클을 맥북 로컬에서 실시간으로 체험할 수 있습니다. 필요에 따라 함수만 추가해 나가면, 곧바로 개발 워크플로 전체를 LLM-에이전트화할 수 있습니다. 즐거운 실험 되세요! 🎉