Spaces:
Sleeping
fix: natural conversation flow — greet before querying DB
Browse files- Add premature_query_penalty (-0.15) when agent issues a DB query as
its first action before greeting the customer; wired into both
compute_step_reward and compute_hierarchy_reward, and into
_handle_query_action which builds its own Reward directly
- Update system prompts (SYSTEM_PROMPT, SUPPORT_AGENT_PROMPT in
inference.py; SUPPORT_AGENT_PROMPT in frontend ai-action route) to
mandate greet → gather info → query flow; remove old "query the DB
first" instruction that drove robotic behaviour
- Fix inference.py missing X-API-Key header on all httpx calls (401s
on reset/step); add _ENV_HEADERS from ENV_API_KEY env var
- Fix frontend Dockerfile healthcheck: wget resolves localhost to ::1
on BusyBox but Next.js only binds IPv4; switch to 127.0.0.1 and
target /demo (which returns 200, not the 307 that / redirects from)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- env/environment.py +9 -3
- env/reward_engine.py +28 -0
- frontend/Dockerfile +1 -1
- frontend/src/app/api/ai-action/route.ts +13 -4
- inference.py +35 -6
|
@@ -260,15 +260,21 @@ class CustomerSupportEnv:
|
|
| 260 |
# Minimal reward — actual signal computed in reward_engine via DB signals.
|
| 261 |
# Using _clamp_db_total so spam queries can't push the reward above the
|
| 262 |
# normal cap, and so wasted-query penalties are bounded.
|
| 263 |
-
from env.reward_engine import compute_db_signals, _clamp_db_total
|
| 264 |
db_signals = compute_db_signals(action, self._ticket, self._history, self._retrieved_data)
|
| 265 |
raw_db = _clamp_db_total(db_signals)
|
|
|
|
| 266 |
import numpy as np
|
| 267 |
reward = Reward(
|
| 268 |
-
value=float(np.clip(0.5 + raw_db, 0.0, 1.0)),
|
| 269 |
resolution_score=0.0, tone_score=0.5,
|
| 270 |
efficiency_score=0.0, accuracy_score=0.0,
|
| 271 |
-
breakdown={
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
)
|
| 273 |
|
| 274 |
self._action_log.append({
|
|
|
|
| 260 |
# Minimal reward — actual signal computed in reward_engine via DB signals.
|
| 261 |
# Using _clamp_db_total so spam queries can't push the reward above the
|
| 262 |
# normal cap, and so wasted-query penalties are bounded.
|
| 263 |
+
from env.reward_engine import compute_db_signals, _clamp_db_total, compute_premature_query_penalty
|
| 264 |
db_signals = compute_db_signals(action, self._ticket, self._history, self._retrieved_data)
|
| 265 |
raw_db = _clamp_db_total(db_signals)
|
| 266 |
+
premature_penalty = compute_premature_query_penalty(action, self._history)
|
| 267 |
import numpy as np
|
| 268 |
reward = Reward(
|
| 269 |
+
value=float(np.clip(0.5 + raw_db + premature_penalty, 0.0, 1.0)),
|
| 270 |
resolution_score=0.0, tone_score=0.5,
|
| 271 |
efficiency_score=0.0, accuracy_score=0.0,
|
| 272 |
+
breakdown={
|
| 273 |
+
"db_signals": db_signals,
|
| 274 |
+
"premature_query_penalty": premature_penalty,
|
| 275 |
+
"is_terminal": False,
|
| 276 |
+
"action_type": at.value,
|
| 277 |
+
},
|
| 278 |
)
|
| 279 |
|
| 280 |
self._action_log.append({
|
|
@@ -215,6 +215,27 @@ def compute_contradiction_penalty(action: Action, history: List[Message]) -> flo
|
|
| 215 |
return -0.15 if (prev_claimed_done and now_asking_info) else 0.0
|
| 216 |
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
def compute_keyword_stuffing_penalty(message: str) -> float:
|
| 219 |
"""
|
| 220 |
Detect and penalize keyword stuffing.
|
|
@@ -449,6 +470,7 @@ def compute_step_reward(
|
|
| 449 |
loop_penalty = compute_loop_penalty(history)
|
| 450 |
contradiction_penalty = compute_contradiction_penalty(action, history)
|
| 451 |
stuffing_penalty = compute_keyword_stuffing_penalty(tone_msg)
|
|
|
|
| 452 |
|
| 453 |
if is_terminal:
|
| 454 |
resolution_score = compute_resolution_score(action, ticket, history)
|
|
@@ -481,6 +503,7 @@ def compute_step_reward(
|
|
| 481 |
+ escalation_penalty
|
| 482 |
+ stuffing_penalty
|
| 483 |
+ info_gathering_bonus
|
|
|
|
| 484 |
+ db_total
|
| 485 |
)
|
| 486 |
|
|
@@ -499,6 +522,7 @@ def compute_step_reward(
|
|
| 499 |
"escalation_penalty": escalation_penalty,
|
| 500 |
"keyword_stuffing_penalty": stuffing_penalty,
|
| 501 |
"info_gathering_bonus": info_gathering_bonus,
|
|
|
|
| 502 |
**{f"db_{k}": v for k, v in db_signals.items()},
|
| 503 |
"is_terminal": is_terminal,
|
| 504 |
},
|
|
@@ -539,6 +563,7 @@ def compute_hierarchy_reward(
|
|
| 539 |
loop_penalty = compute_loop_penalty(history)
|
| 540 |
contradiction_penalty = compute_contradiction_penalty(action, history)
|
| 541 |
stuffing_penalty = compute_keyword_stuffing_penalty(tone_msg)
|
|
|
|
| 542 |
efficiency_score = compute_efficiency_score(steps_used, max_steps)
|
| 543 |
accuracy_score = compute_accuracy_score(history, ticket)
|
| 544 |
|
|
@@ -666,6 +691,7 @@ def compute_hierarchy_reward(
|
|
| 666 |
+ escalation_penalty
|
| 667 |
+ ignored_feedback_penalty
|
| 668 |
+ unnecessary_manager_penalty
|
|
|
|
| 669 |
+ db_total
|
| 670 |
)
|
| 671 |
else:
|
|
@@ -681,6 +707,7 @@ def compute_hierarchy_reward(
|
|
| 681 |
+ stuffing_penalty
|
| 682 |
+ ignored_feedback_penalty
|
| 683 |
+ unnecessary_manager_penalty
|
|
|
|
| 684 |
+ db_total
|
| 685 |
)
|
| 686 |
|
|
@@ -754,6 +781,7 @@ def compute_hierarchy_reward(
|
|
| 754 |
"keyword_stuffing_penalty": stuffing_penalty,
|
| 755 |
"ignored_feedback_penalty": ignored_feedback_penalty,
|
| 756 |
"unnecessary_manager_penalty": unnecessary_manager_penalty,
|
|
|
|
| 757 |
**{f"db_{k}": v for k, v in db_signals.items()},
|
| 758 |
"is_terminal": is_terminal,
|
| 759 |
"role": role,
|
|
|
|
| 215 |
return -0.15 if (prev_claimed_done and now_asking_info) else 0.0
|
| 216 |
|
| 217 |
|
| 218 |
+
def compute_premature_query_penalty(action: Action, history: List[Message]) -> float:
|
| 219 |
+
"""
|
| 220 |
+
-0.15 if the agent fires a DB query before ever greeting the customer.
|
| 221 |
+
|
| 222 |
+
A professional support agent always acknowledges the customer first.
|
| 223 |
+
Querying the DB as the opening move is robotic, hurts empathy scores, and
|
| 224 |
+
signals the model is pattern-matching identifiers rather than conversing.
|
| 225 |
+
|
| 226 |
+
Only triggers when there are zero prior agent messages in the history, so
|
| 227 |
+
legitimate second-or-later queries (after a greeting) are never penalized.
|
| 228 |
+
"""
|
| 229 |
+
if action.action_type not in (
|
| 230 |
+
ActionType.QUERY_USER_PROFILE, ActionType.QUERY_ORDER_DETAILS
|
| 231 |
+
):
|
| 232 |
+
return 0.0
|
| 233 |
+
prior_agent = [m for m in history if m.role == "agent" and m.content.strip()]
|
| 234 |
+
if not prior_agent:
|
| 235 |
+
return -0.15
|
| 236 |
+
return 0.0
|
| 237 |
+
|
| 238 |
+
|
| 239 |
def compute_keyword_stuffing_penalty(message: str) -> float:
|
| 240 |
"""
|
| 241 |
Detect and penalize keyword stuffing.
|
|
|
|
| 470 |
loop_penalty = compute_loop_penalty(history)
|
| 471 |
contradiction_penalty = compute_contradiction_penalty(action, history)
|
| 472 |
stuffing_penalty = compute_keyword_stuffing_penalty(tone_msg)
|
| 473 |
+
premature_query_penalty = compute_premature_query_penalty(action, history)
|
| 474 |
|
| 475 |
if is_terminal:
|
| 476 |
resolution_score = compute_resolution_score(action, ticket, history)
|
|
|
|
| 503 |
+ escalation_penalty
|
| 504 |
+ stuffing_penalty
|
| 505 |
+ info_gathering_bonus
|
| 506 |
+
+ premature_query_penalty
|
| 507 |
+ db_total
|
| 508 |
)
|
| 509 |
|
|
|
|
| 522 |
"escalation_penalty": escalation_penalty,
|
| 523 |
"keyword_stuffing_penalty": stuffing_penalty,
|
| 524 |
"info_gathering_bonus": info_gathering_bonus,
|
| 525 |
+
"premature_query_penalty": premature_query_penalty,
|
| 526 |
**{f"db_{k}": v for k, v in db_signals.items()},
|
| 527 |
"is_terminal": is_terminal,
|
| 528 |
},
|
|
|
|
| 563 |
loop_penalty = compute_loop_penalty(history)
|
| 564 |
contradiction_penalty = compute_contradiction_penalty(action, history)
|
| 565 |
stuffing_penalty = compute_keyword_stuffing_penalty(tone_msg)
|
| 566 |
+
premature_query_penalty = compute_premature_query_penalty(action, history)
|
| 567 |
efficiency_score = compute_efficiency_score(steps_used, max_steps)
|
| 568 |
accuracy_score = compute_accuracy_score(history, ticket)
|
| 569 |
|
|
|
|
| 691 |
+ escalation_penalty
|
| 692 |
+ ignored_feedback_penalty
|
| 693 |
+ unnecessary_manager_penalty
|
| 694 |
+
+ premature_query_penalty
|
| 695 |
+ db_total
|
| 696 |
)
|
| 697 |
else:
|
|
|
|
| 707 |
+ stuffing_penalty
|
| 708 |
+ ignored_feedback_penalty
|
| 709 |
+ unnecessary_manager_penalty
|
| 710 |
+
+ premature_query_penalty
|
| 711 |
+ db_total
|
| 712 |
)
|
| 713 |
|
|
|
|
| 781 |
"keyword_stuffing_penalty": stuffing_penalty,
|
| 782 |
"ignored_feedback_penalty": ignored_feedback_penalty,
|
| 783 |
"unnecessary_manager_penalty": unnecessary_manager_penalty,
|
| 784 |
+
"premature_query_penalty": premature_query_penalty,
|
| 785 |
**{f"db_{k}": v for k, v in db_signals.items()},
|
| 786 |
"is_terminal": is_terminal,
|
| 787 |
"role": role,
|
|
@@ -45,6 +45,6 @@ ENV PORT=3000
|
|
| 45 |
ENV HOSTNAME=0.0.0.0
|
| 46 |
|
| 47 |
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
|
| 48 |
-
CMD wget -qO- http://
|
| 49 |
|
| 50 |
CMD ["node", "server.js"]
|
|
|
|
| 45 |
ENV HOSTNAME=0.0.0.0
|
| 46 |
|
| 47 |
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
|
| 48 |
+
CMD wget -qO- http://127.0.0.1:3000/demo > /dev/null 2>&1 || exit 1
|
| 49 |
|
| 50 |
CMD ["node", "server.js"]
|
|
@@ -19,11 +19,20 @@ ACTION TYPES — output exactly one per step:
|
|
| 19 |
- "query_user_profile" → look up customer DB (internal) → requires: "email"
|
| 20 |
- "query_order_details" → look up order DB (internal) → requires: "order_id"
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
SCORING: Empathy(30%) + Accuracy(25%) + Resolution(25%) + Efficiency(20%)
|
| 23 |
-
Be warm, gather info from "Unresolved issues", use specific resolution language.
|
| 24 |
-
If supervisor gave feedback, INCORPORATE it into your next action.
|
| 25 |
-
If the ticket references an email or order-id you haven't looked up yet, query
|
| 26 |
-
the DB first — never invent facts not present in KNOWN DATA or the conversation.
|
| 27 |
|
| 28 |
OUTPUT FORMAT — return ONLY this JSON, no code fences, no explanation:
|
| 29 |
{"action_type": "respond", "message": "..."} or {"action_type": "escalate", "reason": "..."}`;
|
|
|
|
| 19 |
- "query_user_profile" → look up customer DB (internal) → requires: "email"
|
| 20 |
- "query_order_details" → look up order DB (internal) → requires: "order_id"
|
| 21 |
|
| 22 |
+
CONVERSATION FLOW — MANDATORY ORDER:
|
| 23 |
+
1. FIRST action MUST be "respond": greet the customer warmly and acknowledge their specific issue.
|
| 24 |
+
2. THEN use "request_info" if you still need details before you can help.
|
| 25 |
+
3. ONLY THEN query the DB — but only when the customer has confirmed the email or order ID
|
| 26 |
+
in this conversation AND you need that data to answer accurately.
|
| 27 |
+
4. Respond with grounded facts, then "close" or "escalate".
|
| 28 |
+
|
| 29 |
+
CRITICAL DB QUERY RULES:
|
| 30 |
+
- NEVER query the DB as your very first action, even if the ticket already shows an email or order ID.
|
| 31 |
+
Always greet and acknowledge first. Querying without greeting feels robotic and is penalized.
|
| 32 |
+
- After querying, cite ONLY values from KNOWN DATA or the customer's own messages — never invent facts.
|
| 33 |
+
- If supervisor gave feedback, INCORPORATE it into your next action.
|
| 34 |
+
|
| 35 |
SCORING: Empathy(30%) + Accuracy(25%) + Resolution(25%) + Efficiency(20%)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
OUTPUT FORMAT — return ONLY this JSON, no code fences, no explanation:
|
| 38 |
{"action_type": "respond", "message": "..."} or {"action_type": "escalate", "reason": "..."}`;
|
|
@@ -27,6 +27,8 @@ INFERENCE_MODEL = (
|
|
| 27 |
or "unsloth/Qwen2.5-1.5B-Instruct"
|
| 28 |
)
|
| 29 |
ENV_URL = os.getenv("ENV_URL", "http://localhost:7860")
|
|
|
|
|
|
|
| 30 |
BENCHMARK = "customer-support-env"
|
| 31 |
TASKS = ["easy", "medium", "hard"]
|
| 32 |
HIERARCHY_TASKS = ["hierarchy_easy", "hierarchy_medium", "hierarchy_hard"]
|
|
@@ -158,6 +160,20 @@ ACTION TYPES — output exactly one per step:
|
|
| 158 |
- "query_user_profile" -> look up customer account (internal) -> requires: "email"
|
| 159 |
- "query_order_details" -> look up order details (internal) -> requires: "order_id"
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
OUTPUT FORMAT — return ONLY this JSON, no code fences, no preamble:
|
| 162 |
{"action_type": "...", "message": "..."} <- for respond / request_info / close
|
| 163 |
{"action_type": "escalate", "reason": "..."} <- for escalate
|
|
@@ -190,9 +206,22 @@ ACTION TYPES — output exactly one per step:
|
|
| 190 |
- "query_user_profile" -> look up customer account (internal) -> requires: "email"
|
| 191 |
- "query_order_details" -> look up order details (internal) -> requires: "order_id"
|
| 192 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
SCORING: Empathy(30%) + Accuracy(25%) + Resolution(25%) + Efficiency(20%)
|
| 194 |
-
Be warm, gather info from "Unresolved issues", use specific resolution language.
|
| 195 |
-
If supervisor gave feedback, INCORPORATE it into your next action.
|
| 196 |
|
| 197 |
OUTPUT FORMAT — return ONLY this JSON:
|
| 198 |
{{"action_type": "...", "message": "..."}} or {{"action_type": "escalate", "reason": "..."}}"""
|
|
@@ -337,7 +366,7 @@ _FALLBACKS = {
|
|
| 337 |
|
| 338 |
def run_task(task_name: str) -> None:
|
| 339 |
try:
|
| 340 |
-
r = httpx.post(f"{ENV_URL}/reset", params={"task": task_name}, timeout=30)
|
| 341 |
r.raise_for_status()
|
| 342 |
except Exception as exc:
|
| 343 |
print(f"[ERROR] Reset failed task={task_name}: {exc}", file=sys.stderr)
|
|
@@ -359,7 +388,7 @@ def run_task(task_name: str) -> None:
|
|
| 359 |
action = _FALLBACKS["support_agent"]
|
| 360 |
|
| 361 |
try:
|
| 362 |
-
sr = httpx.post(f"{ENV_URL}/step", params={"session_id": session_id}, json=action, timeout=30)
|
| 363 |
sr.raise_for_status()
|
| 364 |
result = sr.json()
|
| 365 |
except Exception as exc:
|
|
@@ -379,7 +408,7 @@ def run_task(task_name: str) -> None:
|
|
| 379 |
|
| 380 |
def run_hierarchy_task(task_name: str) -> None:
|
| 381 |
try:
|
| 382 |
-
r = httpx.post(f"{ENV_URL}/reset", params={"task": task_name}, timeout=30)
|
| 383 |
r.raise_for_status()
|
| 384 |
except Exception as exc:
|
| 385 |
print(f"[ERROR] Reset failed task={task_name}: {exc}", file=sys.stderr)
|
|
@@ -403,7 +432,7 @@ def run_hierarchy_task(task_name: str) -> None:
|
|
| 403 |
action = _FALLBACKS.get(role, _FALLBACKS["support_agent"])
|
| 404 |
|
| 405 |
try:
|
| 406 |
-
sr = httpx.post(f"{ENV_URL}/step", params={"session_id": session_id}, json=action, timeout=60)
|
| 407 |
sr.raise_for_status()
|
| 408 |
result = sr.json()
|
| 409 |
except Exception as exc:
|
|
|
|
| 27 |
or "unsloth/Qwen2.5-1.5B-Instruct"
|
| 28 |
)
|
| 29 |
ENV_URL = os.getenv("ENV_URL", "http://localhost:7860")
|
| 30 |
+
ENV_API_KEY = os.getenv("ENV_API_KEY", os.getenv("API_KEY", "meta_hack_2026"))
|
| 31 |
+
_ENV_HEADERS = {"X-API-Key": ENV_API_KEY}
|
| 32 |
BENCHMARK = "customer-support-env"
|
| 33 |
TASKS = ["easy", "medium", "hard"]
|
| 34 |
HIERARCHY_TASKS = ["hierarchy_easy", "hierarchy_medium", "hierarchy_hard"]
|
|
|
|
| 160 |
- "query_user_profile" -> look up customer account (internal) -> requires: "email"
|
| 161 |
- "query_order_details" -> look up order details (internal) -> requires: "order_id"
|
| 162 |
|
| 163 |
+
CONVERSATION FLOW — MANDATORY ORDER:
|
| 164 |
+
1. FIRST action MUST be "respond": greet the customer warmly and acknowledge their specific issue.
|
| 165 |
+
2. THEN use "request_info" if you still need information to resolve the issue.
|
| 166 |
+
3. ONLY THEN use "query_user_profile" or "query_order_details" — and only when the customer has
|
| 167 |
+
explicitly confirmed or provided the email / order ID during the conversation AND you need that
|
| 168 |
+
data to answer their question.
|
| 169 |
+
4. Finally "respond" with grounded facts, then "close" or "escalate".
|
| 170 |
+
|
| 171 |
+
CRITICAL DB QUERY RULES:
|
| 172 |
+
- NEVER query the DB as your first action, even if the ticket already shows an email or order ID.
|
| 173 |
+
Always greet and acknowledge first — querying without greeting is robotic and is penalized.
|
| 174 |
+
- Only query AFTER the customer has confirmed the identifier in the current conversation thread.
|
| 175 |
+
- Never invent data not present in KNOWN DATA or the customer's own messages.
|
| 176 |
+
|
| 177 |
OUTPUT FORMAT — return ONLY this JSON, no code fences, no preamble:
|
| 178 |
{"action_type": "...", "message": "..."} <- for respond / request_info / close
|
| 179 |
{"action_type": "escalate", "reason": "..."} <- for escalate
|
|
|
|
| 206 |
- "query_user_profile" -> look up customer account (internal) -> requires: "email"
|
| 207 |
- "query_order_details" -> look up order details (internal) -> requires: "order_id"
|
| 208 |
|
| 209 |
+
CONVERSATION FLOW — MANDATORY ORDER:
|
| 210 |
+
1. FIRST action MUST be "respond": greet the customer warmly by name if possible, acknowledge their issue.
|
| 211 |
+
2. THEN use "request_info" if you still need details to help them.
|
| 212 |
+
3. ONLY THEN query the DB — but ONLY when the customer has explicitly confirmed the email or order ID
|
| 213 |
+
during this conversation AND you need that data to answer accurately.
|
| 214 |
+
4. Respond with the grounded facts, then "close" or "escalate" as appropriate.
|
| 215 |
+
|
| 216 |
+
CRITICAL DB QUERY RULES:
|
| 217 |
+
- NEVER issue a DB query as your very first action — always acknowledge the customer first.
|
| 218 |
+
The ticket description may mention an email or order ID, but greet first. Skipping the greeting
|
| 219 |
+
is robotic, hurts empathy scores, and incurs a penalty.
|
| 220 |
+
- After querying, cite ONLY values from KNOWN DATA or from the customer's own messages.
|
| 221 |
+
Never invent facts (amounts, dates, status) not present in retrieved data.
|
| 222 |
+
- If supervisor gave feedback, INCORPORATE it into your next action.
|
| 223 |
+
|
| 224 |
SCORING: Empathy(30%) + Accuracy(25%) + Resolution(25%) + Efficiency(20%)
|
|
|
|
|
|
|
| 225 |
|
| 226 |
OUTPUT FORMAT — return ONLY this JSON:
|
| 227 |
{{"action_type": "...", "message": "..."}} or {{"action_type": "escalate", "reason": "..."}}"""
|
|
|
|
| 366 |
|
| 367 |
def run_task(task_name: str) -> None:
|
| 368 |
try:
|
| 369 |
+
r = httpx.post(f"{ENV_URL}/reset", params={"task": task_name}, headers=_ENV_HEADERS, timeout=30)
|
| 370 |
r.raise_for_status()
|
| 371 |
except Exception as exc:
|
| 372 |
print(f"[ERROR] Reset failed task={task_name}: {exc}", file=sys.stderr)
|
|
|
|
| 388 |
action = _FALLBACKS["support_agent"]
|
| 389 |
|
| 390 |
try:
|
| 391 |
+
sr = httpx.post(f"{ENV_URL}/step", params={"session_id": session_id}, json=action, headers=_ENV_HEADERS, timeout=30)
|
| 392 |
sr.raise_for_status()
|
| 393 |
result = sr.json()
|
| 394 |
except Exception as exc:
|
|
|
|
| 408 |
|
| 409 |
def run_hierarchy_task(task_name: str) -> None:
|
| 410 |
try:
|
| 411 |
+
r = httpx.post(f"{ENV_URL}/reset", params={"task": task_name}, headers=_ENV_HEADERS, timeout=30)
|
| 412 |
r.raise_for_status()
|
| 413 |
except Exception as exc:
|
| 414 |
print(f"[ERROR] Reset failed task={task_name}: {exc}", file=sys.stderr)
|
|
|
|
| 432 |
action = _FALLBACKS.get(role, _FALLBACKS["support_agent"])
|
| 433 |
|
| 434 |
try:
|
| 435 |
+
sr = httpx.post(f"{ENV_URL}/step", params={"session_id": session_id}, json=action, headers=_ENV_HEADERS, timeout=60)
|
| 436 |
sr.raise_for_status()
|
| 437 |
result = sr.json()
|
| 438 |
except Exception as exc:
|