add the greeting msg
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/krow/krow-backend/go-api/internal/authctx"
|
||||
"github.com/krow/krow-backend/go-api/internal/domain"
|
||||
"github.com/krow/krow-backend/go-api/internal/gateway"
|
||||
"github.com/krow/krow-backend/go-api/internal/runtime"
|
||||
"github.com/krow/krow-backend/go-api/internal/tools"
|
||||
)
|
||||
@@ -163,6 +164,7 @@ func (s *Server) handleAgentRun(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
s.logUnsaved(ident, res)
|
||||
s.logGatewayFailure(ident, res)
|
||||
|
||||
writeJSON(w, http.StatusOK, buildRunResponse(res))
|
||||
}
|
||||
@@ -180,6 +182,43 @@ func (s *Server) logUnsaved(ident authctx.Identity, res *runtime.ExecutionResult
|
||||
}
|
||||
}
|
||||
|
||||
// logGatewayFailure is the operator's record of the model provider not
|
||||
// answering, and it exists because the reader of the message cannot report what
|
||||
// the message does not say.
|
||||
//
|
||||
// Four of the five gateway faults need an administrator and will fail
|
||||
// identically on every retry — a rejected credential, a model id this
|
||||
// deployment cannot use, no key at all, and a 4xx from the endpoint. The person
|
||||
// in the chat panel is told, correctly, that retrying will not help; but
|
||||
// nothing until now told the side that CAN fix it. A deployment failing every
|
||||
// run produced a stream of 200s and no error line, so the only record of which
|
||||
// fault it was lived in a trajectory somebody had to know to go and read.
|
||||
//
|
||||
// Logged at Error because that is what it is: on a rate limit it is a capacity
|
||||
// decision worth seeing, and on the other four it is an outage. It carries the
|
||||
// gateway's code and status and NOT the message — §10 keeps model and document
|
||||
// text out of the log store, and the code is the part that is actionable
|
||||
// anyway.
|
||||
func (s *Server) logGatewayFailure(ident authctx.Identity, res *runtime.ExecutionResult) {
|
||||
if res.Termination != runtime.TerminationGatewayFailure {
|
||||
return
|
||||
}
|
||||
|
||||
// Empty rather than invented when the cause did not survive: "which fault
|
||||
// was it" is the whole point of this line, and a guessed answer to it is
|
||||
// worse than a visible gap.
|
||||
code, status := "", 0
|
||||
var gwErr *gateway.Error
|
||||
if errors.As(res.Error, &gwErr) {
|
||||
code, status = gwErr.Code, gwErr.Status
|
||||
}
|
||||
|
||||
s.log.Error("gateway failure",
|
||||
"run_id", res.RunID, "tenant_id", ident.OrgID,
|
||||
"agent_key", res.AgentID, "agent_version", res.AgentVersion,
|
||||
"gateway_code", code, "gateway_status", status)
|
||||
}
|
||||
|
||||
// buildRunResponse turns a runtime result into the client's shape.
|
||||
//
|
||||
// Every termination answers 200. That looks wrong at first and is not: the
|
||||
@@ -206,7 +245,7 @@ func buildRunResponse(res *runtime.ExecutionResult) runResponse {
|
||||
},
|
||||
}
|
||||
if res.Termination != runtime.TerminationCompleted {
|
||||
out.Message = terminationMessage(res.Termination)
|
||||
out.Message = terminationMessage(res.Termination, res.Error)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -222,7 +261,9 @@ func buildRunResponse(res *runtime.ExecutionResult) runResponse {
|
||||
// Every one of the seven is spelled out. A default that said "something went
|
||||
// wrong" would be the place where a Refused run and a ToolFailure became
|
||||
// indistinguishable to the person best placed to tell us which it was.
|
||||
func terminationMessage(t runtime.Termination) string {
|
||||
// `cause` is the run's own error, carried so GatewayFailure can say which
|
||||
// gateway failure it was. Every other termination ignores it.
|
||||
func terminationMessage(t runtime.Termination, cause error) string {
|
||||
switch t {
|
||||
case runtime.TerminationCompleted:
|
||||
return ""
|
||||
@@ -239,16 +280,70 @@ func terminationMessage(t runtime.Termination) string {
|
||||
case runtime.TerminationRefused:
|
||||
return "The agent declined to answer this one."
|
||||
case runtime.TerminationGatewayFailure:
|
||||
// The one termination where "try again" is honest advice: the
|
||||
// dominant cause is a rate limit that clears within a minute, and
|
||||
// nothing about the question itself was the problem.
|
||||
return "The model behind this agent did not answer — usually it is busy. " +
|
||||
"Wait a minute and ask again. Nothing was changed."
|
||||
return gatewayFailureMessage(cause)
|
||||
default:
|
||||
return "The agent did not finish."
|
||||
}
|
||||
}
|
||||
|
||||
// gatewayFailureMessage tells a GatewayFailure apart from the four others it
|
||||
// used to be indistinguishable from.
|
||||
//
|
||||
// GatewayFailure is everything the gateway can raise except a refusal and a
|
||||
// timeout, which have terminations of their own. That is five different faults,
|
||||
// and the one sentence they all produced was "usually it is busy — wait a
|
||||
// minute and ask again".
|
||||
//
|
||||
// For a rate limit that is true and useful. For the other three it is advice
|
||||
// that CANNOT work: a credential the provider rejected, a model id that is not
|
||||
// on the configured endpoint, and no key at all will each fail identically on
|
||||
// every retry, forever. Telling somebody to wait a minute for a misconfigured
|
||||
// deployment sends them round a loop with no exit, and hides an operator
|
||||
// problem behind what reads as a transient one — the reader retries instead of
|
||||
// reporting it, so nobody with access to fix it ever hears.
|
||||
//
|
||||
// So each says what it is, and only the two that clear on their own invite a
|
||||
// retry. The wording stays free of vendor names and status codes: the person
|
||||
// reading it cannot act on "429 from the model endpoint", and the code is in
|
||||
// the trajectory for the person who can.
|
||||
func gatewayFailureMessage(cause error) string {
|
||||
var gwErr *gateway.Error
|
||||
if !errors.As(cause, &gwErr) {
|
||||
// No gateway error to read — either the cause was lost or something
|
||||
// non-gateway reached this termination. The old sentence is still the
|
||||
// best guess, so it is what an unknown falls back to.
|
||||
return "The model behind this agent did not answer — usually it is busy. " +
|
||||
"Wait a minute and ask again. Nothing was changed."
|
||||
}
|
||||
|
||||
switch gwErr.Code {
|
||||
case gateway.CodeRateLimited:
|
||||
return "The model behind this agent is busy right now. " +
|
||||
"Wait a minute and ask again. Nothing was changed."
|
||||
case gateway.CodeUpstream:
|
||||
if gwErr.Status >= 500 {
|
||||
return "The model behind this agent is having trouble. " +
|
||||
"Try again in a few minutes. Nothing was changed."
|
||||
}
|
||||
return "The model behind this agent could not be reached, and retrying is " +
|
||||
"unlikely to help. This needs an administrator. Nothing was changed."
|
||||
case gateway.CodeUnauthorized:
|
||||
return "This deployment's model credentials were rejected, so the agent " +
|
||||
"cannot answer. Retrying will not help — it needs an administrator. " +
|
||||
"Nothing was changed."
|
||||
case gateway.CodeInvalidRequest:
|
||||
return "The agent is pointed at a model this deployment cannot use, so it " +
|
||||
"cannot answer. Retrying will not help — it needs an administrator. " +
|
||||
"Nothing was changed."
|
||||
case gateway.CodeNotConfigured:
|
||||
return "No model is configured for this deployment, so the agent cannot " +
|
||||
"answer. It needs an administrator. Nothing was changed."
|
||||
default:
|
||||
return "The model behind this agent did not answer — usually it is busy. " +
|
||||
"Wait a minute and ask again. Nothing was changed."
|
||||
}
|
||||
}
|
||||
|
||||
// runLoadError maps a pre-run failure onto the API's error vocabulary.
|
||||
//
|
||||
// These are the errors from LoadExecutableAgent, raised before any run began —
|
||||
@@ -351,6 +446,7 @@ func (s *Server) streamAgentRun(w http.ResponseWriter, r *http.Request, ident au
|
||||
return
|
||||
}
|
||||
s.logUnsaved(ident, res)
|
||||
s.logGatewayFailure(ident, res)
|
||||
writeJSON(w, http.StatusOK, buildRunResponse(res))
|
||||
return
|
||||
}
|
||||
@@ -399,6 +495,7 @@ func (s *Server) streamAgentRun(w http.ResponseWriter, r *http.Request, ident au
|
||||
}
|
||||
|
||||
s.logUnsaved(ident, res)
|
||||
s.logGatewayFailure(ident, res)
|
||||
send(map[string]any{"run": buildRunResponse(res)})
|
||||
fmt.Fprint(w, "data: [DONE]\n\n")
|
||||
flusher.Flush()
|
||||
|
||||
Reference in New Issue
Block a user