Spaces:
Runtime error
Runtime error
project transferred to Langgraph implementation. |
Browse filesAdd .gitignore, enhance app.py with Azure OpenAI integration, update README for setup instructions, and modify requirements.txt for new dependencies
- .gitignore +149 -0
- README.md +36 -1
- app.py +79 -24
- requirements.txt +7 -1
- retriever.py +80 -26
- test_tavily.py +182 -0
- tools.py +113 -50
.gitignore
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment variables
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# Python
|
| 5 |
+
# Byte-compiled / optimized / DLL files
|
| 6 |
+
__pycache__/
|
| 7 |
+
*.py[cod]
|
| 8 |
+
*$py.class
|
| 9 |
+
|
| 10 |
+
# C extensions
|
| 11 |
+
*.so
|
| 12 |
+
|
| 13 |
+
# Distribution / packaging
|
| 14 |
+
.Python
|
| 15 |
+
build/
|
| 16 |
+
develop-eggs/
|
| 17 |
+
dist/
|
| 18 |
+
downloads/
|
| 19 |
+
eggs/
|
| 20 |
+
.eggs/
|
| 21 |
+
lib/
|
| 22 |
+
lib64/
|
| 23 |
+
parts/
|
| 24 |
+
sdist/
|
| 25 |
+
var/
|
| 26 |
+
wheels/
|
| 27 |
+
pip-wheel-metadata/
|
| 28 |
+
share/python-wheels/
|
| 29 |
+
*.egg-info/
|
| 30 |
+
.installed.cfg
|
| 31 |
+
*.egg
|
| 32 |
+
MANIFEST
|
| 33 |
+
|
| 34 |
+
# PyInstaller
|
| 35 |
+
# Usually these files are written by a python script from a template
|
| 36 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 37 |
+
*.manifest
|
| 38 |
+
*.spec
|
| 39 |
+
|
| 40 |
+
# Installer logs
|
| 41 |
+
pip-log.txt
|
| 42 |
+
pip-delete-this-directory.txt
|
| 43 |
+
|
| 44 |
+
# Unit test / coverage reports
|
| 45 |
+
htmlcov/
|
| 46 |
+
.tox/
|
| 47 |
+
.nox/
|
| 48 |
+
.coverage
|
| 49 |
+
.coverage.*
|
| 50 |
+
.cache
|
| 51 |
+
nosetests.xml
|
| 52 |
+
coverage.xml
|
| 53 |
+
*.cover
|
| 54 |
+
*.py,cover
|
| 55 |
+
.hypothesis/
|
| 56 |
+
.pytest_cache/
|
| 57 |
+
|
| 58 |
+
# Translations
|
| 59 |
+
*.mo
|
| 60 |
+
*.pot
|
| 61 |
+
|
| 62 |
+
# Django stuff:
|
| 63 |
+
*.log
|
| 64 |
+
local_settings.py
|
| 65 |
+
db.sqlite3
|
| 66 |
+
db.sqlite3-journal
|
| 67 |
+
|
| 68 |
+
# Flask stuff:
|
| 69 |
+
instance/
|
| 70 |
+
.webassets-cache
|
| 71 |
+
|
| 72 |
+
# Scrapy stuff:
|
| 73 |
+
.scrapy
|
| 74 |
+
|
| 75 |
+
# Sphinx documentation
|
| 76 |
+
docs/_build/
|
| 77 |
+
|
| 78 |
+
# PyBuilder
|
| 79 |
+
target/
|
| 80 |
+
|
| 81 |
+
# Jupyter Notebook
|
| 82 |
+
.ipynb_checkpoints
|
| 83 |
+
|
| 84 |
+
# IPython
|
| 85 |
+
profile_default/
|
| 86 |
+
ipython_config.py
|
| 87 |
+
|
| 88 |
+
# pyenv
|
| 89 |
+
.python-version
|
| 90 |
+
|
| 91 |
+
# pipenv
|
| 92 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 93 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 94 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 95 |
+
# install all needed dependencies.
|
| 96 |
+
#Pipfile.lock
|
| 97 |
+
|
| 98 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
| 99 |
+
__pypackages__/
|
| 100 |
+
|
| 101 |
+
# Celery stuff
|
| 102 |
+
celerybeat-schedule
|
| 103 |
+
celerybeat.pid
|
| 104 |
+
|
| 105 |
+
# SageMath parsed files
|
| 106 |
+
*.sage.py
|
| 107 |
+
|
| 108 |
+
# Environments
|
| 109 |
+
.env
|
| 110 |
+
.venv
|
| 111 |
+
env/
|
| 112 |
+
venv/
|
| 113 |
+
ENV/
|
| 114 |
+
env.bak/
|
| 115 |
+
venv.bak/
|
| 116 |
+
|
| 117 |
+
# Spyder project settings
|
| 118 |
+
.spyderproject
|
| 119 |
+
.spyproject
|
| 120 |
+
|
| 121 |
+
# Rope project settings
|
| 122 |
+
.ropeproject
|
| 123 |
+
|
| 124 |
+
# mkdocs documentation
|
| 125 |
+
/site
|
| 126 |
+
|
| 127 |
+
# mypy
|
| 128 |
+
.mypy_cache/
|
| 129 |
+
.dmypy.json
|
| 130 |
+
dmypy.json
|
| 131 |
+
|
| 132 |
+
# Pyre type checker
|
| 133 |
+
.pyre/
|
| 134 |
+
|
| 135 |
+
# IDE
|
| 136 |
+
.vscode/
|
| 137 |
+
.idea/
|
| 138 |
+
*.swp
|
| 139 |
+
*.swo
|
| 140 |
+
*~
|
| 141 |
+
|
| 142 |
+
# OS
|
| 143 |
+
.DS_Store
|
| 144 |
+
.DS_Store?
|
| 145 |
+
._*
|
| 146 |
+
.Spotlight-V100
|
| 147 |
+
.Trashes
|
| 148 |
+
ehthumbs.db
|
| 149 |
+
Thumbs.db
|
README.md
CHANGED
|
@@ -10,4 +10,39 @@ pinned: false
|
|
| 10 |
license: apache-2.0
|
| 11 |
---
|
| 12 |
|
| 13 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
license: apache-2.0
|
| 11 |
---
|
| 12 |
|
| 13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 14 |
+
|
| 15 |
+
# Agentic RAG with LangGraph
|
| 16 |
+
|
| 17 |
+
A simple agentic RAG system using LangGraph with Azure OpenAI.
|
| 18 |
+
|
| 19 |
+
## Setup
|
| 20 |
+
|
| 21 |
+
1. **Install dependencies:**
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
pip install -r requirements.txt
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
2. **Create `.env` file in the project root:**
|
| 28 |
+
|
| 29 |
+
```
|
| 30 |
+
AZURE_OPENAI_ENDPOINT=your_azure_endpoint
|
| 31 |
+
AZURE_OPENAI_API_KEY=your_api_key
|
| 32 |
+
AZURE_OPENAI_DEPLOYMENT_NAME=your_deployment_name
|
| 33 |
+
AZURE_OPENAI_API_VERSION=2024-02-01
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
## Run
|
| 37 |
+
|
| 38 |
+
```bash
|
| 39 |
+
python app.py
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
The agent will automatically query for "Lady Ada Lovelace" and show the response.
|
| 43 |
+
|
| 44 |
+
## Tools Available
|
| 45 |
+
|
| 46 |
+
- **Guest Info**: Retrieves guest information from the dataset
|
| 47 |
+
- **Weather Info**: Provides dummy weather data
|
| 48 |
+
- **Hub Stats**: Gets Hugging Face model statistics
|
app.py
CHANGED
|
@@ -1,33 +1,88 @@
|
|
| 1 |
-
import
|
| 2 |
-
import
|
| 3 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
-
from tools import DuckDuckGoSearchTool, WeatherInfoTool, HubStatsTool
|
| 7 |
-
from retriever import load_guest_dataset
|
| 8 |
|
| 9 |
-
# Initialize the Hugging Face model
|
| 10 |
-
model = HfApiModel()
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
# Initialize the weather tool
|
| 16 |
-
weather_info_tool = WeatherInfoTool()
|
| 17 |
|
| 18 |
-
#
|
| 19 |
-
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
|
|
|
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
| 30 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import TypedDict, Annotated
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
from langgraph.graph.message import add_messages
|
| 5 |
+
from langchain_core.messages import AnyMessage, HumanMessage, AIMessage
|
| 6 |
+
from langgraph.prebuilt import ToolNode
|
| 7 |
+
from langgraph.graph import START, StateGraph
|
| 8 |
+
from langgraph.prebuilt import tools_condition
|
| 9 |
+
from langchain_openai import AzureChatOpenAI
|
| 10 |
+
from retriever import guest_info_tool
|
| 11 |
+
from tools import weather_info_tool, hub_stats_tool, news_search_tool
|
| 12 |
|
| 13 |
+
load_dotenv()
|
|
|
|
|
|
|
| 14 |
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
chat = AzureChatOpenAI(
|
| 17 |
+
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
|
| 18 |
+
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
|
| 19 |
+
deployment_name=os.getenv("DEPLOYMENT_NAME"),
|
| 20 |
+
openai_api_version=os.getenv("OPENAI_API_VERSION"),
|
| 21 |
+
temperature=0.75,
|
| 22 |
+
streaming=True,
|
| 23 |
+
verbose=True
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
tools = [guest_info_tool, weather_info_tool, hub_stats_tool, news_search_tool]
|
| 27 |
+
chat_with_tools = chat.bind_tools(tools)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class AgentState(TypedDict):
|
| 31 |
+
messages: Annotated[list[AnyMessage], add_messages]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def assistant(state: AgentState):
|
| 35 |
+
return {
|
| 36 |
+
"messages": [chat_with_tools.invoke(state["messages"])],
|
| 37 |
+
}
|
| 38 |
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
# The graph
|
| 41 |
+
builder = StateGraph(AgentState)
|
| 42 |
|
| 43 |
+
# Define nodes: these do the work
|
| 44 |
+
builder.add_node("assistant", assistant)
|
| 45 |
+
builder.add_node("tools", ToolNode(tools))
|
| 46 |
|
| 47 |
+
# Define edges: these determine how the control flow moves
|
| 48 |
+
builder.add_edge(START, "assistant")
|
| 49 |
+
builder.add_conditional_edges(
|
| 50 |
+
"assistant",
|
| 51 |
+
# If the latest message requires a tool, route to tools
|
| 52 |
+
# Otherwise, provide a direct response
|
| 53 |
+
tools_condition,
|
| 54 |
)
|
| 55 |
+
builder.add_edge("tools", "assistant")
|
| 56 |
+
|
| 57 |
+
# Compile with debug mode for verbosity
|
| 58 |
+
alfred = builder.compile(debug=True)
|
| 59 |
+
|
| 60 |
+
messages = [HumanMessage(
|
| 61 |
+
content="One of our guests is from Qwen. What can you tell me about their most recent popular AI model(search about it )?")]
|
| 62 |
+
|
| 63 |
+
print("🔍 Starting Agent Execution...")
|
| 64 |
+
print("="*50)
|
| 65 |
+
|
| 66 |
+
# Use stream instead of invoke to see step-by-step execution
|
| 67 |
+
final_messages = None
|
| 68 |
+
for step in alfred.stream({"messages": messages}):
|
| 69 |
+
print(f"📍 Current Step: {list(step.keys())}")
|
| 70 |
+
for node_name, node_output in step.items():
|
| 71 |
+
print(f"🔧 Node '{node_name}' output:")
|
| 72 |
+
if 'messages' in node_output:
|
| 73 |
+
latest_message = node_output['messages'][-1]
|
| 74 |
+
# Keep track of final messages
|
| 75 |
+
final_messages = node_output['messages']
|
| 76 |
+
print(f" Type: {type(latest_message).__name__}")
|
| 77 |
+
if hasattr(latest_message, 'content'):
|
| 78 |
+
print(f" Content: {latest_message.content[:200]}...")
|
| 79 |
+
if hasattr(latest_message, 'tool_calls') and latest_message.tool_calls:
|
| 80 |
+
print(f" Tool Calls: {latest_message.tool_calls}")
|
| 81 |
+
print("-" * 30)
|
| 82 |
+
|
| 83 |
|
| 84 |
+
print("\n"*3)
|
| 85 |
+
print("="*50)
|
| 86 |
+
print("🎩 Alfred's Final Response:")
|
| 87 |
+
if final_messages:
|
| 88 |
+
print(final_messages[-1].content)
|
requirements.txt
CHANGED
|
@@ -2,4 +2,10 @@ datasets
|
|
| 2 |
smolagents
|
| 3 |
langchain-community
|
| 4 |
rank_bm25
|
| 5 |
-
duckduckgo-search
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
smolagents
|
| 3 |
langchain-community
|
| 4 |
rank_bm25
|
| 5 |
+
duckduckgo-search
|
| 6 |
+
python-dotenv
|
| 7 |
+
langchain
|
| 8 |
+
langchain-openai
|
| 9 |
+
langgraph
|
| 10 |
+
huggingface_hub
|
| 11 |
+
langchain-tavily
|
retriever.py
CHANGED
|
@@ -1,36 +1,30 @@
|
|
| 1 |
-
from
|
| 2 |
from langchain_community.retrievers import BM25Retriever
|
| 3 |
from langchain.docstore.document import Document
|
|
|
|
| 4 |
import datasets
|
|
|
|
|
|
|
|
|
|
| 5 |
|
|
|
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def __init__(self, docs):
|
| 19 |
-
self.is_initialized = False
|
| 20 |
-
self.retriever = BM25Retriever.from_documents(docs)
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def forward(self, query: str):
|
| 24 |
-
results = self.retriever.get_relevant_documents(query)
|
| 25 |
-
if results:
|
| 26 |
-
return "\n\n".join([doc.page_content for doc in results[:3]])
|
| 27 |
-
else:
|
| 28 |
-
return "No matching guest information found."
|
| 29 |
|
| 30 |
|
| 31 |
def load_guest_dataset():
|
| 32 |
# Load the dataset
|
| 33 |
-
guest_dataset = datasets.load_dataset(
|
|
|
|
| 34 |
|
| 35 |
# Convert dataset entries into Document objects
|
| 36 |
docs = [
|
|
@@ -46,8 +40,68 @@ def load_guest_dataset():
|
|
| 46 |
for guest in guest_dataset
|
| 47 |
]
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain.tools import Tool
|
| 2 |
from langchain_community.retrievers import BM25Retriever
|
| 3 |
from langchain.docstore.document import Document
|
| 4 |
+
from langchain_core.messages import HumanMessage
|
| 5 |
import datasets
|
| 6 |
+
from langchain_openai import AzureChatOpenAI
|
| 7 |
+
import os
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
|
| 10 |
+
load_dotenv()
|
| 11 |
|
| 12 |
+
# Create LLM instance once
|
| 13 |
+
conversation_llm = AzureChatOpenAI(
|
| 14 |
+
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
|
| 15 |
+
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
|
| 16 |
+
deployment_name=os.getenv("DEPLOYMENT_NAME"),
|
| 17 |
+
openai_api_version=os.getenv("OPENAI_API_VERSION"),
|
| 18 |
+
temperature=0.75,
|
| 19 |
+
streaming=False,
|
| 20 |
+
verbose=False
|
| 21 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
def load_guest_dataset():
|
| 25 |
# Load the dataset
|
| 26 |
+
guest_dataset = datasets.load_dataset(
|
| 27 |
+
"agents-course/unit3-invitees", split="train")
|
| 28 |
|
| 29 |
# Convert dataset entries into Document objects
|
| 30 |
docs = [
|
|
|
|
| 40 |
for guest in guest_dataset
|
| 41 |
]
|
| 42 |
|
| 43 |
+
return docs
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
docs = load_guest_dataset()
|
| 47 |
+
bm25_retriever = BM25Retriever.from_documents(docs)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def generate_conversation_starter(description: str) -> str:
|
| 51 |
+
"""Generate a conversation starter based on guest description"""
|
| 52 |
+
try:
|
| 53 |
+
generate_prompt = (
|
| 54 |
+
f"Generate a very simple and short conversation starter from the description of the person.\n\n"
|
| 55 |
+
f"For example:\n"
|
| 56 |
+
f"Description: Rear Admiral Grace Hopper was a trailblazer in computer programming and helped invent the first compiler. "
|
| 57 |
+
f"She's passionate about teaching and loves telling stories about debugging.\n\n"
|
| 58 |
+
f"Conversation Starter: Ask her about the time she found a real bug in a computer — she loves that story!\n\n"
|
| 59 |
+
f"Description: {description}\n\n"
|
| 60 |
+
f"Conversation Starter:"
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
response = conversation_llm.invoke(
|
| 64 |
+
[HumanMessage(content=generate_prompt)])
|
| 65 |
+
return response.content.strip()
|
| 66 |
+
except Exception:
|
| 67 |
+
return "Ask them about their background and interests!"
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def retrieve_info_from_name(query: str) -> str:
|
| 71 |
+
"""Retrieves detailed information about gala guests based on their name or relation."""
|
| 72 |
+
results = bm25_retriever.invoke(query)
|
| 73 |
+
if results:
|
| 74 |
+
guest_info_with_starters = []
|
| 75 |
+
|
| 76 |
+
for i, doc in enumerate(results[:3], 1):
|
| 77 |
+
guest_info = doc.page_content
|
| 78 |
+
|
| 79 |
+
# Extract description from the content
|
| 80 |
+
lines = guest_info.split('\n')
|
| 81 |
+
description = ""
|
| 82 |
+
for line in lines:
|
| 83 |
+
if line.startswith("Description:"):
|
| 84 |
+
description = line.replace("Description:", "").strip()
|
| 85 |
+
break
|
| 86 |
+
|
| 87 |
+
# Add guest info
|
| 88 |
+
result_text = f"Guest {i}:\n{guest_info}"
|
| 89 |
+
|
| 90 |
+
# Add conversation starter if description exists
|
| 91 |
+
if description:
|
| 92 |
+
conversation_starter = generate_conversation_starter(
|
| 93 |
+
description)
|
| 94 |
+
result_text += f"\n💬 Conversation Starter: {conversation_starter}"
|
| 95 |
+
|
| 96 |
+
guest_info_with_starters.append(result_text)
|
| 97 |
|
| 98 |
+
return "\n\n" + "="*50 + "\n\n".join(guest_info_with_starters)
|
| 99 |
+
else:
|
| 100 |
+
return "No matching guest information found."
|
| 101 |
|
| 102 |
|
| 103 |
+
guest_info_tool = Tool(
|
| 104 |
+
name="guest_info_retriever",
|
| 105 |
+
func=retrieve_info_from_name,
|
| 106 |
+
description="Retrieves detailed information about gala guests based on their name or relation, including conversation starters."
|
| 107 |
+
)
|
test_tavily.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
from langchain_tavily import TavilySearch
|
| 4 |
+
|
| 5 |
+
# Load environment variables
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_tavily_search():
|
| 10 |
+
"""Test the Tavily search functionality independently"""
|
| 11 |
+
|
| 12 |
+
print("🔍 Testing Tavily Search Tool...")
|
| 13 |
+
print("="*50)
|
| 14 |
+
|
| 15 |
+
# Check if API key is available
|
| 16 |
+
api_key = os.getenv("TAVILY_API_KEY")
|
| 17 |
+
if not api_key:
|
| 18 |
+
print("❌ Error: TAVILY_API_KEY not found in environment variables")
|
| 19 |
+
print("Please add TAVILY_API_KEY to your .env file")
|
| 20 |
+
return
|
| 21 |
+
|
| 22 |
+
print(f"✅ API Key found: {api_key[:10]}...{api_key[-4:]}")
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
# Initialize Tavily search
|
| 26 |
+
print("\n🚀 Initializing Tavily search...")
|
| 27 |
+
tavily_search = TavilySearch(
|
| 28 |
+
api_key=api_key,
|
| 29 |
+
max_results=2,
|
| 30 |
+
topic="general",
|
| 31 |
+
search_depth="basic",
|
| 32 |
+
include_answer=True,
|
| 33 |
+
include_raw_content=False,
|
| 34 |
+
include_images=False
|
| 35 |
+
)
|
| 36 |
+
print("✅ Tavily search initialized successfully")
|
| 37 |
+
|
| 38 |
+
# Test search query
|
| 39 |
+
test_query = "latest portugal won"
|
| 40 |
+
print(f"\n🔎 Searching for: '{test_query}'")
|
| 41 |
+
|
| 42 |
+
# Perform the search
|
| 43 |
+
results = tavily_search.invoke({"query": test_query})
|
| 44 |
+
|
| 45 |
+
print(f"\n📊 Results type: {type(results)}")
|
| 46 |
+
print(
|
| 47 |
+
f"📊 Results length: {len(results) if hasattr(results, '__len__') else 'N/A'}")
|
| 48 |
+
|
| 49 |
+
# Display results
|
| 50 |
+
print("\n📰 Raw Results:")
|
| 51 |
+
print("-" * 30)
|
| 52 |
+
print(results)
|
| 53 |
+
print("-" * 30)
|
| 54 |
+
|
| 55 |
+
# Process and format results
|
| 56 |
+
if isinstance(results, list) and results:
|
| 57 |
+
print(f"\n✅ Found {len(results)} results")
|
| 58 |
+
formatted_news = f"📰 Latest News about '{test_query}':\n\n"
|
| 59 |
+
|
| 60 |
+
for i, result in enumerate(results, 1):
|
| 61 |
+
print(f"\n🔍 Processing result {i}:")
|
| 62 |
+
print(f" Type: {type(result)}")
|
| 63 |
+
|
| 64 |
+
if isinstance(result, dict):
|
| 65 |
+
print(f" Keys: {list(result.keys())}")
|
| 66 |
+
title = result.get('title', 'No title')
|
| 67 |
+
content = result.get('content', 'No content available')
|
| 68 |
+
url = result.get('url', 'No URL')
|
| 69 |
+
|
| 70 |
+
formatted_news += f"{i}. **{title}**\n"
|
| 71 |
+
formatted_news += f" Summary: {content[:200]}...\n"
|
| 72 |
+
formatted_news += f" Source: {url}\n\n"
|
| 73 |
+
else:
|
| 74 |
+
# Handle case where result is a string
|
| 75 |
+
formatted_news += f"{i}. {str(result)[:300]}...\n\n"
|
| 76 |
+
|
| 77 |
+
print(f"\n📝 Formatted Output:")
|
| 78 |
+
print("="*50)
|
| 79 |
+
print(formatted_news)
|
| 80 |
+
print("="*50)
|
| 81 |
+
|
| 82 |
+
else:
|
| 83 |
+
print(f"❌ No results found or unexpected result format")
|
| 84 |
+
print(f"Results: {results}")
|
| 85 |
+
|
| 86 |
+
except Exception as e:
|
| 87 |
+
print(f"❌ Error during search: {e}")
|
| 88 |
+
print(f"Error type: {type(e)}")
|
| 89 |
+
import traceback
|
| 90 |
+
print(f"Full traceback:\n{traceback.format_exc()}")
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def test_tavily_raw():
|
| 94 |
+
"""Test the raw Tavily search functionality"""
|
| 95 |
+
|
| 96 |
+
print("🔍 Testing Raw Tavily Search...")
|
| 97 |
+
print("="*50)
|
| 98 |
+
|
| 99 |
+
# Check if API key is available
|
| 100 |
+
api_key = os.getenv("TAVILY_API_KEY")
|
| 101 |
+
if not api_key:
|
| 102 |
+
print("❌ Error: TAVILY_API_KEY not found in environment variables")
|
| 103 |
+
return False
|
| 104 |
+
|
| 105 |
+
try:
|
| 106 |
+
# Initialize Tavily search
|
| 107 |
+
tavily_search = TavilySearch(
|
| 108 |
+
api_key=api_key,
|
| 109 |
+
max_results=2,
|
| 110 |
+
topic="general",
|
| 111 |
+
search_depth="basic",
|
| 112 |
+
include_answer=True,
|
| 113 |
+
include_raw_content=False,
|
| 114 |
+
include_images=False
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
# Test search query
|
| 118 |
+
test_query = "latest portugal won"
|
| 119 |
+
print(f"🔎 Searching for: '{test_query}'")
|
| 120 |
+
|
| 121 |
+
# Perform the search
|
| 122 |
+
results = tavily_search.invoke({"query": test_query})
|
| 123 |
+
|
| 124 |
+
print(f"✅ Raw search successful!")
|
| 125 |
+
print(f"📊 Results type: {type(results)}")
|
| 126 |
+
if isinstance(results, dict) and 'results' in results:
|
| 127 |
+
print(f"📊 Number of results: {len(results['results'])}")
|
| 128 |
+
return True
|
| 129 |
+
return False
|
| 130 |
+
|
| 131 |
+
except Exception as e:
|
| 132 |
+
print(f"❌ Raw search failed: {e}")
|
| 133 |
+
return False
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def test_tools_function():
|
| 137 |
+
"""Test our get_latest_news function from tools.py"""
|
| 138 |
+
|
| 139 |
+
print("\n🔧 Testing get_latest_news Function...")
|
| 140 |
+
print("="*50)
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
# Import our function
|
| 144 |
+
from tools import get_latest_news
|
| 145 |
+
|
| 146 |
+
# Test the function
|
| 147 |
+
test_query = "portugal won"
|
| 148 |
+
print(f"🔎 Testing with query: '{test_query}'")
|
| 149 |
+
|
| 150 |
+
result = get_latest_news(test_query)
|
| 151 |
+
|
| 152 |
+
print("✅ Function executed successfully!")
|
| 153 |
+
print("\n📰 Function Output:")
|
| 154 |
+
print("-" * 50)
|
| 155 |
+
print(result)
|
| 156 |
+
print("-" * 50)
|
| 157 |
+
|
| 158 |
+
return True
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
print(f"❌ Function test failed: {e}")
|
| 162 |
+
import traceback
|
| 163 |
+
print(f"Full traceback:\n{traceback.format_exc()}")
|
| 164 |
+
return False
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
if __name__ == "__main__":
|
| 168 |
+
print("🧪 Running Tavily Tests...\n")
|
| 169 |
+
|
| 170 |
+
# Test 1: Raw Tavily
|
| 171 |
+
raw_success = test_tavily_raw()
|
| 172 |
+
|
| 173 |
+
# Test 2: Our function
|
| 174 |
+
if raw_success:
|
| 175 |
+
function_success = test_tools_function()
|
| 176 |
+
|
| 177 |
+
if function_success:
|
| 178 |
+
print("\n🎉 All tests passed! The news search tool is working correctly.")
|
| 179 |
+
else:
|
| 180 |
+
print("\n⚠️ Raw search works but our function has issues.")
|
| 181 |
+
else:
|
| 182 |
+
print("\n❌ Raw search failed - check your API key and connection.")
|
tools.py
CHANGED
|
@@ -1,56 +1,119 @@
|
|
| 1 |
-
from
|
| 2 |
-
from smolagents import Tool
|
| 3 |
import random
|
|
|
|
| 4 |
from huggingface_hub import list_models
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
# Initialize the DuckDuckGo search tool
|
| 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 |
|
|
|
|
|
|
| 1 |
+
from langchain.tools import Tool
|
|
|
|
| 2 |
import random
|
| 3 |
+
import os
|
| 4 |
from huggingface_hub import list_models
|
| 5 |
+
# from langchain_tavily import TavilySearch
|
| 6 |
+
from langchain_community.tools import DuckDuckGoSearchRun
|
| 7 |
+
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
|
| 10 |
+
load_dotenv()
|
| 11 |
|
| 12 |
|
| 13 |
# Initialize the DuckDuckGo search tool
|
| 14 |
+
search_tool = DuckDuckGoSearchRun()
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def get_weather_info(location: str) -> str:
|
| 19 |
+
"""Fetches dummy weather information for a given location."""
|
| 20 |
+
# Dummy weather data
|
| 21 |
+
weather_conditions = [
|
| 22 |
+
{"condition": "Rainy", "temp_c": 15},
|
| 23 |
+
{"condition": "Clear", "temp_c": 25},
|
| 24 |
+
{"condition": "Windy", "temp_c": 20},
|
| 25 |
+
]
|
| 26 |
+
# Randomly select a weather condition
|
| 27 |
+
data = random.choice(weather_conditions)
|
| 28 |
+
return f"Weather in {location}: {data['condition']}, {data['temp_c']}°C"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def get_hub_stats(author: str) -> str:
|
| 32 |
+
"""Fetches the most downloaded model from a specific author on the Hugging Face Hub."""
|
| 33 |
+
try:
|
| 34 |
+
# List models from the specified author, sorted by downloads
|
| 35 |
+
models = list(list_models(
|
| 36 |
+
author=author, sort="downloads", direction=-1, limit=1))
|
| 37 |
+
|
| 38 |
+
if models:
|
| 39 |
+
model = models[0]
|
| 40 |
+
return f"The most downloaded model by {author} is {model.id} with {model.downloads:,} downloads."
|
| 41 |
+
else:
|
| 42 |
+
return f"No models found for author {author}."
|
| 43 |
+
except Exception as e:
|
| 44 |
+
return f"Error fetching models for {author}: {str(e)}"
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# def get_latest_news(topic: str) -> str:
|
| 48 |
+
# """Fetches the latest news about a specific topic using Tavily search."""
|
| 49 |
+
# try:
|
| 50 |
+
# # Initialize Tavily search with API key from environment
|
| 51 |
+
# tavily_search = TavilySearch(
|
| 52 |
+
# api_key=os.getenv("TAVILY_API_KEY"),
|
| 53 |
+
# max_results=2,
|
| 54 |
+
# topic="general",
|
| 55 |
+
# search_depth="basic",
|
| 56 |
+
# include_answer=True,
|
| 57 |
+
# include_raw_content=False,
|
| 58 |
+
# include_images=False
|
| 59 |
+
# )
|
| 60 |
+
|
| 61 |
+
# # Search for news about the topic
|
| 62 |
+
# response = tavily_search.invoke(
|
| 63 |
+
# {"query": f"latest news about {topic}"})
|
| 64 |
+
|
| 65 |
+
# # Handle the correct Tavily response format
|
| 66 |
+
# if isinstance(response, dict) and 'results' in response:
|
| 67 |
+
# results = response['results']
|
| 68 |
+
# answer = response.get('answer', '')
|
| 69 |
+
|
| 70 |
+
# if results:
|
| 71 |
+
# # Format the results nicely
|
| 72 |
+
# formatted_news = f"📰 Latest News about '{topic}':\n\n"
|
| 73 |
+
|
| 74 |
+
# # Add AI-generated answer if available
|
| 75 |
+
# if answer:
|
| 76 |
+
# formatted_news += f"🤖 **Quick Summary**: {answer}\n\n"
|
| 77 |
+
|
| 78 |
+
# # Add detailed results
|
| 79 |
+
# formatted_news += "📋 **Detailed Results**:\n\n"
|
| 80 |
+
# for i, result in enumerate(results, 1):
|
| 81 |
+
# title = result.get('title', 'No title')
|
| 82 |
+
# content = result.get('content', 'No content available')
|
| 83 |
+
# url = result.get('url', 'No URL')
|
| 84 |
+
# score = result.get('score', 0)
|
| 85 |
+
|
| 86 |
+
# formatted_news += f"{i}. **{title}**\n"
|
| 87 |
+
# formatted_news += f" 📄 Summary: {content}\n"
|
| 88 |
+
# formatted_news += f" 🔗 Source: {url}\n"
|
| 89 |
+
# formatted_news += f" ⭐ Relevance: {score:.2f}\n\n"
|
| 90 |
+
|
| 91 |
+
# return formatted_news
|
| 92 |
+
# else:
|
| 93 |
+
# return f"No recent news found about '{topic}'. Please try a different search term."
|
| 94 |
+
# else:
|
| 95 |
+
# return f"Unexpected response format from search. Raw response: {str(response)[:500]}..."
|
| 96 |
+
|
| 97 |
+
# except Exception as e:
|
| 98 |
+
# return f"Error fetching news about '{topic}': {str(e)}. Please check your Tavily API key and try again."
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
weather_info_tool = Tool(
|
| 102 |
+
name="weather_info",
|
| 103 |
+
func=get_weather_info,
|
| 104 |
+
description="Fetches dummy weather information for a given location."
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
hub_stats_tool = Tool(
|
| 108 |
+
name="hub_stats",
|
| 109 |
+
func=get_hub_stats,
|
| 110 |
+
description="Fetches the most downloaded model from a specific author on the Hugging Face Hub."
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# news_search_tool = Tool(
|
| 114 |
+
# name="news_search",
|
| 115 |
+
# func=get_latest_news,
|
| 116 |
+
# description="Fetches the latest news about a specific topic using Tavily search. Provide a topic or keyword to search for recent news articles."
|
| 117 |
+
# )
|
| 118 |
|
| 119 |
+
news_search_tool = search_tool
|