File size: 7,103 Bytes
3f069c1
91d8de5
d354d21
 
 
 
0e0b570
d354d21
8b130cd
8df8dbc
0e64b2b
b19d137
3cfa3ec
8b130cd
240c48a
3cfa3ec
 
240c48a
 
 
 
3cfa3ec
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7131ab4
240c48a
 
ba9d5ad
 
 
3cfa3ec
 
ba9d5ad
8b130cd
 
24a2ed7
ef42026
d354d21
 
0e0b570
d354d21
0e64b2b
3cfa3ec
6a257b8
 
 
 
ba9d5ad
c55fff6
 
ba9d5ad
 
 
 
 
240c48a
 
 
 
af177ff
 
 
 
 
 
 
 
 
a606330
 
 
 
 
 
 
 
 
 
 
 
 
 
af177ff
 
 
 
 
 
 
 
 
240c48a
 
 
af177ff
 
 
 
 
1e33d7f
 
 
 
af177ff
 
 
240c48a
 
af177ff
0460bef
e2a6e54
af177ff
e2a6e54
 
 
 
ba9d5ad
de339ce
ba9d5ad
e2a6e54
240c48a
ba9d5ad
 
af177ff
e2a6e54
240c48a
 
 
e2a6e54
240c48a
e2a6e54
 
240c48a
 
 
 
 
 
af177ff
240c48a
e2a6e54
240c48a
af177ff
240c48a
 
af177ff
 
 
 
 
a8d4caf
 
af177ff
 
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
from src.settings import Settings
from smolagents import LiteLLMModel, ToolCallingAgent, CodeAgent, InferenceClientModel, Tool
from tools.FinalAnswerTool import FinalAnswerTool
from tools.ReadAudioTool import ReadAudioTool
from tools.ReadImageTool import ReadImageTool
from tools.ReadTextTool import ReadTextTool
from tools.ReadExcelTool import ReadExcelTool
from tools.ReadVideoTool import ReadVideoTool
from tools.WebSearchTool import TavilySearchTool
from tools.WikipediaTool import LocalWikipediaTool
from tools.YouTubeTool import YouTubeSearchTool
from tools.PythonRunnerTool import PythonRunnerTool
from tools.PythonCalcTool import PythonCalcTool
from tools.SemanticScholar import TavilyResearchTool
from src.utils import InputTokenRateLimiter
import wikipedia as wiki
from markdownify import markdownify as to_markdown
import time
import random

settings = Settings()
import litellm
from litellm import completion 

#litellm._turn_on_debug()
class WikiTitleFinder(Tool):
    name = "wiki_titles"
    description = "Search for related Wikipedia page titles."
    inputs = {"query": {"type": "string", "description": "Search query."}}
    output_type = "string"

    def forward(self, query: str) -> str:
        results = wiki.search(query)
        return ", ".join(results) if results else "No results."

class WikiContentFetcher(Tool):
    name = "wiki_page"
    description = "Fetch Wikipedia page content."
    inputs = {"page_title": {"type": "string", "description": "Wikipedia page title."}}
    output_type = "string"

    def forward(self, page_title: str) -> str:
        try:
            return to_markdown(wiki.page(page_title).html())
        except wiki.exceptions.PageError:
            return f"'{page_title}' not found."

class MathSolver(Tool):
    name = "math_solver"
    description = "Safely evaluate basic math expressions."
    inputs = {"input": {"type": "string", "description": "Math expression to evaluate."}}
    output_type = "string"

    def forward(self, input: str) -> str:
        try:
            return str(eval(input, {"__builtins__": {}}))
        except Exception as e:
            return f"Math error: {e}"
        
class BasicAgent():
    def __init__(self):
        self.model = LiteLLMModel(
            model_id=settings.llm_model_id, 
            api_key=settings.llm_api_key
        )
        self.agent = CodeAgent(
            model=self.model,
            tools=[
                TavilySearchTool(),
                TavilyResearchTool(),
                FinalAnswerTool(),
                PythonCalcTool(),
                ReadAudioTool(),
                ReadImageTool(),
                ReadExcelTool(),
                ReadTextTool(),
                YouTubeSearchTool(),
                PythonRunnerTool(),
                MathSolver(),
                LocalWikipediaTool(),  
                WikiTitleFinder(),
                WikiContentFetcher(),
            ],
            max_steps=10,
            planning_interval=5,
        )
        self.token_rate_limiter = InputTokenRateLimiter()
        self.expected_tokens_per_step = 10000
        self.max_retries = 3
        self.base_delay = 5
        self.token_rate_limiter = InputTokenRateLimiter()
        self.expected_tokens_per_step = 10000
        self.max_retries = 3
        self.base_delay = 5


    def _read_file(self, file_path: str) -> str:
        if not os.path.exists(file_path):
            print(f"File not found: {file_path}")
            return ""
        if file_path.endswith(".txt"):
            with open(file_path, "r", encoding="utf-8") as f:
                return f.read()
        elif file_path.endswith(".csv"):
            data = []
            try:
                # Use csv.DictReader to read the file into a list of dictionaries
                with open(file_path, mode='r', newline='', encoding='utf-8') as file:
                    reader = csv.DictReader(file)
                    for row in reader:
                        data.append(row)
                
                # Return the structured data as a JSON string
                return json.dumps(data, indent=2)
            except Exception as e:
                print(f"Error reading CSV file: {e}")
                return ""
        elif file_path.endswith(".docx"):
            doc = docx.Document(file_path)
            return "\n".join([p.text for p in doc.paragraphs])
        else:
            # For unsupported formats, return empty string
            print(f"Unsupported file type: {file_path}")
            return ""
            
    def run(self, question: str, file_content: str = "", file_path: str = "") -> str:
        final_answer = None
        retry_count = 0

        # If file content is empty but file_path exists, read the file
        if not file_content and file_path:
            file_content = self._read_file(file_path)

        context = ""
        if file_content:
            context = f"Story content:\n{file_content}"
        elif file_path:
            context = f"File path: {file_path}"

        print(f"Starting Agent with question: {question}\nContext length: {len(context)} chars")

        while True:
            try:
                final_input = f"Question: {question}\n\n{context}"
                steps = self.agent.run(final_input)

                # Convert string steps to list
                if isinstance(steps, str):
                    steps = [steps]

                for step in steps:
                    if isinstance(step, str):
                        final_answer = step
                        continue

                    step_name = step.__class__.__name__
                    output = getattr(step, "output", None)
                    if output:
                        final_answer = output

                    self.token_rate_limiter.maybe_wait(self.expected_tokens_per_step)
                    tokens_used = getattr(step, "token_usage", None)
                    if tokens_used:
                        self.token_rate_limiter.add_tokens(tokens_used.input_tokens)

                break  # Exit retry loop if successful

            except Exception as e:
                if "overload" in str(e).lower() or "rate limit" in str(e).lower():
                    if retry_count >= self.max_retries:
                        print("Max retries reached. Exiting...")
                        break
                    delay = self.base_delay * (2 ** retry_count) + random.random()
                    print(f"Retrying in {delay:.1f}s due to rate limit... ({e})")
                    time.sleep(delay)
                    retry_count += 1
                else:
                    print(f"Error during agent run: {e}")
                    break

        # Ensure a valid answer is always returned
        if not final_answer:
            final_answer = "I am unable to answer"

        print(f"Finished agent run. Final Answer: {final_answer}\n{'='*50}")
        return final_answer

    def __call__(self, question: str, file_content: str = "", file_path: str = "") -> str:
        return self.run(question, file_content, file_path)