suchith83 commited on
Commit
d07654f
·
1 Parent(s): 46cbd5f

initial commit with gradio app

Browse files
Files changed (9) hide show
  1. .gitignore +3 -0
  2. app.py +44 -0
  3. requirements.txt +8 -0
  4. research.py +178 -0
  5. tools/__init__.py +6 -0
  6. tools/fetch.py +31 -0
  7. tools/search.py +65 -0
  8. tools/summarize.py +42 -0
  9. tools/tool.py +15 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ venv/
2
+ tools/__pycache__
3
+ .env
app.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from research import research
3
+ from textblob import TextBlob
4
+
5
+ def research_query(query):
6
+ """
7
+ Function to handle research queries through Gradio interface
8
+
9
+ Args:
10
+ text (str): The query to perform websearch and provide summary.
11
+ Returns:
12
+ text (str): A detailed summary on the query asked by perfoming web search.
13
+ """
14
+ if not query.strip():
15
+ return "Please enter a valid query"
16
+
17
+ try:
18
+ result = research(query)
19
+ return result
20
+ except Exception as e:
21
+ return f"Error processing query: {str(e)}"
22
+
23
+ # Create Gradio interface
24
+ demo = gr.Interface(
25
+ fn=research_query,
26
+ inputs=gr.Textbox(
27
+ lines=3,
28
+ placeholder="Enter your research query here...",
29
+ label="Research Query"
30
+ ),
31
+ outputs=gr.Textbox(
32
+ lines=10,
33
+ label="Research Results"
34
+ ),
35
+ title="Research Assistant",
36
+ description="Enter a query to get detailed research results using ReAct agent.",
37
+ examples=[
38
+ ["What are the latest developments in quantum computing?"],
39
+ ["Explain the impact of artificial intelligence on healthcare"],
40
+ ]
41
+ )
42
+
43
+ if __name__ == "__main__":
44
+ demo.launch(mcp_server=True)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ requests
2
+ openai
3
+ python-dotenv
4
+ markdownify
5
+ mcp[cli]
6
+ httpx
7
+ gradio[mcp]
8
+ textblob
research.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List, Dict, Any, Optional
3
+ from openai import OpenAI
4
+ import json
5
+ from tools import SearchTool, FetchTool, SummarizeTool
6
+ from dotenv import load_dotenv
7
+ import httpx
8
+ from mcp.server.fastmcp import FastMCP
9
+ from openai.types.chat import ChatCompletionMessage
10
+ from openai.types.chat.chat_completion import ChatCompletion
11
+
12
+ # mcp = FastMCP("researcher")
13
+
14
+ load_dotenv()
15
+
16
+ class ReActAgent:
17
+ def __init__(self, client):
18
+ self.client = client
19
+ self.model = "qwen-3-32b"
20
+ self.conversation_history: List[Dict[str, str]] = []
21
+ self.max_history_length = 10 # Limit conversation history
22
+ self.tools = [
23
+ SearchTool(),
24
+ FetchTool(),
25
+ SummarizeTool()
26
+ ]
27
+
28
+ self.tools_json = [
29
+ {
30
+ "type": "function",
31
+ "function": tool.to_json()
32
+ }
33
+ for tool in self.tools
34
+ ]
35
+ self.tools_map = {tool.name: tool for tool in self.tools}
36
+ self.process_log = [] # Store the intermediate process
37
+
38
+ def _execute_tool(self, tool_call: Dict[str, Any]) -> str:
39
+ """Execute the called tool and return the result."""
40
+ try:
41
+ tool_name = tool_call.function.name
42
+ arguments = json.loads(tool_call.function.arguments)
43
+
44
+ if tool_name not in self.tools_map:
45
+ return f"Error: Unknown tool: {tool_name}"
46
+
47
+ tool = self.tools_map[tool_name]
48
+ result = tool(**arguments)
49
+
50
+ # Log the tool execution
51
+ self.process_log.append({
52
+ "tool": tool_name,
53
+ "arguments": arguments,
54
+ "result": result
55
+ })
56
+
57
+ return result
58
+ except json.JSONDecodeError:
59
+ error_msg = "Error: Invalid tool arguments format"
60
+ self.process_log.append({
61
+ "tool": tool_call.function.name,
62
+ "arguments": tool_call.function.arguments,
63
+ "result": error_msg
64
+ })
65
+ return error_msg
66
+ except Exception as e:
67
+ error_msg = f"Error executing tool: {str(e)}"
68
+ self.process_log.append({
69
+ "tool": tool_call.function.name,
70
+ "arguments": tool_call.function.arguments,
71
+ "result": error_msg
72
+ })
73
+ return error_msg
74
+
75
+ def _truncate_history(self):
76
+ """Keep only the most recent messages to prevent context overflow."""
77
+ if len(self.conversation_history) > self.max_history_length:
78
+ self.conversation_history = self.conversation_history[-self.max_history_length:]
79
+
80
+ def _format_process_log(self) -> str:
81
+ """Format the process log into a readable string."""
82
+ if not self.process_log:
83
+ return "No intermediate steps were taken."
84
+
85
+ formatted_log = ["<intermediate_steps>"]
86
+ for i, step in enumerate(self.process_log, 1):
87
+ formatted_log.append(f"\nStep {i}:")
88
+ formatted_log.append(f"Tool: {step['tool']}")
89
+ formatted_log.append(f"Arguments: {json.dumps(step['arguments'], indent=2)}")
90
+ formatted_log.append(f"Result: {step['result']}")
91
+ formatted_log.append("</intermediate_steps>")
92
+ return "\n".join(formatted_log)
93
+
94
+ def run(self, user_input: str) -> str:
95
+ """Run the ReAct loop for a single user input."""
96
+ if not user_input or not isinstance(user_input, str):
97
+ return "Error: Invalid input. Please provide a valid string query."
98
+
99
+ try:
100
+ # Reset process log for new query
101
+ self.process_log = []
102
+
103
+ # Add user input to conversation history
104
+ self.conversation_history.append({"role": "user", "content": user_input})
105
+ print(f"\n\nUser input: {user_input}\n--------------------------------\n")
106
+
107
+ while True:
108
+ try:
109
+ # Get response from the model
110
+ response: ChatCompletion = self.client.chat.completions.create(
111
+ model=self.model,
112
+ messages=self.conversation_history,
113
+ tools=self.tools_json,
114
+ )
115
+
116
+ message: ChatCompletionMessage = response.choices[0].message
117
+
118
+ # Add assistant's response to conversation history
119
+ self.conversation_history.append({
120
+ "role": "assistant",
121
+ "content": message.content if message.content else "",
122
+ "tool_calls": message.tool_calls
123
+ })
124
+
125
+ # If no tool calls, return the response with process log
126
+ if not message.tool_calls:
127
+ print("No tool calls\nExiting loop\n--------------------------------")
128
+ final_response = message.content or "No response generated"
129
+ process_log = self._format_process_log()
130
+ return f"{process_log}\n\n{final_response}"
131
+
132
+ # Execute the tool calls
133
+ tool_results = []
134
+ for tool_call in message.tool_calls:
135
+ print(f"Tool call: {tool_call.function.name}\nTool arguments: {tool_call.function.arguments}")
136
+ tool_result = self._execute_tool(tool_call)
137
+ print(f"Tool result: {tool_result}\n--------------------------------\n")
138
+ tool_results.append({
139
+ "tool_call_id": tool_call.id,
140
+ "role": "tool",
141
+ "name": tool_call.function.name,
142
+ "content": tool_result
143
+ })
144
+
145
+ # Add tool results to conversation history
146
+ self.conversation_history.extend(tool_results)
147
+ self._truncate_history()
148
+
149
+ except Exception as e:
150
+ error_msg = f"Error during model interaction: {str(e)}"
151
+ process_log = self._format_process_log()
152
+ return f"{error_msg}\n\n{process_log}"
153
+
154
+ except Exception as e:
155
+ error_msg = f"Error in research process: {str(e)}"
156
+ process_log = self._format_process_log()
157
+ return f"{error_msg}\n\n{process_log}"
158
+
159
+ # @mcp.tool()
160
+ def research(query: str) -> str:
161
+ """Get final answer on the query after detailed research"""
162
+ try:
163
+ api_key = os.environ.get("CEREBRAS_API_KEY")
164
+ if not api_key:
165
+ return "Error: Please set CEREBRAS_API_KEY environment variable"
166
+
167
+ client = OpenAI(
168
+ base_url="https://api.cerebras.ai/v1",
169
+ api_key=api_key
170
+ )
171
+
172
+ agent = ReActAgent(client)
173
+ return agent.run(query)
174
+ except Exception as e:
175
+ return f"Error in research function: {str(e)}"
176
+
177
+ # if __name__ == "__main__":
178
+ # mcp.run()
tools/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from .search import SearchTool
2
+ from .fetch import FetchTool
3
+ from .summarize import SummarizeTool
4
+ from .tool import Tool
5
+
6
+ __all__ = ["SearchTool", "FetchTool", "SummarizeTool", "Tool"]
tools/fetch.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .tool import Tool
2
+ from markdownify import markdownify
3
+ import requests
4
+
5
+ class FetchTool(Tool):
6
+ def __init__(self):
7
+ super().__init__(
8
+ name="fetch",
9
+ description="Fetch the content of a URL and return the markdownified version of the content",
10
+ inputSchema={
11
+ "type": "object",
12
+ "properties": {
13
+ "url": {"type": "string", "description": "The URL to fetch"}
14
+ }
15
+ }
16
+ )
17
+
18
+ def __call__(self, url: str):
19
+ try:
20
+ if not url:
21
+ return "Error: URL parameter is required"
22
+
23
+ resp = requests.get(url)
24
+ resp.raise_for_status() # Raise an exception for bad status codes
25
+
26
+ return markdownify(resp.text)
27
+
28
+ except requests.exceptions.RequestException as e:
29
+ return f"Error fetching URL: {str(e)}"
30
+ except Exception as e:
31
+ return f"Unexpected error while processing URL: {str(e)}"
tools/search.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from dotenv import load_dotenv
3
+ import os
4
+ from .tool import Tool
5
+
6
+ load_dotenv("./.env")
7
+
8
+ class SearchTool(Tool):
9
+ def __init__(self):
10
+ super().__init__(
11
+ name="search",
12
+ description="Search the web for information",
13
+ inputSchema={
14
+ "type": "object",
15
+ "properties": {
16
+ "query": {"type": "string", "description": "The search query"}
17
+ }
18
+ }
19
+ )
20
+
21
+ self.api_key = os.environ.get("GOOGLE_API_KEY")
22
+ self.search_engine_id = os.environ.get("GOOGLE_CSE_ID")
23
+
24
+ if not self.api_key:
25
+ raise ValueError("Please set GOOGLE_API_KEY environment variable")
26
+ if not self.search_engine_id:
27
+ raise ValueError("Please set GOOGLE_CSE_ID environment variable")
28
+
29
+ def __call__(self, query: str):
30
+ try:
31
+ if not query:
32
+ return "Error: Query parameter is required"
33
+
34
+ params = {
35
+ "q": query,
36
+ "key": self.api_key,
37
+ "cx": self.search_engine_id
38
+ }
39
+
40
+ resp = requests.get("https://www.googleapis.com/customsearch/v1", params=params)
41
+ resp.raise_for_status() # Raise an exception for bad status codes
42
+
43
+ _results = resp.json().get("items", [])
44
+ results = []
45
+ for result in _results[:3]:
46
+ results.append({
47
+ "title": result.get("title", "No title"),
48
+ "link": result.get("link", "No link"),
49
+ "snippet": result.get("snippet", "No snippet")
50
+ })
51
+
52
+ if not results:
53
+ return "No results found for the given query."
54
+
55
+ # Format results as a string
56
+ formatted_results = []
57
+ for i, result in enumerate(results, 1):
58
+ formatted_results.append(f"Result {i}:\nTitle: {result['title']}\nLink: {result['link']}\nSnippet: {result['snippet']}\n")
59
+
60
+ return "\n".join(formatted_results)
61
+
62
+ except requests.exceptions.RequestException as e:
63
+ return f"Error during search: {str(e)}"
64
+ except Exception as e:
65
+ return f"Unexpected error during search: {str(e)}"
tools/summarize.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .tool import Tool
2
+ from openai import OpenAI
3
+ from dotenv import load_dotenv
4
+ import os
5
+
6
+ load_dotenv("./.env")
7
+
8
+ class SummarizeTool(Tool):
9
+ def __init__(self):
10
+ super().__init__(
11
+ name="summarize",
12
+ description="Summarize the content of a URL",
13
+ inputSchema={
14
+ "type": "object",
15
+ "properties": {
16
+ "content": {"type": "string", "description": "The content to summarize"}
17
+ }
18
+ }
19
+ )
20
+
21
+ api_key = os.environ.get("CEREBRAS_API_KEY")
22
+ if not api_key:
23
+ raise ValueError("Please set CEREBRAS_API_KEY environment variable")
24
+
25
+ self.client = OpenAI(base_url="https://api.cerebras.ai/v1", api_key=api_key)
26
+
27
+ def __call__(self, **kwargs):
28
+ try:
29
+ content = kwargs.get("content")
30
+ if not content:
31
+ return "Error: Content parameter is required"
32
+
33
+ response = self.client.chat.completions.create(
34
+ model="qwen-3-32b",
35
+ messages=[
36
+ {"role": "system", "content": "You are a helpful assistant that summarizes content while keeping the all important information."},
37
+ {"role": "user", "content": content}
38
+ ]
39
+ )
40
+ return response.choices[0].message.content
41
+ except Exception as e:
42
+ return f"Error during summarization: {str(e)}"
tools/tool.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Tool:
2
+ def __init__(self, name: str, description: str, inputSchema: dict):
3
+ self.name = name
4
+ self.description = description
5
+ self.inputSchema = inputSchema
6
+
7
+ def __repr__(self):
8
+ return f"Tool(name={self.name}, description={self.description}, inputSchema={self.inputSchema})"
9
+
10
+ def to_json(self):
11
+ return {
12
+ "name": self.name,
13
+ "description": self.description,
14
+ "parameters": self.inputSchema
15
+ }