thangvip's picture
feat: deploy complete Compliment Forest app
9dad6a7 verified
Raw
History Blame
4.99 kB
from __future__ import annotations
import json
from typing import Protocol
from urllib.parse import urlparse
import httpx
from compliment_forest.prompts import author_messages, critic_messages
class TextBackend(Protocol):
def author(
self,
name: str,
situation: str,
*,
feedback: dict[str, str] | None = None,
original: dict[str, object] | None = None,
) -> str: ...
def critic(self, name: str, situation: str, forest: dict[str, object]) -> str: ...
class DemoTextBackend:
"""Deterministic development backend with the same model contract."""
_CLEARINGS = (
(
"The Patient Fox",
"patience under pressure",
"a gentle russet fox sitting in a mossy clearing, soft kind eyes",
"I am allowed to learn.",
),
(
"The Listening Owl",
"careful perspective",
"a round tawny owl resting on a low branch, attentive kind eyes",
"I can listen before I leap.",
),
(
"The Brave Snail",
"quiet courage",
"a tiny snail crossing a fern frond, softly glowing spiral shell",
"I make progress at my pace.",
),
(
"The Steady Deer",
"steadiness through change",
"a small deer standing in morning mist, calm gentle expression",
"I can meet one moment.",
),
(
"The Clear-Voiced Wren",
"honest self-expression",
"a tiny wren singing beside dusty rose wildflowers",
"I can speak gently and clearly.",
),
)
def author(
self,
name: str,
situation: str,
*,
feedback: dict[str, str] | None = None,
original: dict[str, object] | None = None,
) -> str:
clearings = []
for creature, strength, image_prompt, spell in self._CLEARINGS:
clearings.append(
{
"creature": creature,
"strength": strength,
"line": (
f"{situation.rstrip('.')} may feel uncertain. Your {strength} "
"lets you respond without pretending the hard part is easy."
),
"reflection": (
"What becomes possible when you ask for one honest next step "
"instead of a perfect answer?"
),
"spell": spell,
"image_prompt": image_prompt,
}
)
payload = {
"forest_title": f"{name}'s Path Through the New",
"proposed_strengths": [item[1] for item in self._CLEARINGS],
"clearings": clearings,
}
return json.dumps(payload)
def critic(self, name: str, situation: str, forest: dict[str, object]) -> str:
count = min(len(forest.get("clearings", [])), 5)
return json.dumps(
{
"keep_indices": list(range(count)),
"revise_indices": [],
"reasons": {},
}
)
class LlamaCppTextBackend:
"""OpenAI-compatible client restricted to a local llama.cpp server."""
def __init__(
self,
base_url: str = "http://127.0.0.1:8080",
model: str = "compliment-forest-minicpm5-1b",
timeout: float = 90,
) -> None:
parsed = urlparse(base_url)
if parsed.hostname not in {"127.0.0.1", "localhost", "::1"}:
raise ValueError("llama.cpp base URL must resolve to the local machine")
self.base_url = base_url.rstrip("/")
self.model = model
self.client = httpx.Client(timeout=timeout)
def _complete(self, messages: list[dict[str, str]], max_tokens: int) -> str:
response = self.client.post(
f"{self.base_url}/v1/chat/completions",
json={
"model": self.model,
"messages": messages,
"temperature": 0.35,
"top_p": 0.9,
"max_tokens": max_tokens,
"response_format": {"type": "json_object"},
"chat_template_kwargs": {"enable_thinking": False},
},
)
response.raise_for_status()
payload = response.json()
return payload["choices"][0]["message"]["content"]
def author(
self,
name: str,
situation: str,
*,
feedback: dict[str, str] | None = None,
original: dict[str, object] | None = None,
) -> str:
return self._complete(
author_messages(name, situation, feedback=feedback, original=original),
max_tokens=1700,
)
def critic(self, name: str, situation: str, forest: dict[str, object]) -> str:
return self._complete(critic_messages(name, situation, forest), max_tokens=800)