deploy
This commit is contained in:
41
agent.py
41
agent.py
@@ -58,7 +58,11 @@ LEARNHOUSE_CONTEXT = (
|
||||
"After writing course content, verify every chapter has at least one "
|
||||
"activity through course metadata or the chapter activity endpoint. When "
|
||||
"updating activities, do not send published_version; this LearnHouse "
|
||||
"backend currently rejects that field."
|
||||
"backend currently rejects that field. Do not use GET /api/v1/health as "
|
||||
"a preflight or blocker for API-token workflows; that endpoint rejects API "
|
||||
"tokens with 403. For smoke/status checks, use the read-only courses count "
|
||||
"or course list endpoint with org_slug 'default', then proceed to the "
|
||||
"requested courses, chapters, or activities endpoint."
|
||||
)
|
||||
|
||||
|
||||
@@ -73,7 +77,7 @@ class OperationInput(BaseModel):
|
||||
class LearnhouseAgent(A2AAgent):
|
||||
name = "learnhouse-agent"
|
||||
description = "LearnHouse is an open-source platform tailored for learning experiences."
|
||||
version = "1.2.11"
|
||||
version = "1.2.12"
|
||||
consumer_setup = ConsumerSetup.from_fields(
|
||||
ConsumerSetupField.config("OPENAPI_BASE_URL", label="API base URL", description="Override the default API server (https://learnhouse.a2acloud.io).", required=False, input_type="url"),
|
||||
ConsumerSetupField.secret(
|
||||
@@ -224,7 +228,7 @@ class LearnhouseAgent(A2AAgent):
|
||||
LEARNHOUSE_CONTEXT,
|
||||
"Delegate API work to the matching subagent with the task tool.",
|
||||
"Do not invent API responses; ask subagents to call their tools for real results.",
|
||||
"For smoke tests, health checks, status checks, or ping-style requests, prefer explicit health/status/ping route groups over a generic root '/' route group.",
|
||||
"Do not call GET /api/v1/health as a preflight; it rejects API tokens. For LearnHouse smoke/status checks, use the read-only courses count or courses list endpoint for org_slug 'default'.",
|
||||
"If a subagent reports missing consumer setup, tell the user which setup field is required.",
|
||||
"If a subagent reports that the generated OpenAPI agent may be stale, ask whether the user wants to refresh it from the latest OpenAPI spec.",
|
||||
"",
|
||||
@@ -239,7 +243,7 @@ class LearnhouseAgent(A2AAgent):
|
||||
"You operate an API through generated OpenAPI tools.",
|
||||
LEARNHOUSE_CONTEXT,
|
||||
"Call tools to get real results. Do not invent API responses.",
|
||||
"For smoke tests, health checks, status checks, or ping-style requests, prefer explicit health/status/ping GET operations over generic root '/' operations.",
|
||||
"Do not call GET /api/v1/health as a preflight; it rejects API tokens. For LearnHouse smoke/status checks, use the read-only courses count or courses list endpoint for org_slug 'default'.",
|
||||
"Use a root '/' operation only when the user asks for the service root/homepage or no more specific read-only operation fits.",
|
||||
"For write, update, or delete operations, explain the intended action before calling the tool.",
|
||||
"If an operation reports missing consumer setup, tell the user which setup field is required.",
|
||||
@@ -260,7 +264,7 @@ class LearnhouseAgent(A2AAgent):
|
||||
LEARNHOUSE_CONTEXT,
|
||||
"Use the available operation tools to make real API calls. Do not invent API responses.",
|
||||
"Read the route group's SKILL.md when it applies; it lists every route you cover.",
|
||||
"For smoke tests, health checks, status checks, or ping-style requests, prefer explicit health/status/ping GET operations over generic root '/' operations.",
|
||||
"Do not call GET /api/v1/health as a preflight; it rejects API tokens. For LearnHouse smoke/status checks, use the read-only courses count or courses list endpoint for org_slug 'default'.",
|
||||
"Use a root '/' operation only when the user asks for the service root/homepage or no more specific read-only operation fits.",
|
||||
"For write, update, or delete operations, explain the intended action before calling the tool.",
|
||||
"If an operation reports missing consumer setup, return the exact setup field name.",
|
||||
@@ -571,16 +575,33 @@ def _select_smoke_operation(goal: str) -> str | None:
|
||||
smoke_terms = ("smoke", "health", "status", "ping", "readiness", "liveness")
|
||||
if not any(term in text for term in smoke_terms):
|
||||
return None
|
||||
preferred_terms = ("health", "status", "ping", "ready", "readiness", "live", "liveness")
|
||||
|
||||
# LearnHouse's health endpoint rejects API tokens with 403. Use a harmless
|
||||
# read-only domain endpoint instead so smoke checks do not block real work.
|
||||
preferred_operation_ids = (
|
||||
"api_get_courses_count_api_v1_courses_org_slug_org_slug_count_get",
|
||||
"api_get_course_by_orgslug_api_v1_courses_org_slug_org_slug_page_page_limit_limit",
|
||||
"api_get_org_by_slug_api_v1_orgs_slug_org_slug_get",
|
||||
)
|
||||
for operation_id in preferred_operation_ids:
|
||||
if operation_id in OPERATIONS:
|
||||
return operation_id
|
||||
|
||||
preferred_terms = ("courses", "course", "org", "organization", "status", "ping", "ready", "readiness")
|
||||
candidates: list[tuple[int, int, str]] = []
|
||||
for operation_id, operation in OPERATIONS.items():
|
||||
if str(operation.get("method") or "").upper() != "GET":
|
||||
continue
|
||||
if operation.get("destructive"):
|
||||
continue
|
||||
if any(param.get("required") for param in operation.get("parameters") or []):
|
||||
continue
|
||||
path = str(operation.get("path") or "")
|
||||
if _is_learnhouse_health_operation(operation):
|
||||
continue
|
||||
if any(
|
||||
param.get("required") and str(param.get("name")) not in {"org_slug", "org_id", "page", "limit"}
|
||||
for param in operation.get("parameters") or []
|
||||
):
|
||||
continue
|
||||
searchable = " ".join(
|
||||
str(operation.get(key) or "")
|
||||
for key in ("operation_id", "skill_name", "path", "summary", "description")
|
||||
@@ -604,6 +625,10 @@ def _select_smoke_operation(goal: str) -> str | None:
|
||||
return min(candidates)[2]
|
||||
|
||||
|
||||
def _is_learnhouse_health_operation(operation: dict[str, Any]) -> bool:
|
||||
return str(operation.get("path") or "").rstrip("/") == "/api/v1/health"
|
||||
|
||||
|
||||
def _body_preview(body: Any, limit: int) -> str:
|
||||
if isinstance(body, str):
|
||||
text = body
|
||||
|
||||
Reference in New Issue
Block a user