001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.agent.react; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.List; 021 022public interface ReActStepParser { 023 024 ReActStepParser DEFAULT = content -> { 025 if (content == null || content.trim().isEmpty()) { 026 return Collections.emptyList(); 027 } 028 029 List<ReActStep> steps = new ArrayList<>(); 030 String[] lines = content.split("\n"); 031 032 String currentThought = null; 033 String currentAction = null; 034 String currentRequest = null; 035 StringBuilder currentActionInput = new StringBuilder(); 036 boolean inActionInput = false; 037 038 for (String line : lines) { 039 String trimmedLine = line.trim(); 040 041 // 如果遇到新的 Thought,且已有完整步骤,先保存 042 if (trimmedLine.startsWith("Thought:")) { 043 if (currentThought != null && currentAction != null) { 044 // 保存上一个完整的 step(在遇到新 Thought 或 Final Answer 时触发) 045 steps.add(new ReActStep(currentThought, currentAction, currentActionInput.toString().trim())); 046 // 重置状态 047 currentActionInput.setLength(0); 048 inActionInput = false; 049 } 050 currentThought = trimmedLine.substring("Thought:".length()).trim(); 051 currentAction = null; 052 } 053 // 如果遇到 Action 054 else if (trimmedLine.startsWith("Action:")) { 055 if (currentThought == null) { 056 // 如果 Action 出现在 Thought 之前,视为格式错误,可选择忽略或报错 057 continue; 058 } 059 currentAction = trimmedLine.substring("Action:".length()).trim(); 060 } 061 // 如果遇到 Action Input 062 else if (trimmedLine.startsWith("Action Input:")) { 063 if (currentAction == null) { 064 // Action Input 出现在 Action 之前,跳过 065 continue; 066 } 067 String inputPart = trimmedLine.substring("Action Input:".length()).trim(); 068 currentActionInput.append(inputPart); 069 inActionInput = true; 070 } 071 // 如果正在读取 Action Input 的后续行(多行 JSON) 072 else if (inActionInput) { 073 // 判断是否是下一个结构的开始:Thought / Action / Final Answer 074 if (trimmedLine.startsWith("Thought:") || 075 trimmedLine.startsWith("Action:") || 076 trimmedLine.startsWith("Final Answer:")) { 077 // 实际上这一行属于下一段,应退回处理 078 // 但我们是在 for 循环里,无法“退回”,所以先保存当前 step 079 steps.add(new ReActStep(currentThought, currentAction, currentActionInput.toString().trim())); 080 currentActionInput.setLength(0); 081 inActionInput = false; 082 currentThought = null; 083 currentAction = null; 084 // 重新处理当前行(递归或标记),但为简化,我们直接继续下一轮 085 // 因为下一轮会处理 Thought/Action 086 continue; 087 } else { 088 // 是 Action Input 的续行,追加(保留原始换行或加空格) 089 if (currentActionInput.length() > 0) { 090 currentActionInput.append("\n"); 091 } 092 currentActionInput.append(line); // 保留原始缩进(可选) 093 } 094 } 095 // 如果遇到 Final Answer,结束当前步骤(如果有) 096 else if (trimmedLine.startsWith("Final Answer:")) { 097 if (currentThought != null && currentAction != null) { 098 steps.add(new ReActStep(currentThought, currentAction, currentActionInput.toString().trim())); 099 } 100 // Final Answer 本身不作为 ReActStep,通常单独处理 101 break; // 或 continue,视需求而定 102 } 103 // 空行或无关行:如果是 Action Input 多行内容,已在上面处理;否则忽略 104 } 105 106 // 循环结束后,检查是否还有未保存的步骤 107 if (currentThought != null && currentAction != null) { 108 steps.add(new ReActStep(currentThought, currentAction, currentActionInput.toString().trim())); 109 } 110 111 return steps; 112 }; 113 114 115 List<ReActStep> parse(String content); 116 117 118 default boolean isFinalAnswer(String content) { 119 return content.contains(getFinalAnswerFlag()); 120 } 121 122 default boolean isReActAction(String content) { 123 return content.contains("Action:") && content.contains("Action Input:"); 124 } 125 126 default boolean isRequest(String content) { 127 return content.contains("Request:"); 128 } 129 130 default String extractRequestQuestion(String content) { 131 return content.trim().substring("Request:".length()).trim(); 132 } 133 134 default String getFinalAnswerFlag() { 135 return "Final Answer:"; 136 } 137}