fix: workflow inference chain — surface llama-server errors, user-turn-last, bundle-relative prompts
- LlamaCppInferenceProvider: check HTTP status and surface the real error body instead of masking it as a ChatCompletionResponse deserialization failure - PromptRenderer: render the live conversation layer (L1) last so the user turn is the final message (fixes Qwen 'No user query found' 500 when L3 recall is present) - TomlWorkflowLoader: resolve stage prompts relative to the workflow file's own dir (bundle), CWD-independent; config-dir fallback for globals - SessionOrchestrator: hard-fail a stage whose declared prompt can't resolve (was a silent user-less request) - move healthcheck prompts next to the example workflow (examples/workflows/prompts/)
This commit is contained in:
@@ -12,6 +12,14 @@ data class ChatMessage(
|
||||
)
|
||||
|
||||
object PromptRenderer {
|
||||
// L1 (live conversation, ends with the current user turn) must render last so
|
||||
// the model template sees the user query as the final message. Background/memory
|
||||
// layers (L2, L3, L4) are injected before it.
|
||||
private val nonL0RenderOrder: Comparator<ContextLayer> = Comparator { a, b ->
|
||||
val priority = { layer: ContextLayer -> if (layer == ContextLayer.L1) Int.MAX_VALUE else layer.ordinal }
|
||||
priority(a) - priority(b)
|
||||
}
|
||||
|
||||
fun render(contextPack: ContextPack): List<ChatMessage> {
|
||||
val sorted = contextPack.layers.entries.sortedBy { it.key.ordinal }
|
||||
val systemContent = sorted
|
||||
@@ -19,19 +27,22 @@ object PromptRenderer {
|
||||
.flatMap { it.value }
|
||||
.joinToString("\n\n") { it.content }
|
||||
.takeIf { it.isNotBlank() }
|
||||
val conversationMessages = sorted.filter { it.key != ContextLayer.L0 }.flatMap { (_, entries) ->
|
||||
entries.map { entry ->
|
||||
ChatMessage(
|
||||
role = when (entry.role) {
|
||||
EntryRole.SYSTEM -> "system"
|
||||
EntryRole.ASSISTANT -> "assistant"
|
||||
EntryRole.TOOL -> "tool"
|
||||
EntryRole.USER -> "user"
|
||||
},
|
||||
content = entry.content,
|
||||
)
|
||||
val conversationMessages = contextPack.layers.entries
|
||||
.filter { it.key != ContextLayer.L0 }
|
||||
.sortedWith(Comparator.comparing({ it.key }, nonL0RenderOrder))
|
||||
.flatMap { (_, entries) ->
|
||||
entries.map { entry ->
|
||||
ChatMessage(
|
||||
role = when (entry.role) {
|
||||
EntryRole.SYSTEM -> "system"
|
||||
EntryRole.ASSISTANT -> "assistant"
|
||||
EntryRole.TOOL -> "tool"
|
||||
EntryRole.USER -> "user"
|
||||
},
|
||||
content = entry.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val messages = buildList {
|
||||
systemContent?.let { add(ChatMessage("system", it)) }
|
||||
addAll(conversationMessages)
|
||||
|
||||
+6
-4
@@ -222,7 +222,7 @@ abstract class SessionOrchestrator(
|
||||
} ?: emptyList()
|
||||
val promptEntries = stageConfig.metadata["prompt"]
|
||||
?.let { path ->
|
||||
runCatching { promptResolver.resolve(path) }
|
||||
val resolvedText = runCatching { promptResolver.resolve(path) }
|
||||
.onFailure {
|
||||
log.error(
|
||||
"[SessionOrchestrator] stage=${stageId.value}: " +
|
||||
@@ -230,9 +230,11 @@ abstract class SessionOrchestrator(
|
||||
)
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.let { text ->
|
||||
val text = resolvedText?.takeIf { it.isNotBlank() }
|
||||
?: error(
|
||||
"[SessionOrchestrator] stage=${stageId.value}: " +
|
||||
"declared prompt '$path' could not be resolved",
|
||||
)
|
||||
listOf(
|
||||
ContextEntry(
|
||||
id = ContextEntryId(UUID.randomUUID().toString()),
|
||||
|
||||
Reference in New Issue
Block a user