mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-29 06:00:52 +02:00
Fix Eino final output fallback capture
This commit is contained in:
@@ -55,6 +55,70 @@ func TestEinoExtractFallbackAssistantFromMsgs_prefersToolOverEarlierAssistant(t
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoExtractFallbackAssistantFromMsgs_plainAssistant(t *testing.T) {
|
||||
msgs := []*schema.Message{
|
||||
schema.UserMessage("hi"),
|
||||
schema.AssistantMessage("plain answer", nil),
|
||||
}
|
||||
if got := einoExtractFallbackAssistantFromMsgs(msgs); got != "plain answer" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoExtractFallbackAssistantFromMsgs_finalAssistantAfterToolResult(t *testing.T) {
|
||||
msgs := []*schema.Message{
|
||||
schema.UserMessage("hi"),
|
||||
schema.AssistantMessage("", []schema.ToolCall{{
|
||||
ID: "call-1",
|
||||
Type: "function",
|
||||
Function: schema.FunctionCall{
|
||||
Name: "execute",
|
||||
Arguments: `{"command":"pwd"}`,
|
||||
},
|
||||
}}),
|
||||
schema.ToolMessage("/tmp", "call-1", schema.WithToolName("execute")),
|
||||
schema.AssistantMessage("final after tool", nil),
|
||||
}
|
||||
if got := einoExtractFallbackAssistantFromMsgs(msgs); got != "final after tool" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoExtractFallbackAssistantFromMsgs_doesNotUseAssistantBeforeUnfinishedToolResult(t *testing.T) {
|
||||
msgs := []*schema.Message{
|
||||
schema.UserMessage("hi"),
|
||||
schema.AssistantMessage("I will inspect that.", nil),
|
||||
schema.AssistantMessage("", []schema.ToolCall{{
|
||||
ID: "call-1",
|
||||
Type: "function",
|
||||
Function: schema.FunctionCall{
|
||||
Name: "execute",
|
||||
Arguments: `{"command":"pwd"}`,
|
||||
},
|
||||
}}),
|
||||
schema.ToolMessage("/tmp", "call-1", schema.WithToolName("execute")),
|
||||
}
|
||||
if got := einoExtractFallbackAssistantFromMsgs(msgs); got != "" {
|
||||
t.Fatalf("got %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoRunResultBuilderFinalFallsBackToPlainAssistantTrace(t *testing.T) {
|
||||
runMessages := newEinoRunMessageAccumulator(nil)
|
||||
runMessages.Append(schema.UserMessage("hi"))
|
||||
runMessages.Append(schema.AssistantMessage("plain answer", nil))
|
||||
|
||||
got := newEinoRunResultBuilder(einoRunResultBuilderConfig{
|
||||
OrchMode: "deep",
|
||||
EmptyHint: "empty",
|
||||
RunMessages: runMessages,
|
||||
}).BuildFinal()
|
||||
|
||||
if got.Response != "plain answer" {
|
||||
t.Fatalf("response = %q, want plain answer", got.Response)
|
||||
}
|
||||
}
|
||||
|
||||
func toolExitMsg(content, callID string) *schema.Message {
|
||||
m := schema.ToolMessage(content, callID)
|
||||
m.ToolName = "exit"
|
||||
|
||||
@@ -57,6 +57,16 @@ func (a *einoRunMessageAccumulator) Messages() []adk.Message {
|
||||
return a.msgs
|
||||
}
|
||||
|
||||
func (a *einoRunMessageAccumulator) NewMessages() []adk.Message {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
if a.baseCount < 0 || a.baseCount >= len(a.msgs) {
|
||||
return nil
|
||||
}
|
||||
return a.msgs[a.baseCount:]
|
||||
}
|
||||
|
||||
func (a *einoRunMessageAccumulator) BaseCount() int {
|
||||
if a == nil {
|
||||
return 0
|
||||
|
||||
@@ -27,6 +27,10 @@ func TestEinoRunMessageAccumulatorTracksBaseAndAppends(t *testing.T) {
|
||||
if len(msgs) != 2 || msgs[1].Role != schema.Assistant || msgs[1].Content != "hello" {
|
||||
t.Fatalf("messages = %#v", msgs)
|
||||
}
|
||||
newMsgs := acc.NewMessages()
|
||||
if len(newMsgs) != 1 || newMsgs[0].Content != "hello" {
|
||||
t.Fatalf("new messages = %#v", newMsgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoRunMessageAccumulatorToolMessage(t *testing.T) {
|
||||
|
||||
@@ -45,7 +45,7 @@ func (b *einoRunResultBuilder) BuildFinal() *RunResult {
|
||||
func (b *einoRunResultBuilder) build(partial bool) *RunResult {
|
||||
var runMsgs []adk.Message
|
||||
if b.cfg.RunMessages != nil {
|
||||
runMsgs = b.cfg.RunMessages.Messages()
|
||||
runMsgs = b.cfg.RunMessages.NewMessages()
|
||||
}
|
||||
var lastAssistant string
|
||||
var lastPlanExecuteExecutor string
|
||||
@@ -107,6 +107,9 @@ func buildEinoRunResultFromAccumulated(
|
||||
if cleaned == "" {
|
||||
if fb := strings.TrimSpace(einoExtractFallbackAssistantFromMsgs(runAccumulatedMsgs)); fb != "" {
|
||||
cleaned = fb
|
||||
if orchMode == "plan_execute" {
|
||||
cleaned = UnwrapPlanExecuteUserText(cleaned)
|
||||
}
|
||||
}
|
||||
}
|
||||
cleaned = dedupeRepeatedParagraphs(cleaned, 80)
|
||||
@@ -146,32 +149,38 @@ func markModelFacingTraceForPersistence(msgs []adk.Message) []adk.Message {
|
||||
return out
|
||||
}
|
||||
|
||||
// einoExtractFallbackAssistantFromMsgs 在「主通道未产出助手正文」时,从 Eino ADK 轨迹中回填用户可见回复。
|
||||
// 典型场景:监督者仅调用 exit(final_result 落在 Tool 消息中),或工具结果已写入历史但 lastAssistant 未更新。
|
||||
// einoExtractFallbackAssistantFromMsgs 在「主通道未产出助手正文」时,从 Eino ADK
|
||||
// 原生消息轨迹中回填用户可见回复。这里保持克制:只采纳倒序最近的可交付终态,
|
||||
// 避免把工具调用前的过渡语或子任务过程误升为最终回复。
|
||||
//
|
||||
// 优先级:最后一次 exit 工具输出 → 最后一条含 exit 的助手 tool_calls 参数中的 final_result。
|
||||
// 可交付终态:
|
||||
// - exit 工具输出;
|
||||
// - assistant 调用 exit 时 arguments.final_result;
|
||||
// - 没有后续普通工具结果截断的纯 assistant 正文。
|
||||
func einoExtractFallbackAssistantFromMsgs(msgs []adk.Message) string {
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
m := msgs[i]
|
||||
if m == nil || m.Role != schema.Tool {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(m.ToolName), adk.ToolInfoExit.Name) {
|
||||
continue
|
||||
}
|
||||
content := strings.TrimSpace(m.Content)
|
||||
if content == "" || strings.HasPrefix(content, einomcp.ToolErrorPrefix) {
|
||||
continue
|
||||
}
|
||||
return content
|
||||
}
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
m := msgs[i]
|
||||
if m == nil || m.Role != schema.Assistant {
|
||||
continue
|
||||
}
|
||||
if s := einoExtractExitFinalFromAssistantToolCalls(m); s != "" {
|
||||
return s
|
||||
switch m.Role {
|
||||
case schema.Tool:
|
||||
if strings.EqualFold(strings.TrimSpace(m.ToolName), adk.ToolInfoExit.Name) {
|
||||
content := strings.TrimSpace(m.Content)
|
||||
if content != "" && !strings.HasPrefix(content, einomcp.ToolErrorPrefix) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
case schema.Assistant:
|
||||
if s := einoExtractExitFinalFromAssistantToolCalls(m); s != "" {
|
||||
return s
|
||||
}
|
||||
if len(m.ToolCalls) == 0 {
|
||||
if content := strings.TrimSpace(m.Content); content != "" {
|
||||
return content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -55,6 +55,24 @@ func TestEinoRunResultBuilderFinalUsesSnapshots(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoRunResultBuilderFallbackIgnoresBaseHistory(t *testing.T) {
|
||||
runMessages := newEinoRunMessageAccumulator([]adk.Message{
|
||||
schema.UserMessage("previous request"),
|
||||
schema.AssistantMessage("previous answer", nil),
|
||||
schema.UserMessage("new request"),
|
||||
})
|
||||
|
||||
got := newEinoRunResultBuilder(einoRunResultBuilderConfig{
|
||||
OrchMode: "deep",
|
||||
EmptyHint: "empty",
|
||||
RunMessages: runMessages,
|
||||
}).BuildFinal()
|
||||
|
||||
if got.Response != "empty" {
|
||||
t.Fatalf("response = %q, want empty hint", got.Response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoRunResultBuilderPlanExecutePrefersExecutorOutput(t *testing.T) {
|
||||
runMessages := newEinoRunMessageAccumulator(nil)
|
||||
runMessages.Append(schema.AssistantMessage(`{"response":"planner text"}`, nil))
|
||||
@@ -73,3 +91,18 @@ func TestEinoRunResultBuilderPlanExecutePrefersExecutorOutput(t *testing.T) {
|
||||
t.Fatalf("response = %q, want executor text", got.Response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEinoRunResultBuilderPlanExecuteUnwrapsFallbackAssistant(t *testing.T) {
|
||||
runMessages := newEinoRunMessageAccumulator(nil)
|
||||
runMessages.Append(schema.AssistantMessage(`{"response":"fallback executor text"}`, nil))
|
||||
|
||||
got := newEinoRunResultBuilder(einoRunResultBuilderConfig{
|
||||
OrchMode: "plan_execute",
|
||||
EmptyHint: "empty",
|
||||
RunMessages: runMessages,
|
||||
}).BuildFinal()
|
||||
|
||||
if got.Response != "fallback executor text" {
|
||||
t.Fatalf("response = %q, want fallback executor text", got.Response)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user