Kunal commited on
Commit
deb711d
·
1 Parent(s): 00fb7aa

added anime agent Config files

Browse files
requirements.txt CHANGED
@@ -1,3 +1,10 @@
1
  altair
2
  pandas
3
- streamlit
 
 
 
 
 
 
 
 
1
  altair
2
  pandas
3
+ streamlit
4
+ smolagents
5
+ smolagents[toolkit]
6
+ streamlit
7
+ DuckDuckGoSearchTool
8
+ requests
9
+ smolagents[openai]
10
+ duckduckgo-search
src/__pycache__/agent.cpython-313.pyc ADDED
Binary file (1.41 kB). View file
 
src/__pycache__/auth.cpython-313.pyc ADDED
Binary file (6.26 kB). View file
 
src/__pycache__/tools.cpython-313.pyc ADDED
Binary file (8.63 kB). View file
 
src/agent.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tools import add_anime, remove_anime, get_anime_list, search_anime, format_anime_list, display_anime_cards, hianime_watchlink, anime_suggestion
2
+ from smolagents import CodeAgent, OpenAIServerModel
3
+ from smolagents import DuckDuckGoSearchTool
4
+
5
+
6
+ GEMINI_API_KEY = "AIzaSyDrSVoJnOEZclbciDf5hyor665iHG1nQsQ"
7
+ DEEPSEEK_API_KEY = "sk-5901a93f69824788a61a2bc68925a3a9"
8
+
9
+ def initialize_agent():
10
+ """
11
+ Initialize the agent with necessary tools and configurations.
12
+ """
13
+ tools = [
14
+ add_anime,
15
+ remove_anime,
16
+ get_anime_list,
17
+ search_anime,
18
+ format_anime_list,
19
+ display_anime_cards,
20
+ hianime_watchlink,
21
+ anime_suggestion,
22
+ DuckDuckGoSearchTool()
23
+ ]
24
+
25
+ # Configure models
26
+ gemini_llm = OpenAIServerModel(
27
+ model_id="gemini-2.0-flash",
28
+ api_base="https://generativelanguage.googleapis.com/v1beta/openai/",
29
+ api_key=GEMINI_API_KEY,
30
+ max_tokens=1096,
31
+ temperature=0.5,
32
+ )
33
+
34
+ deepseek_llm = OpenAIServerModel(
35
+ model_id="deepseek-chat",
36
+ api_base="https://api.deepseek.com",
37
+ api_key=DEEPSEEK_API_KEY,
38
+ )
39
+
40
+
41
+
42
+ return CodeAgent(
43
+ model=gemini_llm, # Changed parameter to 'llm'
44
+ tools=tools,
45
+ # max_tokens=4096,
46
+ # temperature=0.5
47
+ )
src/auth.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # auth.py
2
+ import streamlit as st
3
+ import requests
4
+ import webbrowser
5
+ import secrets
6
+ import threading
7
+ import time
8
+ from http.server import HTTPServer, BaseHTTPRequestHandler
9
+ from urllib.parse import urlparse, parse_qs
10
+
11
+ # Configuration (move these to secrets.toml in production)
12
+ CLIENT_ID = "e85f0329ae62138e962530f300c6549b"
13
+ CLIENT_SECRET = "6a2bb65ea4fb863507cf7cf0452aa5c59825f142eee3c6bd639846155dddcec0"
14
+ REDIRECT_URI = "http://localhost:8080/callback"
15
+ PORT = 8080
16
+
17
+ class MALAuth:
18
+ def __init__(self):
19
+ self.code_verifier = secrets.token_urlsafe(64)
20
+ self.code_challenge = self.code_verifier # Using 'plain' method
21
+ self.auth_code = None
22
+ self.error = None
23
+
24
+ class OAuthHandler(BaseHTTPRequestHandler):
25
+ auth_code = None
26
+ error = None
27
+
28
+ def do_GET(self):
29
+ parsed = urlparse(self.path)
30
+ params = parse_qs(parsed.query)
31
+ if "code" in params:
32
+ MALAuth.OAuthHandler.auth_code = params["code"][0]
33
+ self.send_response(200)
34
+ self.end_headers()
35
+ self.wfile.write(b"Authorization successful! Return to the app.")
36
+ elif "error" in params:
37
+ MALAuth.OAuthHandler.error = params["error"][0]
38
+ self.send_response(400)
39
+ self.end_headers()
40
+ self.wfile.write(b"Authorization failed. Check your settings.")
41
+ else:
42
+ self.send_response(400)
43
+ self.end_headers()
44
+ self.wfile.write(b"Invalid request")
45
+
46
+ def run_server(self):
47
+ server = HTTPServer(('localhost', PORT), self.OAuthHandler)
48
+ server.timeout = 120
49
+ server.handle_request()
50
+
51
+ def start_oauth_flow(self):
52
+ server_thread = threading.Thread(target=self.run_server)
53
+ server_thread.daemon = True
54
+ server_thread.start()
55
+
56
+ auth_url = (
57
+ "https://myanimelist.net/v1/oauth2/authorize?"
58
+ f"response_type=code&"
59
+ f"client_id={CLIENT_ID}&"
60
+ f"code_challenge={self.code_challenge}&"
61
+ f"redirect_uri={REDIRECT_URI}"
62
+ )
63
+ webbrowser.open(auth_url)
64
+
65
+ start_time = time.time()
66
+ while not self.OAuthHandler.auth_code and not self.OAuthHandler.error:
67
+ if time.time() - start_time > 120:
68
+ return None, "Authorization timed out"
69
+ time.sleep(0.5)
70
+
71
+ return self.OAuthHandler.auth_code, self.OAuthHandler.error
72
+
73
+ def get_access_token(self, auth_code):
74
+ token_url = "https://myanimelist.net/v1/oauth2/token"
75
+ data = {
76
+ "client_id": CLIENT_ID,
77
+ "client_secret": CLIENT_SECRET,
78
+ "code": auth_code,
79
+ "code_verifier": self.code_verifier,
80
+ "grant_type": "authorization_code",
81
+ "redirect_uri": REDIRECT_URI
82
+ }
83
+ try:
84
+ response = requests.post(token_url, data=data)
85
+ response.raise_for_status()
86
+ return response.json()["access_token"], None
87
+ except requests.exceptions.HTTPError as e:
88
+ return None, f"Token exchange failed: {e.response.status_code} {e.response.text}"
89
+
90
+ def login_button():
91
+ """Streamlit component for handling MAL login"""
92
+ if st.button("Login with MyAnimeList"):
93
+ with st.spinner("Authenticating..."):
94
+ auth = MALAuth()
95
+ auth_code, error = auth.start_oauth_flow()
96
+
97
+ if error:
98
+ st.error(error)
99
+ return False
100
+
101
+ access_token, error = auth.get_access_token(auth_code)
102
+ if error:
103
+ st.error(error)
104
+ return False
105
+
106
+ st.session_state.access_token = access_token
107
+ st.rerun()
108
+ return True
src/streamlit_app.py CHANGED
@@ -1,40 +1,112 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
 
 
 
 
 
 
 
 
8
 
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
 
 
 
12
 
13
- In the meantime, below is an example of what you can do with just a few lines of code:
 
 
 
 
 
14
  """
15
 
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
 
 
2
  import streamlit as st
3
+ from auth import login_button
4
+ from agent import initialize_agent
5
 
6
+ st.set_page_config(page_title="MyAnimeList Chat Assistant", page_icon="🎮")
7
+
8
+ system_prompt = """
9
+ You are a Helpful and Friendly Anime Assistant with comprehensive capabilities for managing and discovering anime content.
10
+
11
+ ## Core Responsibilities:
12
+ - **MyAnimeList Management**: View, add, and remove anime from user's personal list
13
+ - **Anime Recommendations**: Provide personalized suggestions based on user preferences
14
+ - **Web Search**: Search for additional anime information when needed
15
+ - **Watch Links**: Provide streaming links exclusively from HiAnime
16
 
17
+ ## Tool Usage Guidelines:
18
+ - **format_anime_list**: ALWAYS use this tool to display anime lists in a user-friendly format
19
+ - **anime_suggestion**: Use for generating personalized anime recommendations
20
+ - **display_anime_cards**: Format anime suggestions into visually appealing cards
21
+ - **hianime_watchlink**: ONLY use this tool for providing watch links - no other sources
22
+ - **DuckDuckGoSearchTool**: Use for additional web searches when more information is needed
23
 
24
+ ## Key Rules:
25
+ 1. Always format anime lists using the format_anime_list tool
26
+ 2. Present recommendations as cards using display_anime_cards
27
+ 3. Only provide HiAnime links for watching anime online
28
+ 4. Be helpful, friendly, and engaging in all interactions
29
+ 5. Search the web when you need additional context or information
30
  """
31
 
32
+ # Initialize session state variables
33
+ if "access_token" not in st.session_state:
34
+ st.session_state.access_token = None
35
+ if "messages" not in st.session_state:
36
+ st.session_state.messages = [
37
+ {"role": "assistant", "content": "Hi! I'm your MyAnimeList assistant. How can I help you today?"}
38
+ ]
39
+ if "agent" not in st.session_state:
40
+ st.session_state.agent = None
41
+ if "user_stats" not in st.session_state:
42
+ st.session_state.user_stats = None
43
+
44
+ # --- Authentication Flow ---
45
+ if not st.session_state.access_token:
46
+ st.title("MyAnimeList Assistant")
47
+ st.write("Please log in with your MyAnimeList account to continue.")
48
+ login_button()
49
+ else:
50
+ # Initialize the agent after login if not already done
51
+ if st.session_state.agent is None:
52
+ with st.spinner("Setting up your personalized assistant..."):
53
+ # Option 1: Simple initialization with custom system prompt
54
+ st.session_state.agent = initialize_agent()
55
+
56
+ # Option 2: Personalized initialization (if you have user stats)
57
+ # if st.session_state.user_stats:
58
+ # st.session_state.agent = initialize_personalized_agent(st.session_state.user_stats)
59
+ # else:
60
+ # st.session_state.agent = initialize_agent()
61
+
62
+ st.title("MyAnimeList Chat Assistant")
63
+
64
+ # Add a sidebar with agent info (optional)
65
+ with st.sidebar:
66
+ st.header("Assistant Info")
67
+ st.write("🤖 **Specialized MAL Assistant**")
68
+ st.write("I can help you with:")
69
+ st.write("- View your anime list")
70
+ st.write("- Filter by status")
71
+ st.write("- Get recommendations")
72
+ st.write("- Analyze your viewing habits")
73
+
74
+ # Reset conversation button
75
+ if st.button("🔄 Reset Conversation"):
76
+ st.session_state.messages = [
77
+ {"role": "assistant", "content": "Hi! I'm your MyAnimeList assistant. How can I help you today?"}
78
+ ]
79
+ st.rerun()
80
+
81
+ # Display chat history
82
+ for msg in st.session_state.messages:
83
+ with st.chat_message(msg["role"]):
84
+ st.markdown(msg["content"]) # Use markdown for better table rendering
85
+
86
+ # Chat input
87
+ if prompt := st.chat_input("Ask me anything about your anime list..."):
88
+ # Add user message
89
+ st.session_state.messages.append({"role": "user", "content": prompt})
90
+
91
+ # Display user message immediately
92
+ with st.chat_message("user"):
93
+ st.markdown(prompt)
94
+
95
+ # Get agent response
96
+ with st.chat_message("assistant"):
97
+ with st.spinner("Analyzing your request..."):
98
+ main_prompt = f"""
99
+ System: {system_prompt}
100
+
101
+ User: {prompt}
102
+ """
103
+ try:
104
+ response = st.session_state.agent.run(prompt, reset=False) # Keep context
105
+ st.markdown(response)
106
+ st.session_state.messages.append({"role": "assistant", "content": response})
107
+ except Exception as e:
108
+ error_msg = f"Sorry, I encountered an error: {str(e)}"
109
+ st.error(error_msg)
110
+ st.session_state.messages.append({"role": "assistant", "content": error_msg})
111
+
112
+ st.rerun() # Refresh to display the new message
src/tools.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from smolagents import tool
2
+ import streamlit as st
3
+ import requests
4
+ import pandas as pd
5
+ # from duckduckgo_search import DDGS
6
+
7
+ # def duckduckgo_search(query: str, max_results: int = 3) -> list:
8
+ # """Search the web using DuckDuckGo"""
9
+ # with DDGS() as ddgs:
10
+ # return [r for r in ddgs.text(query, max_results=max_results)]
11
+
12
+ #MAL Tools
13
+ @tool
14
+ def add_anime(anime_id: int, status:str = "Watching", episodes_watched: int = 0, score: int = 0) -> str:
15
+ """
16
+ Add an anime to your MyAnimeList.
17
+ Args:
18
+ anime_id (int): The ID of the anime to add.
19
+ status (str): The status of the anime (e.g., "Watching", "Completed", "On-Hold", "Dropped", "Plan to Watch").
20
+ episodes_watched (int): The number of episodes watched.
21
+ score (int): Your score for the anime (0-10).
22
+ Returns:
23
+ str: A message indicating the success or failure of the operation.
24
+ """
25
+ try:
26
+ response = requests.patch( # Changed to PATCH
27
+ f"https://api.myanimelist.net/v2/anime/{anime_id}/my_list_status",
28
+ headers={
29
+ "Authorization": f"Bearer {st.session_state.access_token}",
30
+ "Content-Type": "application/x-www-form-urlencoded" # Required
31
+ },
32
+ data={ # Use data instead of json
33
+ "status": status,
34
+ "num_watched_episodes": episodes_watched,
35
+ "score": score
36
+ }
37
+ )
38
+ response.raise_for_status()
39
+ return "Anime added/updated successfully!"
40
+ except Exception as e:
41
+ return f"Failed to add anime: {str(e)}"
42
+
43
+ @tool
44
+ def remove_anime(anime_id: int) -> str:
45
+ """
46
+ Remove an anime from your MyAnimeList.
47
+ Args:
48
+ anime_id (int): The ID of the anime to remove.
49
+ Returns:
50
+ str: A message indicating the success or failure of the operation.
51
+ """
52
+ try:
53
+ response = requests.delete(
54
+ f"https://api.myanimelist.net/v2/anime/{anime_id}/my_list_status",
55
+ headers={
56
+ "Authorization": f"Bearer {st.session_state.access_token}",
57
+ }
58
+ )
59
+ response.raise_for_status()
60
+ return "Anime removed successfully!"
61
+ except Exception as e:
62
+ return f"Failed to remove anime: {e}"
63
+
64
+ @tool
65
+ def get_anime_list(status_filter: str = None) -> str:
66
+ """
67
+ Get the list of anime in your MyAnimeList.
68
+ Args:
69
+ status_filter (str): Filter by status (e.g., "Watching", "Completed", "On-Hold", "Dropped", "Plan to Watch").
70
+ Returns:
71
+ str: A markdown table representation of the anime list with columns: Name, Status, Episodes Watched, Total Episodes, and Score.
72
+ """
73
+ try:
74
+ params = {
75
+ "fields": "list_status,media_type,num_episodes",
76
+ "limit": 100
77
+ }
78
+ if status_filter:
79
+ params["status"] = status_filter.lower().replace(" ", "_") # Convert to MAL format
80
+
81
+ response = requests.get(
82
+ "https://api.myanimelist.net/v2/users/@me/animelist",
83
+ headers={"Authorization": f"Bearer {st.session_state.access_token}"},
84
+ params=params
85
+ )
86
+ response.raise_for_status() # Check for HTTP errors
87
+ return format_anime_list(response.json().get("data", []))
88
+ except Exception as e:
89
+ return f"Error fetching list: {str(e)}"
90
+
91
+
92
+ @tool
93
+ def search_anime(query: str) -> str:
94
+ """
95
+ Search for anime by title.
96
+ Args:
97
+ query (str): The title of the anime to search for.
98
+ Returns:
99
+ str: A string representation of the search results.
100
+ """
101
+ try:
102
+ response = requests.get(
103
+ "https://api.myanimelist.net/v2/anime",
104
+ headers={
105
+ "Authorization": f"Bearer {st.session_state.access_token}",
106
+ },
107
+ params={
108
+ "q": query,
109
+ "limit": 10
110
+ }
111
+ )
112
+ response.raise_for_status()
113
+ return response.json()
114
+ except Exception as e:
115
+ return f"Failed to search anime: {e}"
116
+
117
+ @tool
118
+ def format_anime_list(anime_list: list) -> str:
119
+ """
120
+ Format the anime list as a markdown table.
121
+ Args:
122
+ anime_list (list): The list of anime entries.
123
+ Returns:
124
+ str: A markdown table representation of the anime list with columns: Name, Status, Episodes Watched, Total Episodes, and Score.
125
+ """
126
+ if not anime_list:
127
+ return "No anime found."
128
+
129
+ # Create markdown table header
130
+ table = "| Name | Status | Episodes Watched | Total Episodes | Score |\n"
131
+ table += "|------|--------|------------------|----------------|-------|\n"
132
+
133
+ # Add rows for each anime
134
+ for entry in anime_list:
135
+ anime = entry["node"]
136
+ list_status = entry["list_status"]
137
+
138
+ title = anime["title"]
139
+ status = list_status["status"].replace("_", " ").title()
140
+ episodes_watched = list_status["num_episodes_watched"]
141
+ total_episodes = anime.get("num_episodes", "Unknown")
142
+ score = list_status.get("score", "Not Rated")
143
+
144
+ # Handle score display
145
+ score_display = str(score) if score and score > 0 else "Not Rated"
146
+
147
+ # Escape pipe characters in title if any
148
+ title_escaped = title.replace("|", "\\|")
149
+
150
+ table += f"| {title_escaped} | {status} | {episodes_watched} | {total_episodes} | {score_display} |\n"
151
+
152
+ return table
153
+ @tool
154
+ def display_anime_cards(search_results: list[dict]) -> str:
155
+ """
156
+ Render a list of anime search results as visually formatted cards using Streamlit.
157
+
158
+ This function displays multiple anime entries in a structured, scrollable layout,
159
+ where each anime is presented as a card with its cover image, title (linked to detail page),
160
+ rating, episode count, and a truncated synopsis. Designed for use in interactive applications
161
+ like SmolAgents, this enhances user experience by presenting search data in an intuitive format.
162
+
163
+ Args:
164
+ search_results (list[dict]): A list of dictionaries, where each dictionary contains metadata
165
+ for a single anime. Expected keys in each dictionary:
166
+ - title (str): Title of the anime.
167
+ - cover_image (str): URL of the anime’s cover/poster image.
168
+ - rating (float): Average rating (0.0 to 10.0 scale).
169
+ - episodes (int): Total number of episodes.
170
+ - synopsis (str): Brief plot summary.
171
+ - url (str): Link to the anime’s full detail page.
172
+
173
+ Returns:
174
+ None: The function does not return any value.
175
+ It directly renders UI components using the Streamlit.
176
+
177
+ Example:
178
+ search_results = [
179
+ {
180
+ "title": "Naruto",
181
+ "cover_image": "https://cdn.example.com/naruto.jpg",
182
+ "rating": 7.9,
183
+ "episodes": 220,
184
+ "synopsis": "A young ninja strives to become Hokage...",
185
+ "url": "https://myanimelist.net/anime/20/Naruto"
186
+ },
187
+ ...
188
+ ]
189
+ display_anime_cards(search_results)
190
+ """
191
+ pass
192
+ # for anime in search_results:
193
+ # # Extract cover image URL (prefer large, then medium, then fallback)
194
+ # cover_url = None
195
+ # if "main_picture" in anime:
196
+ # cover_url = anime["main_picture"].get("large") or anime["main_picture"].get("medium")
197
+ # elif "cover_url" in anime:
198
+ # cover_url = anime["cover_url"]
199
+ # else:
200
+ # cover_url = "https://via.placeholder.com/150x220.png?text=No+Image"
201
+
202
+ # with st.container(border=True):
203
+ # col1, col2 = st.columns([1, 3])
204
+ # with col1:
205
+ # st.image(cover_url, width=150)
206
+ # with col2:
207
+ # st.markdown(f"### {anime.get('title', 'Untitled')}")
208
+ # mal_score = anime.get('score', 0)
209
+ # normalized_rating = mal_score / 2 # Convert to 0-5 scale
210
+ # full_stars = int(normalized_rating)
211
+ # half_star = 1 if normalized_rating - full_stars >= 0.5 else 0
212
+ # empty_stars = 5 - full_stars - half_star
213
+ # stars = "⭐" * full_stars + "⯨" * half_star + "✰" * empty_stars
214
+ # st.markdown(f"**MAL Score:** {stars} ({mal_score}/10)")
215
+ # if 'episodes' in anime:
216
+ # st.caption(f"Episodes: {anime['episodes']}")
217
+ # if 'status' in anime:
218
+ # st.caption(f"Airing Status: {anime['status'].capitalize()}")
219
+ # return "Displayed anime results successfully"
220
+
221
+ @tool
222
+ def hianime_watchlink(query: str) -> str:
223
+ """
224
+ Search for a HiAnime watch link for the anime in query make Sure its hianime.sx link.
225
+ Args:
226
+ query (str): The title of the anime to search for.
227
+ Returns:
228
+ str: A string representation of the hianime watch link or an error message.
229
+ """
230
+ pass
231
+ # try:
232
+ # results = duckduckgo_search(f"{query} site:hianime.tv", max_results=1)
233
+ # return results[0]['href'] if results else "No HiAnime link found"
234
+ # except Exception as e:
235
+ # return f"Search failed: {str(e)}"
236
+
237
+
238
+ @tool
239
+ def anime_suggestion(query: str) -> str:
240
+ """
241
+ Suggest an anime based on a query.
242
+ Args:
243
+ query (str): The title or description of the anime to suggest.
244
+ Returns:
245
+ str: A tabular representation of the suggested anime or an error message, this suggestion than searched through search_anime tool to get more information.
246
+ """
247
+ # try:
248
+ # results = duckduckgo_search(query + " anime suggestion")
249
+ # if not results:
250
+ # return "No anime suggestion found."
251
+ # return results[0].get("href", "No valid suggestion found.")
252
+ # except Exception as e:
253
+ # return f"Failed to suggest anime: {e}"
254
+ pass