Changeset 0.26.0 (#165)
This commit is contained in:
@@ -161,7 +161,7 @@ func (h *CompletionHandler) evaluateRouting(
|
||||
Model: model,
|
||||
ConfigID: configID,
|
||||
HealthStatus: healthStatus,
|
||||
Pricing: map[string]*models.ModelPricing{}, // TODO: populate from pricing store
|
||||
Pricing: map[string]*models.ModelPricing{}, // populated when cost_limit routing is enabled (deferred)
|
||||
}
|
||||
|
||||
ranked, dec := h.router.Evaluate(routingCtx, policies, candidates)
|
||||
@@ -457,6 +457,18 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ── Workflow form template injection (v0.26.4) ──
|
||||
// When the channel is a workflow instance, inject the current stage's
|
||||
// form_template as a system message so the persona knows what to collect.
|
||||
if channelType == "workflow" {
|
||||
if hint := h.buildWorkflowFormHint(c.Request.Context(), channelID); hint != "" {
|
||||
messages = append(messages[:1], append([]providers.Message{{
|
||||
Role: "system",
|
||||
Content: hint,
|
||||
}}, messages[1:]...)...)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve capabilities early — needed for vision gating below
|
||||
caps := h.getModelCapabilities(c, model, configID)
|
||||
|
||||
@@ -709,9 +721,9 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
}
|
||||
|
||||
if stream {
|
||||
h.streamCompletion(c, provider, providerCfg, provReq, channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID)
|
||||
h.streamCompletion(c, provider, providerCfg, provReq, channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID, teamID)
|
||||
} else {
|
||||
h.syncCompletion(c, provider, providerCfg, provReq, channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID)
|
||||
h.syncCompletion(c, provider, providerCfg, provReq, channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID, teamID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,7 +736,7 @@ func (h *CompletionHandler) multiModelStream(
|
||||
c *gin.Context,
|
||||
targets []models.ChannelModel,
|
||||
messages []providers.Message,
|
||||
channelID, userID, personaID, personaSystemPrompt, workspaceID string,
|
||||
channelID, userID, personaID, personaSystemPrompt, workspaceID, teamID string,
|
||||
req completionRequest,
|
||||
) {
|
||||
// Set SSE headers once for the entire multi-model stream
|
||||
@@ -821,7 +833,7 @@ func (h *CompletionHandler) multiModelStream(
|
||||
}
|
||||
|
||||
// Stream this model's response using the shared streaming core
|
||||
result := streamModelResponse(c, provider, providerCfg, &provReq, model, providerID, target.DisplayName, userID, channelID, personaID, workspaceID, configID, h.hub, h.health)
|
||||
result := streamModelResponse(c, provider, providerCfg, &provReq, model, providerID, target.DisplayName, userID, channelID, personaID, workspaceID, configID, teamID, h.hub, h.health)
|
||||
|
||||
// Persist assistant message with model attribution
|
||||
if result.Content != "" {
|
||||
@@ -1012,14 +1024,14 @@ func (h *CompletionHandler) streamCompletion(
|
||||
provider providers.Provider,
|
||||
cfg providers.ProviderConfig,
|
||||
req providers.CompletionRequest,
|
||||
channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID string,
|
||||
channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID, teamID string,
|
||||
) {
|
||||
// Apply provider-specific request hooks (v0.22.1)
|
||||
if hooks := providers.GetHooks(providerID); hooks != nil {
|
||||
hooks.PreRequest(cfg, &req)
|
||||
}
|
||||
|
||||
result := streamWithToolLoop(c, provider, cfg, &req, model, providerID, userID, channelID, personaID, workspaceID, configID, h.hub, h.health)
|
||||
result := streamWithToolLoop(c, provider, cfg, &req, model, providerID, userID, channelID, personaID, workspaceID, configID, teamID, h.hub, h.health)
|
||||
|
||||
// Persist assistant response
|
||||
if result.Content != "" {
|
||||
@@ -1052,7 +1064,7 @@ func (h *CompletionHandler) syncCompletion(
|
||||
provider providers.Provider,
|
||||
cfg providers.ProviderConfig,
|
||||
req providers.CompletionRequest,
|
||||
channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID string,
|
||||
channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID, teamID string,
|
||||
) {
|
||||
// Apply provider-specific request hooks (v0.22.1)
|
||||
if hooks := providers.GetHooks(providerID); hooks != nil {
|
||||
@@ -1137,6 +1149,7 @@ func (h *CompletionHandler) syncCompletion(
|
||||
ChannelID: channelID,
|
||||
PersonaID: personaID,
|
||||
WorkspaceID: workspaceID,
|
||||
TeamID: teamID,
|
||||
}
|
||||
for _, tc := range resp.ToolCalls {
|
||||
call := tools.ToolCall{
|
||||
@@ -1335,6 +1348,71 @@ func (h *CompletionHandler) conversationHasPersonaMessages(ctx context.Context,
|
||||
return err == nil && id != ""
|
||||
}
|
||||
|
||||
// buildWorkflowFormHint loads the current workflow stage's form_template
|
||||
// and returns a system message instructing the persona what to collect.
|
||||
// Returns empty string if not a workflow or no template defined.
|
||||
func (h *CompletionHandler) buildWorkflowFormHint(ctx context.Context, channelID string) string {
|
||||
var workflowID *string
|
||||
var currentStage int
|
||||
_ = database.DB.QueryRowContext(ctx, database.Q(`
|
||||
SELECT workflow_id, COALESCE(current_stage, 0)
|
||||
FROM channels WHERE id = $1 AND type = 'workflow'
|
||||
`), channelID).Scan(&workflowID, ¤tStage)
|
||||
|
||||
if workflowID == nil || *workflowID == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if h.stores.Workflows == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
stages, err := h.stores.Workflows.ListStages(ctx, *workflowID)
|
||||
if err != nil || currentStage >= len(stages) {
|
||||
return ""
|
||||
}
|
||||
|
||||
stage := stages[currentStage]
|
||||
if len(stage.FormTemplate) == 0 || string(stage.FormTemplate) == "{}" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Parse form template to extract field descriptions
|
||||
var fields map[string]interface{}
|
||||
if err := json.Unmarshal(stage.FormTemplate, &fields); err != nil {
|
||||
return ""
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
hint := "You are in workflow stage: " + stage.Name + ".\n\n"
|
||||
hint += "Collect the following information from the user through natural conversation:\n\n"
|
||||
for name, spec := range fields {
|
||||
switch v := spec.(type) {
|
||||
case map[string]interface{}:
|
||||
desc, _ := v["description"].(string)
|
||||
required, _ := v["required"].(bool)
|
||||
if desc != "" {
|
||||
hint += "- " + name + ": " + desc
|
||||
} else {
|
||||
hint += "- " + name
|
||||
}
|
||||
if required {
|
||||
hint += " (required)"
|
||||
}
|
||||
hint += "\n"
|
||||
default:
|
||||
hint += "- " + name + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
hint += "\nWhen you have gathered all required information, call the workflow_advance tool " +
|
||||
"with the collected data as a JSON object. Do not advance until all required fields are filled."
|
||||
|
||||
return hint
|
||||
}
|
||||
|
||||
// buildParticipantHint builds a system message listing available @mentionable
|
||||
// personas so the LLM knows who it can direct responses to.
|
||||
// Only includes personas accessible to this user. Excludes the current persona.
|
||||
|
||||
Reference in New Issue
Block a user