Spaces:
Running
Running
Nikolay Angelov
commited on
Commit
ยท
224b69c
1
Parent(s):
f9764de
move everything to one app on port 7860 due to hugging face space limitations
Browse files- Dockerfile +4 -0
- README.md +21 -17
- UI.py +8 -6
- app.py +4 -14
- docker-compose.yml +0 -1
- main.py +9 -21
- requirements.txt +1 -3
- tools/web_search.py +0 -27
Dockerfile
CHANGED
@@ -17,4 +17,8 @@ COPY --chown=user requirements.txt requirements.txt
|
|
17 |
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
18 |
|
19 |
COPY --chown=user . /app
|
|
|
|
|
|
|
|
|
20 |
CMD ["python", "main.py"]
|
|
|
17 |
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
18 |
|
19 |
COPY --chown=user . /app
|
20 |
+
|
21 |
+
# Expose Gradio port (used by Hugging Face Spaces)
|
22 |
+
EXPOSE 7860
|
23 |
+
|
24 |
CMD ["python", "main.py"]
|
README.md
CHANGED
@@ -23,7 +23,7 @@ An AI-powered career coaching assistant built with FastAPI, Gradio UI, LangChain
|
|
23 |
|
24 |
## ๐ Features
|
25 |
|
26 |
-
- **
|
27 |
- **AI-Powered Responses**: Utilizing Mixtral-8x7B-Instruct-v0.1 model
|
28 |
- **Interactive Chat Interface**: Real-time conversation with the AI agent
|
29 |
- **Multi-tool Integration**: Including webpage visits and time zone conversions
|
@@ -31,7 +31,7 @@ An AI-powered career coaching assistant built with FastAPI, Gradio UI, LangChain
|
|
31 |
|
32 |
## ๐ ๏ธ Technical Stack
|
33 |
|
34 |
-
- **Backend Framework**: FastAPI
|
35 |
- **UI Framework**: Gradio with SmolaGents
|
36 |
- **AI Framework**:
|
37 |
- LangChain ReAct Agent (Backend) - For structured reasoning and tool usage
|
@@ -39,7 +39,6 @@ An AI-powered career coaching assistant built with FastAPI, Gradio UI, LangChain
|
|
39 |
- **ML Models**: Hugging Face (Mixtral-8x7B-Instruct-v0.1)
|
40 |
- **Additional Key Libraries**:
|
41 |
- `uvicorn`: ASGI server
|
42 |
-
- `accelerate`: ML model support
|
43 |
- `markdownify`: Web content processing
|
44 |
- `langchain`: AI framework and tools
|
45 |
- `smolagents`: UI agent framework
|
@@ -78,16 +77,28 @@ export HUGGINGFACEHUB_API_TOKEN=your_token_here
|
|
78 |
python main.py
|
79 |
```
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
## ๐ API Documentation
|
82 |
|
83 |
-
|
84 |
-
- Swagger UI: http://localhost:8000/docs
|
85 |
-
- ReDoc: http://localhost:8000/redoc
|
86 |
|
87 |
## ๐ Key Endpoints
|
88 |
|
89 |
-
|
90 |
-
-
|
|
|
|
|
91 |
|
92 |
## ๐ How It Works
|
93 |
|
@@ -98,19 +109,12 @@ The application uses a ReAct (Reasoning and Acting) agent pattern, which follows
|
|
98 |
4. **Thought**: The agent reasons about the observation
|
99 |
5. **Action**: The agent either uses another tool or provides a final answer
|
100 |
|
101 |
-
This pattern allows the agent to:
|
102 |
-
- Use tools in a structured way
|
103 |
-
- Reason step-by-step about complex problems
|
104 |
-
- Provide transparent decision-making
|
105 |
-
- Handle multiple tool interactions
|
106 |
-
|
107 |
## โ ๏ธ Important Notes
|
108 |
|
109 |
- The application requires active internet connection for AI model access
|
110 |
- Hugging Face API token is required for model access
|
111 |
-
-
|
112 |
-
- The UI
|
113 |
-
- The backend uses LangChain's ReAct agent for structured reasoning and tool usage
|
114 |
|
115 |
## ๐ค Contributing
|
116 |
|
|
|
23 |
|
24 |
## ๐ Features
|
25 |
|
26 |
+
- **Unified Interface**: Combined FastAPI and Gradio UI on a single port (7860)
|
27 |
- **AI-Powered Responses**: Utilizing Mixtral-8x7B-Instruct-v0.1 model
|
28 |
- **Interactive Chat Interface**: Real-time conversation with the AI agent
|
29 |
- **Multi-tool Integration**: Including webpage visits and time zone conversions
|
|
|
31 |
|
32 |
## ๐ ๏ธ Technical Stack
|
33 |
|
34 |
+
- **Backend Framework**: FastAPI (mounted with Gradio)
|
35 |
- **UI Framework**: Gradio with SmolaGents
|
36 |
- **AI Framework**:
|
37 |
- LangChain ReAct Agent (Backend) - For structured reasoning and tool usage
|
|
|
39 |
- **ML Models**: Hugging Face (Mixtral-8x7B-Instruct-v0.1)
|
40 |
- **Additional Key Libraries**:
|
41 |
- `uvicorn`: ASGI server
|
|
|
42 |
- `markdownify`: Web content processing
|
43 |
- `langchain`: AI framework and tools
|
44 |
- `smolagents`: UI agent framework
|
|
|
77 |
python main.py
|
78 |
```
|
79 |
|
80 |
+
The application will be available at:
|
81 |
+
- Main UI: http://localhost:7860
|
82 |
+
- API Documentation: http://localhost:7860/docs/
|
83 |
+
|
84 |
+
## ๐ Hugging Face Spaces Deployment
|
85 |
+
|
86 |
+
This application is specifically designed to work with Hugging Face Spaces:
|
87 |
+
- Uses a single port (7860) as required by Spaces
|
88 |
+
- Combines FastAPI and Gradio on the same port
|
89 |
+
- API documentation is accessible at `/docs/` on the same port
|
90 |
+
- All functionality works within Spaces' constraints
|
91 |
+
|
92 |
## ๐ API Documentation
|
93 |
|
94 |
+
The API documentation is available at `/docs/` on the same port as the main application (7860). This unified setup ensures compatibility with Hugging Face Spaces while maintaining all functionality.
|
|
|
|
|
95 |
|
96 |
## ๐ Key Endpoints
|
97 |
|
98 |
+
All endpoints are available on port 7860:
|
99 |
+
- `/`: Main Gradio UI
|
100 |
+
- `/docs/`: API Documentation
|
101 |
+
- `/agent/query`: Send queries to the AI agent
|
102 |
|
103 |
## ๐ How It Works
|
104 |
|
|
|
109 |
4. **Thought**: The agent reasons about the observation
|
110 |
5. **Action**: The agent either uses another tool or provides a final answer
|
111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
## โ ๏ธ Important Notes
|
113 |
|
114 |
- The application requires active internet connection for AI model access
|
115 |
- Hugging Face API token is required for model access
|
116 |
+
- All services run on port 7860 to comply with Hugging Face Spaces requirements
|
117 |
+
- The UI and API are served from the same port for better integration
|
|
|
118 |
|
119 |
## ๐ค Contributing
|
120 |
|
UI.py
CHANGED
@@ -1,7 +1,5 @@
|
|
1 |
-
import mimetypes
|
2 |
import os
|
3 |
import re
|
4 |
-
import shutil
|
5 |
from typing import Optional, List, Dict, Any, Callable
|
6 |
|
7 |
from smolagents.agent_types import AgentAudio, AgentImage, AgentText, handle_agent_output_types
|
@@ -208,8 +206,8 @@ class AgentUI:
|
|
208 |
self.chat_history = []
|
209 |
return "Started new conversation"
|
210 |
|
211 |
-
def
|
212 |
-
"""
|
213 |
api_port = int(self.api_url.split(":")[-1])
|
214 |
|
215 |
with gr.Blocks(css="""
|
@@ -265,12 +263,16 @@ class AgentUI:
|
|
265 |
outputs=[chatbot, menu_output]
|
266 |
)
|
267 |
docs_btn.click(
|
268 |
-
fn=lambda: f"<script>window.open('
|
269 |
inputs=[],
|
270 |
outputs=[menu_output]
|
271 |
)
|
272 |
|
273 |
-
|
|
|
|
|
|
|
|
|
274 |
interface.launch(server_name=server_name, server_port=server_port, **kwargs)
|
275 |
|
276 |
__all__ = ["stream_to_gradio", "AgentUI"]
|
|
|
|
|
1 |
import os
|
2 |
import re
|
|
|
3 |
from typing import Optional, List, Dict, Any, Callable
|
4 |
|
5 |
from smolagents.agent_types import AgentAudio, AgentImage, AgentText, handle_agent_output_types
|
|
|
206 |
self.chat_history = []
|
207 |
return "Started new conversation"
|
208 |
|
209 |
+
def get_gradio_app(self):
|
210 |
+
"""Get the Gradio app for mounting in FastAPI"""
|
211 |
api_port = int(self.api_url.split(":")[-1])
|
212 |
|
213 |
with gr.Blocks(css="""
|
|
|
263 |
outputs=[chatbot, menu_output]
|
264 |
)
|
265 |
docs_btn.click(
|
266 |
+
fn=lambda: f"<script>window.open('/docs', '_blank')</script>",
|
267 |
inputs=[],
|
268 |
outputs=[menu_output]
|
269 |
)
|
270 |
|
271 |
+
return interface
|
272 |
+
|
273 |
+
def launch(self, server_name: str = "0.0.0.0", server_port: int = 7860, **kwargs):
|
274 |
+
"""Launch the Gradio interface standalone (for development)"""
|
275 |
+
interface = self.get_gradio_app()
|
276 |
interface.launch(server_name=server_name, server_port=server_port, **kwargs)
|
277 |
|
278 |
__all__ = ["stream_to_gradio", "AgentUI"]
|
app.py
CHANGED
@@ -4,6 +4,7 @@ from langchain.agents import AgentExecutor, create_react_agent
|
|
4 |
from langchain_core.prompts import PromptTemplate
|
5 |
|
6 |
from tools.visit_webpage import visit_webpage
|
|
|
7 |
|
8 |
import gradio as gr
|
9 |
import datetime
|
@@ -62,16 +63,6 @@ def get_current_time(timezone: str = "UTC") -> str:
|
|
62 |
except Exception as e:
|
63 |
return f"Error: {str(e)}"
|
64 |
|
65 |
-
@tool
|
66 |
-
def visit_webpage(url: str) -> str:
|
67 |
-
"""Visit a webpage and return its content as markdown."""
|
68 |
-
try:
|
69 |
-
response = requests.get(url, timeout=10)
|
70 |
-
response.raise_for_status()
|
71 |
-
return f"Successfully visited {url}. Content length: {len(response.text)} characters"
|
72 |
-
except Exception as e:
|
73 |
-
return f"Error visiting webpage: {str(e)}"
|
74 |
-
|
75 |
# Load system prompt and template
|
76 |
with open("prompts.yaml", 'r') as stream:
|
77 |
prompt_templates = yaml.safe_load(stream)
|
@@ -80,7 +71,7 @@ with open("prompts.yaml", 'r') as stream:
|
|
80 |
prompt = PromptTemplate.from_template(prompt_templates["template"])
|
81 |
|
82 |
# Create the agent
|
83 |
-
tools = [get_current_time
|
84 |
agent = create_react_agent(
|
85 |
llm=llm,
|
86 |
tools=tools,
|
@@ -104,10 +95,9 @@ class QueryRequest(BaseModel):
|
|
104 |
async def root():
|
105 |
return HTMLResponse("<h2>Welcome! Please use the Gradio UI above.</h2>")
|
106 |
|
107 |
-
@app.get("/docs")
|
108 |
async def redirect_to_docs():
|
109 |
-
|
110 |
-
return RedirectResponse(url=f"{base_url}:8000/docs")
|
111 |
|
112 |
@app.post("/agent/query")
|
113 |
async def query_agent(request: QueryRequest):
|
|
|
4 |
from langchain_core.prompts import PromptTemplate
|
5 |
|
6 |
from tools.visit_webpage import visit_webpage
|
7 |
+
from tools.final_answer import final_answer
|
8 |
|
9 |
import gradio as gr
|
10 |
import datetime
|
|
|
63 |
except Exception as e:
|
64 |
return f"Error: {str(e)}"
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
# Load system prompt and template
|
67 |
with open("prompts.yaml", 'r') as stream:
|
68 |
prompt_templates = yaml.safe_load(stream)
|
|
|
71 |
prompt = PromptTemplate.from_template(prompt_templates["template"])
|
72 |
|
73 |
# Create the agent
|
74 |
+
tools = [get_current_time]
|
75 |
agent = create_react_agent(
|
76 |
llm=llm,
|
77 |
tools=tools,
|
|
|
95 |
async def root():
|
96 |
return HTMLResponse("<h2>Welcome! Please use the Gradio UI above.</h2>")
|
97 |
|
98 |
+
@app.get("/docs", include_in_schema=False)
|
99 |
async def redirect_to_docs():
|
100 |
+
return RedirectResponse(url="/docs/")
|
|
|
101 |
|
102 |
@app.post("/agent/query")
|
103 |
async def query_agent(request: QueryRequest):
|
docker-compose.yml
CHANGED
@@ -4,7 +4,6 @@ services:
|
|
4 |
app:
|
5 |
build: .
|
6 |
ports:
|
7 |
-
- "8000:8000"
|
8 |
- "7860:7860"
|
9 |
env_file:
|
10 |
- .env
|
|
|
4 |
app:
|
5 |
build: .
|
6 |
ports:
|
|
|
7 |
- "7860:7860"
|
8 |
env_file:
|
9 |
- .env
|
main.py
CHANGED
@@ -1,36 +1,24 @@
|
|
1 |
-
import threading
|
2 |
-
import time
|
3 |
import uvicorn
|
4 |
from app import app, agent
|
5 |
from UI import AgentUI
|
6 |
|
7 |
def main():
|
8 |
"""
|
9 |
-
Run
|
10 |
"""
|
11 |
# Configuration
|
12 |
-
|
13 |
-
ui_port = 7860
|
14 |
|
15 |
-
#
|
16 |
-
def run_fastapi():
|
17 |
-
uvicorn.run(app, host="0.0.0.0", port=api_port)
|
18 |
-
|
19 |
-
api_thread = threading.Thread(target=run_fastapi)
|
20 |
-
api_thread.daemon = True
|
21 |
-
api_thread.start()
|
22 |
-
|
23 |
-
# Give FastAPI time to start
|
24 |
-
print(f"Starting FastAPI server on port {api_port}...")
|
25 |
-
time.sleep(1)
|
26 |
-
|
27 |
-
# Start Gradio UI in the main thread
|
28 |
-
print(f"Starting Gradio UI on port {ui_port}...")
|
29 |
gradio_ui = AgentUI(
|
30 |
agent=agent,
|
31 |
-
api_url=f"http://localhost:{
|
32 |
)
|
33 |
-
gradio_ui.
|
|
|
|
|
|
|
|
|
34 |
|
35 |
if __name__ == "__main__":
|
36 |
main()
|
|
|
|
|
|
|
1 |
import uvicorn
|
2 |
from app import app, agent
|
3 |
from UI import AgentUI
|
4 |
|
5 |
def main():
|
6 |
"""
|
7 |
+
Run FastAPI and Gradio UI on the same port
|
8 |
"""
|
9 |
# Configuration
|
10 |
+
port = 7860
|
|
|
11 |
|
12 |
+
# Create and mount Gradio app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
gradio_ui = AgentUI(
|
14 |
agent=agent,
|
15 |
+
api_url=f"http://localhost:{port}"
|
16 |
)
|
17 |
+
app.mount("/", gradio_ui.get_gradio_app())
|
18 |
+
|
19 |
+
# Start the combined app
|
20 |
+
print(f"Starting combined server on port {port}...")
|
21 |
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
22 |
|
23 |
if __name__ == "__main__":
|
24 |
main()
|
requirements.txt
CHANGED
@@ -4,7 +4,6 @@ requests>=2.31.0
|
|
4 |
fastapi>=0.104.1
|
5 |
uvicorn[standard]>=0.24.0
|
6 |
gradio>=4.7.1
|
7 |
-
accelerate>=0.26.0
|
8 |
langchain>=0.1.0
|
9 |
langchain-core>=0.1.0
|
10 |
langchain-community>=0.0.13
|
@@ -13,5 +12,4 @@ pydantic>=2.5.2
|
|
13 |
pytz>=2023.3
|
14 |
python-dateutil>=2.8.2
|
15 |
huggingface-hub>=0.19.4
|
16 |
-
python-multipart>=0.0.6
|
17 |
-
aiohttp>=3.9.0
|
|
|
4 |
fastapi>=0.104.1
|
5 |
uvicorn[standard]>=0.24.0
|
6 |
gradio>=4.7.1
|
|
|
7 |
langchain>=0.1.0
|
8 |
langchain-core>=0.1.0
|
9 |
langchain-community>=0.0.13
|
|
|
12 |
pytz>=2023.3
|
13 |
python-dateutil>=2.8.2
|
14 |
huggingface-hub>=0.19.4
|
15 |
+
python-multipart>=0.0.6
|
|
tools/web_search.py
DELETED
@@ -1,27 +0,0 @@
|
|
1 |
-
from typing import Any, Optional
|
2 |
-
from smolagents.tools import Tool
|
3 |
-
import duckduckgo_search
|
4 |
-
|
5 |
-
class DuckDuckGoSearchTool(Tool):
|
6 |
-
name = "web_search"
|
7 |
-
description = "Performs a duckduckgo web search based on your query (think a Google search) then returns the top search results."
|
8 |
-
inputs = {'query': {'type': 'string', 'description': 'The search query to perform.'}}
|
9 |
-
output_type = "string"
|
10 |
-
|
11 |
-
def __init__(self, max_results=10, **kwargs):
|
12 |
-
super().__init__()
|
13 |
-
self.max_results = max_results
|
14 |
-
try:
|
15 |
-
from duckduckgo_search import DDGS
|
16 |
-
except ImportError as e:
|
17 |
-
raise ImportError(
|
18 |
-
"You must install package `duckduckgo_search` to run this tool: for instance run `pip install duckduckgo-search`."
|
19 |
-
) from e
|
20 |
-
self.ddgs = DDGS(**kwargs)
|
21 |
-
|
22 |
-
def forward(self, query: str) -> str:
|
23 |
-
results = self.ddgs.text(query, max_results=self.max_results)
|
24 |
-
if len(results) == 0:
|
25 |
-
raise Exception("No results found! Try a less restrictive/shorter query.")
|
26 |
-
postprocessed_results = [f"[{result['title']}]({result['href']})\n{result['body']}" for result in results]
|
27 |
-
return "## Search Results\n\n" + "\n\n".join(postprocessed_results)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|