File size: 10,417 Bytes
a23082c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b8f6b7f
a23082c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b8f6b7f
114747f
b8f6b7f
a23082c
 
 
 
 
 
 
 
 
b8f6b7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114747f
b8f6b7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68bd1d5
b8f6b7f
 
 
 
 
 
 
 
 
a23082c
 
 
 
 
 
 
 
 
 
b8f6b7f
 
 
 
 
 
 
 
 
a23082c
 
 
 
 
 
114747f
a23082c
 
 
 
 
 
 
68bd1d5
a23082c
 
 
 
 
 
 
 
 
 
 
 
b8f6b7f
 
 
 
a23082c
114747f
a23082c
 
114747f
 
 
 
 
 
 
 
 
 
 
 
a23082c
b8f6b7f
a23082c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import os
import logging

from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.tools import FunctionTool
from llama_index.llms.google_genai import GoogleGenAI
from llama_index.llms.openai import OpenAI

# Setup logging
logger = logging.getLogger(__name__)

# Helper function to load prompt from file
def load_prompt_from_file(filename: str, default_prompt: str) -> str:
    """Loads a prompt from a text file."""
    try:
        # Assuming the prompt file is in the same directory as the agent script
        script_dir = os.path.dirname(__file__)
        prompt_path = os.path.join(script_dir, filename)
        with open(prompt_path, "r") as f:
            prompt = f.read()
            logger.info(f"Successfully loaded prompt from {prompt_path}")
            return prompt
    except FileNotFoundError:
        logger.warning(f"Prompt file {filename} not found at {prompt_path}. Using default.")
        return default_prompt
    except Exception as e:
        logger.error(f"Error loading prompt file {filename}: {e}", exc_info=True)
        return default_prompt

# --- Tool Function ---

def reasoning_tool_fn(context: str) -> str:
    """
    Perform chain-of-thought reasoning over the provided context using a dedicated LLM.
    Args:
        context (str): The conversation/workflow history and current problem statement.
    Returns:
        str: A structured reasoning trace and conclusion, or an error message.
    """
    logger.info(f"Executing reasoning tool with context length: {len(context)}")
    
    # Configuration for the reasoning LLM (OpenAI in the original)
    reasoning_llm_model = os.getenv("REASONING_LLM_MODEL", "gpt-4o-mini") # Use gpt-4o-mini as default
    openai_api_key = os.getenv("OPENAI_API_KEY")
    
    if not openai_api_key:
        logger.error("ALPAFLOW_OPENAI_API_KEY not found for reasoning tool LLM.")
        return "Error: ALPAFLOW_OPENAI_API_KEY must be set to use the reasoning tool."

    # Define the prompt for the reasoning LLM
    reasoning_prompt = f"""You are an expert reasoning engine. Analyze the following workflow context and problem statement:

    --- CONTEXT START ---
    {context}
    --- CONTEXT END ---

    Perform the following steps:
    1. **Comprehension**: Identify the core question/problem and key constraints from the context.
    2. **Decomposition**: Break the problem into logical sub-steps.
    3. **Chain-of-Thought**: Reason through each sub-step, stating assumptions and deriving implications.
    4. **Verification**: Check conclusions against constraints.
    5. **Synthesis**: Integrate results into a cohesive answer/recommendation.
    6. **Clarity**: Use precise language.

    Respond with your numbered reasoning steps followed by a concise final conclusion or recommendation.
    """

    try:
        # Note: Original used OpenAI with a specific key and model. Retaining that.
        # Consider adding `reasoning_effort="high"` if supported and desired.
        llm = OpenAI(
            model=reasoning_llm_model,
            api_key=openai_api_key,
            reasoning_effort="high",
            temperature=0.055,
            max_tokens=16384
        )
        logger.info(f"Using reasoning LLM: {reasoning_llm_model}")
        response = llm.complete(reasoning_prompt)
        logger.info("Reasoning tool execution successful.")
        return response.text
    except Exception as e:
        logger.error(f"Error during reasoning tool LLM call: {e}", exc_info=True)
        return f"Error during reasoning: {e}"


def answer_question(question: str) -> str:
    """
    Answer any question by following this strict format:
      1. Include your chain of thought (your reasoning steps).
      2. End your reply with the exact template:
         FINAL ANSWER: [YOUR FINAL ANSWER]
    YOUR FINAL ANSWER must be:
      - A number, or
      - As few words as possible, or
      - A comma-separated list of numbers and/or strings.
    Formatting rules:
      * If asked for a number, do not use commas or units (e.g., $, %), unless explicitly requested.
      * If asked for a string, do not include articles or abbreviations (e.g., city names), and write digits in plain text.
      * If asked for a comma-separated list, apply the above rules to each element.
    This tool should be invoked immediately after completing the final planning sub-step.
    """
    logger.info(f"Answering question: {question[:100]}")

    gemini_api_key = os.getenv("GEMINI_API_KEY")
    if not gemini_api_key:
        logger.error("GEMINI_API_KEY not set for answer_question tool.")
        return "Error: GEMINI_API_KEY not set."

    model_name = os.getenv("ANSWER_TOOL_LLM_MODEL", "gemini-2.5-pro-preview-03-25")

    # Build the assistant prompt enforcing the required format
    assistant_prompt = (
        "You are a general AI assistant. I will ask you a question. "
        "Report your thoughts, and finish your answer with the following template: "
        "FINAL ANSWER: [YOUR FINAL ANSWER]. "
        "YOUR FINAL ANSWER should be a number OR as few words as possible "
        "OR a comma separated list of numbers and/or strings. "
        "If you are asked for a number, don't use commas for thousands or any units like $ or % unless specified. "
        "If you are asked for a string, omit articles and abbreviations, and write digits in plain text. "
        "If you are asked for a comma separated list, apply these rules to each element.\n\n"
        f"Question: {question}\n"
        "Answer:"
    )

    try:
        llm = GoogleGenAI(api_key=gemini_api_key, model="gemini-2.5-pro-preview-03-25", temperature=0.05)
        logger.info(f"Using answer LLM: {model_name}")
        response = llm.complete(assistant_prompt)
        logger.info("Answer generated successfully.")
        return response.text
    except Exception as e:
        logger.error(f"LLM call failed during answer generation: {e}", exc_info=True)
        return f"Error during answer generation: {e}"


# --- Tool Definition ---
reasoning_tool = FunctionTool.from_defaults(
    fn=reasoning_tool_fn,
    name="reasoning_tool",
    description=(
        "Applies detailed chain-of-thought reasoning to the provided workflow context using a dedicated LLM. "
        "Input: context (str). Output: Reasoning steps and conclusion (str) or error message."
    ),
)

answer_question = FunctionTool.from_defaults(
    fn=answer_question,
    name="answer_question",
    description=(
        "Use this tool to answer any question, reporting your reasoning steps and ending with 'FINAL ANSWER: ...'. "
        "Invoke this tool immediately after the final sub-step of planning is complete."
    ),
)

# --- Agent Initialization ---
def initialize_reasoning_agent() -> ReActAgent:
    """Initializes the Reasoning Agent."""
    logger.info("Initializing ReasoningAgent...")

    # Configuration for the agent's main LLM (Google GenAI)
    agent_llm_model = os.getenv("REASONING_AGENT_LLM_MODEL", "gemini-2.5-pro-preview-03-25")
    gemini_api_key = os.getenv("GEMINI_API_KEY")

    if not gemini_api_key:
        logger.error("GEMINI_API_KEY not found for ReasoningAgent.")
        raise ValueError("GEMINI_API_KEY must be set for ReasoningAgent")

    try:
        llm = GoogleGenAI(api_key=gemini_api_key, model="gemini-2.5-pro-preview-03-25", temperature=0.05)
        logger.info(f"Using agent LLM: {agent_llm_model}")

        # Load system prompt
        default_system_prompt = ("You are ReasoningAgent... [Default prompt content - replace with actual]" # Placeholder
                              )
        system_prompt = load_prompt_from_file("../prompts/reasoning_agent_prompt.txt", default_system_prompt)
        if system_prompt == default_system_prompt:
             logger.warning("Using default/fallback system prompt for ReasoningAgent.")

        agent = ReActAgent(
            name="reasoning_agent",
            description=(
                "An autonomous reasoning specialist that applies `reasoning_tool` to perform "
                "in-depth chain-of-thought analysis on incoming queries or contexts, "
                "then seamlessly delegates the synthesized insights to `planner_agent` "
                "or `long_context_management_agent` for subsequent task orchestration."
            ),
            tools=[reasoning_tool],
            llm=llm,
            system_prompt=system_prompt,
            can_handoff_to=[
                "code_agent",
                "research_agent",
                "math_agent",
                "role_agent",
                "image_analyzer_agent",
                "text_analyzer_agent",
                "planner_agent",
                "long_context_management_agent",
                "advanced_validation_agent",
                "video_analyzer_agent"
            ],
        )

        return agent

    except Exception as e:
        logger.error(f"Error during ReasoningAgent initialization: {e}", exc_info=True)
        raise

# Example usage (for testing if run directly)
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    logger.info("Running reasoning_agent.py directly for testing...")

    # Check required keys
    required_keys = ["GEMINI_API_KEY", "ALPAFLOW_OPENAI_API_KEY"]
    missing_keys = [key for key in required_keys if not os.getenv(key)]
    if missing_keys:
        print(f"Error: Required environment variable(s) not set: {', '.join(missing_keys)}. Cannot run test.")
    else:
        try:
            # Test the reasoning tool directly
            print("\nTesting reasoning_tool_fn...")
            test_context = "User asked: What is the capital of France? ResearchAgent found: Paris. VerifierAgent confirmed: High confidence."
            reasoning_output = reasoning_tool_fn(test_context)
            print(f"Reasoning Tool Output:\n{reasoning_output}")
            
            # Initialize the agent (optional)
            # test_agent = initialize_reasoning_agent()
            # print("\nReasoning Agent initialized successfully for testing.")
            # Example chat (would require context passing mechanism)
            # result = test_agent.chat("Synthesize the findings about the capital of France.") 
            # print(f"Agent chat result: {result}")

        except Exception as e:
            print(f"Error during testing: {e}")