Files
tazlin 535e4a40c4 tools: track the chat stack drivers, probes and recipes with a usage page
- `tools/chat_stack/` holds the repeatable local-stack tooling: the fake chat worker driver, the logging proxy, the native and SDK round trips, the SSE probe and stream demo, the documentation claim checker, the OpenAI shape probe, the chat worker validator, the coding demo, and the `start_horde.sh` and `start_front.sh` recipes over `tests/run_with_test_stack.sh`
- Every tool takes its endpoints as arguments with environment fallbacks and its key from an argument or `HORDE_API_KEY` (`HORDE_WORKER_API_KEY` for the worker driver, `OPENAI_API_KEY` for the probe), so it does not assume a file path, machine name or key file; outputs go to the working directory or `--out`
- `tools/chat_stack/README.md` lists each tool with every argument, fallback and default, the stack bring-up order and the full-suite command
2026-10-30 23:46:12 -04:00

144 lines
5.6 KiB
Python

# SPDX-FileCopyrightText: 2026 Tazlin
#
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Live checks of delta relay and the messages offload against the local front.
Usage: stream_demo.py [--base-url URL] [--horde-url URL] [--api-key KEY] [--model NAME]
Cases:
1. streamed plain request: deltas must arrive before the final reply (several content chunks, spread in time)
2. non-streamed plain request: unchanged whole reply
3. streamed request with tools: buffered (one content/tool chunk only after the reply completes)
4. streamed request padded past the offload threshold: served through messages_url (checked on the native status)
"""
from __future__ import annotations
import argparse
import json
import os
import time
import httpx
from openai import OpenAI
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument(
"--base-url", default=os.environ.get("CHAT_FRONT_URL", "http://127.0.0.1:7002/v1"), help="Front base URL (env CHAT_FRONT_URL)."
)
p.add_argument("--api-key", default=os.environ.get("HORDE_API_KEY"), help="Requester API key (env HORDE_API_KEY).")
p.add_argument("--model", default="koboldcpp/Llama-3.2-3B-Instruct")
p.add_argument("--horde-url", default=os.environ.get("HORDE_URL", "http://127.0.0.1:7101"), help="Horde root (env HORDE_URL).")
a = p.parse_args()
if not a.api_key:
p.error("an API key is required: pass --api-key or set HORDE_API_KEY")
model = a.model
client = OpenAI(base_url=a.base_url, api_key=a.api_key, timeout=600)
horde = httpx.Client(base_url=f"{a.horde_url.rstrip('/')}/api/v2", headers={"apikey": a.api_key}, timeout=30)
def streamed(messages, **extra):
start = time.monotonic()
stamps = []
text = []
finish = None
usage = None
request_id = None
for chunk in client.chat.completions.create(
model=model, messages=messages, stream=True, max_tokens=400, stream_options={"include_usage": True}, **extra
):
now = time.monotonic() - start
if chunk.choices:
delta = chunk.choices[0].delta
if delta.content:
stamps.append(now)
text.append(delta.content)
if chunk.choices[0].finish_reason:
finish = chunk.choices[0].finish_reason
x_horde = getattr(chunk, "x_horde", None)
if x_horde and isinstance(x_horde, dict):
request_id = x_horde.get("request_id") or request_id
if chunk.usage:
usage = chunk.usage
total = time.monotonic() - start
return {
"chunks": len(stamps),
"first": stamps[0] if stamps else None,
"last": stamps[-1] if stamps else None,
"total": total,
"text": "".join(text),
"finish": finish,
"usage": usage and usage.model_dump(),
"request_id": request_id,
}
print("== 1. streamed plain")
r1 = streamed([{"role": "user", "content": "Write a long story about a lighthouse keeper and a storm, at least twelve sentences."}])
print(json.dumps({k: v for k, v in r1.items() if k != "text"}, indent=1))
print("text:", r1["text"][:200].replace("\n", " "))
assert r1["chunks"] >= 2, "expected more than one content chunk"
assert r1["last"] - r1["first"] > 0.3, "chunks did not spread over time: not live"
assert r1["first"] < r1["total"] * 0.8, "first chunk arrived too late to be live"
print("LIVE DELTAS OK")
print("== 2. non-streamed plain")
start = time.monotonic()
c2 = client.chat.completions.create(model=model, messages=[{"role": "user", "content": "Name three colours, one per line."}], max_tokens=60)
print({"total": round(time.monotonic() - start, 2), "finish": c2.choices[0].finish_reason, "text": c2.choices[0].message.content[:120]})
assert c2.choices[0].message.content
print("NON-STREAM OK")
print("== 3. streamed with tools (buffered)")
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Weather for a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}
]
start = time.monotonic()
stamps = []
tool_chunks = 0
content_chunks = 0
for chunk in client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "What is the weather in Paris? Use the tool."}],
stream=True,
max_tokens=120,
tools=tools,
tool_choice="auto",
):
if chunk.choices:
d = chunk.choices[0].delta
if d.tool_calls:
tool_chunks += 1
stamps.append(time.monotonic() - start)
if d.content:
content_chunks += 1
stamps.append(time.monotonic() - start)
total = time.monotonic() - start
print({"tool_chunks": tool_chunks, "content_chunks": content_chunks, "stamps": [round(s, 2) for s in stamps], "total": round(total, 2)})
assert tool_chunks + content_chunks >= 1
if len(stamps) > 1:
assert stamps[-1] - stamps[0] < 0.5, "tool request streamed live; expected buffered"
print("TOOLS BUFFERED OK")
print("== 4. streamed, padded past the offload threshold")
padding = "The quick brown fox jumps over the lazy dog. " * 80
r4 = streamed([{"role": "system", "content": padding}, {"role": "user", "content": "Reply with one short sentence about foxes."}])
print(json.dumps({k: v for k, v in r4.items() if k != "text"}, indent=1))
print("text:", r4["text"][:160].replace("\n", " "))
assert r4["text"]
if r4["request_id"]:
status = horde.get(f"/generate/chat/status/{r4['request_id']}").json()
print(
"native status generations:",
json.dumps([{k: g.get(k) for k in ("worker_name", "model", "state")} for g in status.get("generations", [])], indent=1),
)
print("OFFLOAD REQUEST OK")