updates on the ai agents and time series prediction and updates on the api to

This commit is contained in:
2026-10-09 13:50:30 +05:30
parent 29690c56f2
commit 153be40e5c
42 changed files with 4889 additions and 186 deletions

View File

@@ -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")