updates on the ai agents and time series prediction and updates on the api to
This commit is contained in:
@@ -21,6 +21,7 @@ func CreateAgentDecision(c *fiber.Ctx) error {
|
||||
Context map[string]interface{} `json:"context"`
|
||||
Decision map[string]interface{} `json:"decision"`
|
||||
Reasoning string `json:"reasoning"`
|
||||
TenantID *uint64 `json:"tenant_id"`
|
||||
ContextEmbedding []float32 `json:"context_embedding"`
|
||||
}
|
||||
|
||||
@@ -44,6 +45,7 @@ func CreateAgentDecision(c *fiber.Ctx) error {
|
||||
record := models.AgentDecision{
|
||||
DecisionType: body.DecisionType,
|
||||
BookingID: body.BookingID,
|
||||
TenantID: body.TenantID,
|
||||
Context: string(contextJSON),
|
||||
Decision: string(decisionJSON),
|
||||
Reasoning: body.Reasoning,
|
||||
@@ -73,6 +75,10 @@ func CreateAgentDecision(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// GET /api/v1/internal/agent-decisions/similar
|
||||
// POST /api/v1/internal/agent-decisions/similar
|
||||
//
|
||||
// POST rather than GET because the body carries a 1536-float embedding, and a
|
||||
// GET body does not survive nginx — which fronts this API today.
|
||||
func FindSimilarDecisions(c *fiber.Ctx) error {
|
||||
decisionType := c.Query("decision_type")
|
||||
limit, err := strconv.Atoi(c.Query("limit", "5"))
|
||||
@@ -82,6 +88,11 @@ func FindSimilarDecisions(c *fiber.Ctx) error {
|
||||
|
||||
type req struct {
|
||||
Embedding []float32 `json:"embedding"`
|
||||
// TenantID scopes the search. Omitted or null means "decisions with no
|
||||
// tenant" — NOT "every tenant". Precedent must never cross tenants:
|
||||
// the engine feeds these rows to the model, so one client's history
|
||||
// would otherwise shape another client's dispatch.
|
||||
TenantID *uint64 `json:"tenant_id"`
|
||||
}
|
||||
body := new(req)
|
||||
if err := c.BodyParser(body); err != nil || len(body.Embedding) == 0 {
|
||||
@@ -102,14 +113,24 @@ func FindSimilarDecisions(c *fiber.Ctx) error {
|
||||
}
|
||||
var results []row
|
||||
|
||||
// outcome IS NOT NULL is the point of the table: an unresolved decision is
|
||||
// not precedent, it is just a past guess. The outcome sweeper
|
||||
// (internal/ai/outcomes) is what makes rows eligible here.
|
||||
//
|
||||
// The tenant predicate uses IS NOT DISTINCT FROM so a NULL tenant matches
|
||||
// only NULL — plain `= ?` would match nothing at all for B2C decisions and
|
||||
// silently return an empty recall forever.
|
||||
if err := db.DB.Raw(
|
||||
`SELECT decision, reasoning, outcome,
|
||||
context_embedding <=> ? AS distance
|
||||
FROM agent_decisions
|
||||
WHERE decision_type = ? AND outcome IS NOT NULL
|
||||
WHERE decision_type = ?
|
||||
AND outcome IS NOT NULL
|
||||
AND context_embedding IS NOT NULL
|
||||
AND tenantid IS NOT DISTINCT FROM ?
|
||||
ORDER BY context_embedding <=> ?
|
||||
LIMIT ?`,
|
||||
embeddingStr, decisionType, embeddingStr, limit,
|
||||
embeddingStr, decisionType, body.TenantID, embeddingStr, limit,
|
||||
).Scan(&results).Error; err != nil {
|
||||
utils.Error("FindSimilarDecisions: query failed", "error", err)
|
||||
return utils.Internal(c, "failed to query similar decisions")
|
||||
|
||||
Reference in New Issue
Block a user