Skip to main content
This command sets up a chatbot wrapper template that you can fill in with your chatbot’s details. It also creates or updates a chatbots.json file in .snowglobe directory that tracks the necessary information to connect your chatbot to Snowglobe.

Why use this

Use this to scaffold a wrapper that adapts your chatbot to Snowglobe’s expected input/output so Snowglobe can call it during simulations.

Prerequisites

  • You have installed snowglobe
As of client 1.0.0, you no longer need to authenticate before running init. If you aren’t logged in, init runs the login flow inline and then continues automatically.

Usage

snowglobe init
This will open an interactive prompt to select the chatbot (agent) on Snowglobe for which you want to create a wrapper. Pick the chatbot using the index number printed in the terminal. If you don’t have an agent yet, choose the New Agent option (new) in the prompt. init will ask for a name and description and create the agent for you, then continue. (As of client 1.0.0; previously this required creating the agent in the dashboard first.) Once you’ve selected (or created) the chatbot, the library will create a chatbot wrapper file in the current directory. You can fill in the details of your chatbot in the wrapper file.

Options

--file
If provided, the chatbot wrapper file will be created at the specified path.

Examples of Chatbot Wrappers

A simple chatbot calling an LLM

Example of a chatbot that needs a single LLM call and returns a final string.
llm-calling-chatbot.py
from snowglobe.client import CompletionRequest, CompletionFunctionOutputs
from openai import OpenAI
import os

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def process_scenario(request: CompletionRequest) -> CompletionFunctionOutputs:
"""
Process a scenario request from Snowglobe.

This function is called by the Snowglobe client to process requests. It should return a
CompletionFunctionOutputs object with the response content.

Example CompletionRequest:
CompletionRequest(
    messages=[
        SnowglobeMessage(role="user", content="Hello, how are you?", snowglobe_data=None),
    ]
)

Example CompletionFunctionOutputs:
CompletionFunctionOutputs(response="This is a string response from your application")

Args:
    request (CompletionRequest): The request object containing the messages.

Returns:
    CompletionFunctionOutputs: The response object with the generated content.
"""

# Process the request using the messages. Example:
messages = request.to_openai_messages()
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages
)
return CompletionFunctionOutputs(response=response.choices[0].message.content)

A chatbot that does tool calling

Example of a chatbot that makes a one-shot tool call and then returns a final string to the user.
tool-calling-chatbot.py
"""
ShopSupport: single-step tool-call example

What Snowglobe sends:
  CompletionRequest(
      messages=[SnowglobeMessage(role="user", content="Where is order A1001?")]
  )

What you must return:
  CompletionFunctionOutputs(response="string to display")

Notes:
  - One OpenAI call only. If a tool is requested, we execute it once
    then render a final user-facing string ourselves.
"""

# 1) Imports and setup
from snowglobe.client import CompletionRequest, CompletionFunctionOutputs
from openai import OpenAI
import os, json, uuid
from typing import Dict, Any, Callable, Tuple

MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# 2) Toy app data and business functions
ORDERS: Dict[str, Dict[str, Any]] = {
    "A1001": {"status": "Shipped", "eta_days": 2, "carrier": "UPS"},
    "A1002": {"status": "Processing", "eta_days": 5, "carrier": None},
}
TICKETS: Dict[str, Dict[str, Any]] = {}

def get_order_status(order_id: str) -> Dict[str, Any]:
    """Return order details or found=False."""
    data = ORDERS.get(order_id.upper())
    if not data:
        return {"found": False, "order_id": order_id}
    return {"found": True, "order_id": order_id.upper(), **data}

def create_ticket(email: str, subject: str, body: str) -> Dict[str, Any]:
    """Create a support ticket and return its id."""
    ticket_id = f"T{uuid.uuid4().hex[:8].upper()}"
    TICKETS[ticket_id] = {"email": email, "subject": subject, "body": body, "status": "open"}
    return {"ticket_id": ticket_id, "status": "open"}

# 3) Renderers: turn tool JSON into a final user-facing string
def render_order_status(out: Dict[str, Any], _args: Dict[str, Any]) -> str:
    if not out.get("found"):
        return f"I could not find order {out.get('order_id')}."
    eta = out["eta_days"]
    carrier = out["carrier"] or "carrier pending"
    return f"Order {out['order_id']}: {out['status']}. ETA about {eta} day(s) via {carrier}."

def render_ticket(out: Dict[str, Any], args: Dict[str, Any]) -> str:
    return f"Ticket {out['ticket_id']} opened. We will email updates to {args.get('email')}."

# 4) Tool specs for the model and a small dispatch table
TOOL_SPECS = [
    {
        "type": "function",
        "function": {
            "name": "get_order_status",
            "description": "Look up an order by id like A1234",
            "parameters": {
                "type": "object",
                "properties": {"order_id": {"type": "string"}},
                "required": ["order_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "create_ticket",
            "description": "Open a support ticket",
            "parameters": {
                "type": "object",
                "properties": {
                    "email": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"},
                },
                "required": ["email", "subject", "body"],
            },
        },
    },
]

# name -> (callable, renderer)
TOOL_REGISTRY: Dict[str, Tuple[Callable[..., Dict[str, Any]], Callable[[Dict[str, Any], Dict[str, Any]], str]]] = {
    "get_order_status": (get_order_status, render_order_status),
    "create_ticket": (create_ticket, render_ticket),
}

# 5) System prompt for predictable routing
SYSTEM_PROMPT = (
    "You are ShopSupport. "
    "If the user asks about an order, call get_order_status. "
    "If they want to open a support ticket, call create_ticket. "
    "If no tool fits, answer directly and be concise."
)

# 6) The Snowglobe entry point
def process_scenario(request: CompletionRequest) -> CompletionFunctionOutputs:
    """
    Entry point called by Snowglobe. Returns a plain string response.
    """
    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    messages += request.to_openai_messages()

    resp = client.chat.completions.create(
        model=MODEL,
        messages=messages,
        tools=TOOL_SPECS,
        tool_choice="auto",
    )
    msg = resp.choices[0].message
    tool_calls = getattr(msg, "tool_calls", None)

    # No tool called: just return the model's text
    if not tool_calls:
        return CompletionFunctionOutputs(response=msg.content or "")

    # Single step: handle the first tool call only
    tc = tool_calls[0]
    try:
        args = json.loads(tc.function.arguments or "{}")
    except Exception:
        return CompletionFunctionOutputs(response="I could not parse the tool arguments.")

    handler = TOOL_REGISTRY.get(tc.function.name)
    if not handler:
        return CompletionFunctionOutputs(response="I cannot run the requested tool.")

    func, render = handler
    try:
        out = func(**args)  # run the business function
        text = render(out, args)  # format a user-friendly message
        return CompletionFunctionOutputs(response=text)
    except TypeError:
        return CompletionFunctionOutputs(response="The tool arguments were incomplete.")
    except Exception:
        return CompletionFunctionOutputs(response="The tool failed to run.")

A chatbot that maintains sessions for conversation

Example of a chat chatbot that maintains per-session chat history across turns.
session-chatbot.py
"""
SessionChatbot: simple session-managed chat example

What Snowglobe sends:
  CompletionRequest(
      messages=[
        # Include a hidden system tag to identify the session (fallback):
        SnowglobeMessage(role="system", content="[session: u123]"),
        # Each message also includes IDs you can use directly:
        SnowglobeMessage(
          role="user",
          content="Hey, what's up?",
          conversation_id="conv_u123",  # stable per conversation
          message_id="msg_0001"          # unique per message
        )
      ]
  )

What you return:
  CompletionFunctionOutputs(response="string to display")

Notes:
  - The wrapper keeps a chat history per session_id in memory.
  - Prefer session management via `conversation_id` on `SnowglobeMessage`.
    Fallback: read a system tag like [session: your-id] if needed.
  - `SnowglobeMessage` also contains `message_id`, which you can use
    to build a per-message lookup table (e.g., metadata, deduping).
  - Replace the in-memory store with Redis for production.
"""

# 1) Imports and setup
from snowglobe.client import CompletionRequest, CompletionFunctionOutputs
from openai import OpenAI
import os, re
from typing import Dict, List

MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# 2) Session store (swap with Redis or your DB later)
class SessionStore:
    def __init__(self, max_turns: int = 20):
        # session_id -> list of OpenAI-style messages
        self._db: Dict[str, List[Dict[str, str]]] = {}
        self.max_turns = max_turns

    def load(self, session_id: str) -> List[Dict[str, str]]:
        return list(self._db.get(session_id, []))

    def save(self, session_id: str, history: List[Dict[str, str]]) -> None:
        # keep the last N turns to cap context size
        self._db[session_id] = history[-self.max_turns :]

STORE = SessionStore(max_turns=24)

# 3) Helpers: session id, system prompts, last user text
SESSION_TAG = re.compile(r"\[session:\s*([A-Za-z0-9_-]{1,64})\]", flags=re.I)

def get_session_id(request: CompletionRequest) -> str:
    """
    Use `conversation_id` from any message if present.
    Otherwise, look for a tag like [session: abc123] in system messages.
    If neither exists, use 'default'.
    """
    # 1) Prefer explicit IDs provided by Snowglobe
    for m in request.messages:
        cid = getattr(m, "conversation_id", None)
        if cid:
            return str(cid)

    # 2) Fallback to a system-tag-based session id
    for m in request.messages:
        if getattr(m, "role", "") == "system" and m.content:
            hit = SESSION_TAG.search(m.content)
            if hit:
                return hit.group(1)
    return "default"

def system_prompts_from_request(request: CompletionRequest) -> List[str]:
    """
    Pull through any system prompts from Snowglobe, but strip session tags.
    """
    prompts: List[str] = []
    for m in request.messages:
        if getattr(m, "role", "") == "system" and m.content:
            cleaned = SESSION_TAG.sub("", m.content).strip()
            if cleaned:
                prompts.append(cleaned)
    return prompts

def latest_user_text(request: CompletionRequest) -> str:
    for m in reversed(request.messages):
        if getattr(m, "role", "") == "user":
            return m.content or ""
    return ""

# 4) System prompt for tone and routing
BASE_SYSTEM = (
    "You are a concise helpful assistant. "
    "Use prior conversation context to stay consistent."
)

# 5) Snowglobe entry point
def process_scenario(request: CompletionRequest) -> CompletionFunctionOutputs:
    """
    Per-call flow:
      1) Read session_id from system tag.
      2) Load prior history from the store.
      3) Append the latest user message.
      4) Call the model with [base system + any extra system prompts + history + new user].
      5) Save the new user and assistant turns back to the store.
      6) Return the assistant text as a string.
    """
    session_id = get_session_id(request)
    history = STORE.load(session_id)
    user_text = latest_user_text(request)

    # Build the prompt
    messages: List[Dict[str, str]] = [{"role": "system", "content": BASE_SYSTEM}]
    for sp in system_prompts_from_request(request):
        messages.append({"role": "system", "content": sp})
    messages.extend(history)  # prior turns for this session
    if user_text:
        messages.append({"role": "user", "content": user_text})

    # Call the model
    resp = client.chat.completions.create(model=MODEL, messages=messages)
    assistant_text = resp.choices[0].message.content or ""

    # Persist new turns for the session
    if user_text:
        history.append({"role": "user", "content": user_text})
    history.append({"role": "assistant", "content": assistant_text})
    STORE.save(session_id, history)

    return CompletionFunctionOutputs(response=assistant_text)
Every SnowglobeMessage includes conversation_id and message_id. Use:
  • conversation_id: as your session key to load and persist per-conversation history
  • message_id: to maintain a lookup table for message-level metadata (e.g., tool outputs, retrieval keys, deduplication)