mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
102 lines
4.3 KiB
Python
102 lines
4.3 KiB
Python
"""Twelve questions about one support ticket: asked in one call, then in twelve calls of one.
|
|
|
|
Run with HANZO_API_KEY set, after `pip install hanzo-kai`.
|
|
"""
|
|
|
|
from hanzo_kai import Kai, Choice, Noul, Score, RetryPolicy
|
|
|
|
TICKET = {
|
|
"subject": "Deploys to production failing since 09:10 UTC",
|
|
"body": (
|
|
"Every deploy of our api to production has failed since 09:10 UTC with 'image pull backoff'. "
|
|
"Staging deploys still work. We launch on Friday and the whole team is blocked. "
|
|
"If this is on your side, we expect the build minutes refunded."
|
|
),
|
|
"plan": "team",
|
|
}
|
|
|
|
QUESTIONS = {
|
|
"team": Choice(
|
|
instructions="Which team should handle `body`?",
|
|
criteria={
|
|
"billing": "charges, invoices and refunds",
|
|
"platform": "deploys, builds and clusters",
|
|
"account": "logins, keys and members",
|
|
"sales": "plans, pricing and upgrades",
|
|
},
|
|
),
|
|
"intent": Choice(
|
|
instructions="What does the sender want?",
|
|
criteria={
|
|
"fix": "something broken made to work",
|
|
"howto": "to learn how to do something",
|
|
"feature": "a capability that does not exist yet",
|
|
"cancel": "to stop using the service",
|
|
},
|
|
),
|
|
"environment": Choice(
|
|
instructions="Which environment is failing?",
|
|
criteria={"production": "production only", "staging": "staging only", "both": "production and staging"},
|
|
),
|
|
"language": Choice(instructions="Which language is `body` written in?", criteria=["english", "spanish", "german", "french"]),
|
|
"refund": Noul(
|
|
instructions="The sender asks for money back.",
|
|
criteria={"true": "a refund or credit is requested", "false": "no refund or credit is requested"},
|
|
),
|
|
"outage": Noul(
|
|
instructions="The sender reports something that does not work.",
|
|
criteria={"true": "a service or feature is failing", "false": "everything works"},
|
|
),
|
|
"blocked": Noul(
|
|
instructions="The sender's work is blocked.",
|
|
criteria={"true": "they cannot proceed until this is fixed", "false": "they can keep working"},
|
|
),
|
|
"human": Noul(
|
|
instructions="A person should reply, not a template.",
|
|
criteria={"true": "the case needs judgment or an apology", "false": "a standard answer covers it"},
|
|
),
|
|
"urgency": Score(instructions="How urgent is `body`?", criteria=["can wait", "this week", "today", "now"]),
|
|
"sentiment": Score(instructions="How does the sender feel?", criteria=["angry", "frustrated", "neutral", "pleased"]),
|
|
"churn": Score(instructions="How likely is the sender to leave?", criteria=["unlikely", "possible", "likely"]),
|
|
"severity": Score(
|
|
instructions="How severe is the problem?",
|
|
criteria=["cosmetic", "degraded", "broken for some users", "broken for everyone"],
|
|
),
|
|
}
|
|
|
|
|
|
def top(answer):
|
|
"""An answer's most probable label or level, and its probability."""
|
|
if answer.type == "noul":
|
|
return ("true" if answer.noul >= 0.5 else "false"), max(answer.noul, 1 - answer.noul)
|
|
best = max(answer.probabilities, key=answer.probabilities.get)
|
|
label = answer.legend[best] if answer.type == "score" else best
|
|
return label, answer.probabilities[best]
|
|
|
|
|
|
def distribution(answer):
|
|
"""Every probability an answer carries, in order."""
|
|
if answer.type == "noul":
|
|
return [answer.noul]
|
|
return list(answer.probabilities.values())
|
|
|
|
|
|
with Kai(retry=RetryPolicy(max_retries=20)) as kai:
|
|
batched = kai.decide(TICKET, QUESTIONS)
|
|
single = {name: kai.decide(TICKET, {name: question}) for name, question in QUESTIONS.items()}
|
|
|
|
print(f"{'question':12} {'one call':>28} {'one call each':>28}")
|
|
gap = 0.0
|
|
for name in QUESTIONS:
|
|
(a, p), (b, q) = top(batched.answers[name]), top(single[name].answers[name])
|
|
print(f"{name:12} {a:>22} {p:.3f} {b:>22} {q:.3f}")
|
|
pairs = zip(distribution(batched.answers[name]), distribution(single[name].answers[name]))
|
|
gap = max([gap, *(abs(x - y) for x, y in pairs)])
|
|
|
|
once = batched.usage.input_tokens
|
|
each = sum(d.usage.input_tokens for d in single.values())
|
|
print()
|
|
print(f"calls 1 against {len(single)}")
|
|
print(f"billed input tokens {once:>9} against {each}")
|
|
print(f"largest probability gap between the two ways: {gap:.6f}")
|