본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

Stateless session과 remote Ollama

~18 min · adapters, session, remote

Level 0Downloader
0 XP0/41 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Ollama는 stateless

Ollama는 request 사이에 아무것도 기억 안 해. Session id도 없고 server 쪽 history도 없어. 대화를 이어가려면 매 turn마다 message history를 통째로 다시 보내야 해. Claude Agent SDK는 session마다 subprocess를 살려두는데, 그게 예외지 규칙이 아니야. 대부분의 LLM API는 stateless고, Ollama가 그 일반적인 쪽이야.

뭐 만들어?

Adapter 위에 얇은 OllamaSession을 하나 얹어. message list를 들고 있다가 user/assistant turn마다 붙이고, 보낼 때마다 통째로 다시 실어 보내는 거야. Adapter는 이런 게 있는지도 몰라. session은 그 위층 얘기니까.

Remote Ollama

Server Mac에 OLLAMA_HOST=0.0.0.0:11434 박아서 daemon을 네트워크에 노출. 그러면 노트북에선 adapter의 주소만 http://server-mac.local:11434 (또는 Tailscale IP)로 바꿔서, 무거운 inference를 큰 하드웨어로 넘길 수 있어. 같은 API에 base URL만 다른 거야.

피파 fleet 패턴

피파 office Mac이 Ollama로 더 큰 70B-class 모델 로드해서 돌려. Fleet의 다른 Mac들은 무거운 inference면 Ollama adapter를 office의 Tailscale IP 쪽으로 돌리고, 가벼운 작업이면 local에서 작은 모델을 돌려. 이 선택은 호출마다 갈려. adapter의 base_url 하나로 정해지는 거야.

Code

Adapter 위 session wrapper·python
class OllamaSession:
    """Stateless Ollama용 대화 state 유지."""

    def __init__(self, adapter: OllamaAdapter, system_prompt: str | None = None):
        self.adapter = adapter
        self.history: list[dict] = []
        if system_prompt:
            self.history.append({"role": "system", "content": system_prompt})

    async def send(self, user_message: str) -> str:
        self.history.append({"role": "user", "content": user_message})
        full = ""
        async for chunk in self.adapter.stream(self.history):
            if chunk.done:
                break
            full += chunk.content
        self.history.append({"role": "assistant", "content": full})
        return full

    def clear(self) -> None:
        """System prompt만 유지하고 reset."""
        self.history = [m for m in self.history if m["role"] == "system"]
Tailscale 통한 remote Ollama·python
# Server Mac에 한 번:
#   launchctl setenv OLLAMA_HOST 0.0.0.0:11434
#   (그다음 Ollama 재시작)
# 이게 daemon을 모든 인터페이스에 노출. Tailscale이나 다른 VPN 사용 —
# 진짜 auth layer 앞에 안 두고는 절대 public IP에 두지 마.

LOCAL  = OllamaAdapter(base_url="http://localhost:11434",
                        model="qwen2.5:3b")          # 빠른 작은 작업
REMOTE = OllamaAdapter(base_url="http://100.x.x.x:11434",
                        model="qwen2.5:72b")         # 무거운 reasoning

async def smart_call(prompt: str, heavy: bool):
    a = REMOTE if heavy else LOCAL
    async for chunk in a.stream([{"role": "user", "content": prompt}]):
        if chunk.done:
            break
        print(chunk.content, end="", flush=True)

External links

Exercise

OllamaSession 구현. 5-turn 대화 가지고 모델이 이전 turn 기억하는지 verify. 그다음 두 번째 adapter를 다른 머신 (또는 non-default port의 Docker Ollama)에 가리켜서 같은 session class가 어느 base URL에서든 동작 확인.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.