Omar10lfc commited on
Commit
7d5b137
·
verified ·
1 Parent(s): 62982ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +283 -283
app.py CHANGED
@@ -1,284 +1,284 @@
1
- import gradio as gr
2
- import os
3
- import requests
4
- import re
5
- import traceback
6
- from huggingface_hub import InferenceClient
7
-
8
- # --- CONFIGURATION ---
9
- TOKEN = os.getenv("HF_TOKEN")
10
- if not TOKEN:
11
- raise ValueError("HF_TOKEN environment variable is missing. Please add it in Settings.")
12
- MODEL_ID = "Qwen/Qwen2.5-Coder-32B-Instruct"
13
-
14
- client = InferenceClient(MODEL_ID, token=TOKEN)
15
-
16
- # --- FPL THEME (Official Colors) ---
17
- fpl_theme = gr.themes.Base(
18
- primary_hue="green",
19
- secondary_hue="indigo",
20
- neutral_hue="slate",
21
- ).set(
22
- body_background_fill="#37003c", # Official FPL Purple
23
- block_background_fill="#47004d", # Lighter Purple for bubbles
24
- body_text_color="#ffffff", # White text
25
- block_label_text_color="#00ff85", # Neon Green labels
26
- block_title_text_color="#00ff85", # Neon Green titles
27
- button_primary_background_fill="#00ff85", # Neon Green Button
28
- button_primary_text_color="#37003c", # Purple Text on Button
29
- border_color_primary="#00ff85", # Green Borders
30
- )
31
-
32
- # --- API TOOLS ---
33
- BASE_URL = "https://fantasy.premierleague.com/api"
34
- HEADERS = {
35
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
36
- }
37
- DATA_CACHE = {"bootstrap": None}
38
-
39
- def fetch_bootstrap():
40
- """Fetches data and caches it. Retries if empty."""
41
- if DATA_CACHE["bootstrap"] is None:
42
- try:
43
- r = requests.get(f"{BASE_URL}/bootstrap-static/", headers=HEADERS, timeout=10)
44
- if r.status_code == 200:
45
- DATA_CACHE["bootstrap"] = r.json()
46
- else:
47
- print(f"❌ API connection failed. Status: {r.status_code}")
48
- return None
49
- except Exception as e:
50
- print(f"❌ Network error: {e}")
51
- return None
52
- return DATA_CACHE.get("bootstrap")
53
-
54
- def get_team_name(team_id):
55
- data = fetch_bootstrap()
56
- if not data: return "UNK"
57
- for t in data['teams']:
58
- if t['id'] == team_id: return t['short_name']
59
- return "UNK"
60
-
61
- def get_player_id(name_query):
62
- data = fetch_bootstrap()
63
- if not data: return None
64
- name_query = str(name_query).lower().strip()
65
- for p in data['elements']:
66
- full_name = f"{p['first_name']} {p['second_name']}".lower()
67
- if name_query in full_name or name_query in p['web_name'].lower(): return p
68
- return None
69
-
70
- def get_player_report(name_query):
71
- player = get_player_id(name_query)
72
- if not player: return f"❌ Player '{name_query}' not found in 2024/25 Database."
73
- try:
74
- r = requests.get(f"{BASE_URL}/element-summary/{player['id']}/", headers=HEADERS, timeout=10)
75
- if r.status_code != 200: return "⚠️ API Error: Cannot fetch history."
76
-
77
- data = r.json()
78
- history = data.get('history', [])
79
- last_3 = history[-3:] if len(history) >= 3 else history
80
- recent_pts = sum(m['total_points'] for m in last_3)
81
-
82
- fixtures = data.get('fixtures', [])
83
- next_3 = fixtures[:3]
84
- fix_str = ""
85
- for f in next_3:
86
- is_home = f['is_home']
87
- opp_id = f['team_a'] if is_home else f['team_h']
88
- opp = get_team_name(opp_id)
89
- diff = f['difficulty']
90
- fix_str += f"{opp} (Dif:{diff}), "
91
-
92
- return (
93
- f"### 📊 {player['web_name']}\n"
94
- f"**Price:** £{player['now_cost']/10}m | **Form:** {player['form']}\n"
95
- f"**Next 3:** {fix_str.strip(', ')}\n"
96
- f"**Last 3 Pts:** {recent_pts}"
97
- )
98
- except: return "⚠️ Error processing stats."
99
-
100
- def compare_players(name1, name2):
101
- p1 = get_player_id(name1)
102
- p2 = get_player_id(name2)
103
- if not p1 or not p2: return "❌ Player not found."
104
- s1 = (float(p1['form']) * 0.6) + (float(p1['points_per_game']) * 0.4)
105
- s2 = (float(p2['form']) * 0.6) + (float(p2['points_per_game']) * 0.4)
106
- return f"### ⚔️ Compare\n**{p1['web_name']}** ({s1:.1f}) vs **{p2['web_name']}** ({s2:.1f})\nPick: **{p1['web_name'] if s1>s2 else p2['web_name']}**"
107
-
108
- def find_hidden_gems(position="MID", max_price=15.0):
109
- data = fetch_bootstrap()
110
- if not data: return "⚠️ API Error: Cannot load database."
111
-
112
- pos_map = {
113
- "GK": 1, "GKP": 1, "GOALKEEPER": 1,
114
- "DEF": 2, "DEFENDER": 2, "DF": 2, "CB": 2,
115
- "MID": 3, "MIDFIELDER": 3, "MF": 3, "WINGER": 3,
116
- "FWD": 4, "FORWARD": 4, "STRIKER": 4, "ATTACKER": 4, "ATT": 4
117
- }
118
- pos_key = str(position).upper().strip()
119
- pos_id = pos_map.get(pos_key, 3)
120
-
121
- try:
122
- limit = float(max_price)
123
- if limit > 200: limit = limit / 1000000 if limit > 100000 else limit / 10
124
- except: limit = 15.0
125
-
126
- gems = []
127
- for p in data['elements']:
128
- cost = p['now_cost'] / 10
129
- # Logic: Correct Pos + Under Budget + Active
130
- if p['element_type'] == pos_id and cost <= limit:
131
- if float(p['form']) > 0.0 or float(p['selected_by_percent']) > 0.5:
132
- gems.append(p)
133
-
134
- # Sort by Form
135
- gems = sorted(gems, key=lambda x: float(x['form']), reverse=True)[:5]
136
- if not gems: return f"No gems found for {pos_key} under £{limit}m."
137
-
138
- res = f"### 💎 Gems ({pos_key} < £{limit}m)\n"
139
- for g in gems:
140
- r = requests.get(f"{BASE_URL}/element-summary/{g['id']}/", headers=HEADERS, timeout=5)
141
- next_fix = "UNK"
142
- if r.status_code == 200:
143
- fixtures = r.json().get('fixtures', [])
144
- if fixtures:
145
- f = fixtures[0]
146
- opp = get_team_name(f['team_a'] if f['is_home'] else f['team_h'])
147
- next_fix = f"{opp} (Dif:{f['difficulty']})"
148
- res += f"- **{g['web_name']}** (£{g['now_cost']/10}m) Form: {g['form']} | Next: {next_fix}\n"
149
- return res
150
-
151
- # --- PRO-LEVEL SYSTEM PROMPT ---
152
- SYSTEM_PROMPT = """
153
- You are 'FPL Scout', an elite Fantasy Premier League tactical assistant.
154
- You have access to a LIVE database of the current 2024/25 season via tools.
155
-
156
- ### 🧠 KNOWLEDGE BASE (OFFICIAL RULES - USE THIS):
157
- - **Wildcard:** Allows unlimited PERMANENT transfers. Best used when your team has multiple injuries or poor form.
158
- - **Free Hit:** Allows unlimited transfers for ONE Gameweek only. Squad reverts to previous state. Use in Blank/Double Gameweeks.
159
- - **Bench Boost:** Points from bench players count for one GW. Use when your bench has strong fixtures.
160
- - **Triple Captain:** Captain gets 3x points. Best used on Double Gameweeks.
161
-
162
- ### 🛑 ANTI-HALLUCINATION PROTOCOLS:
163
- 1. **IGNORE INTERNAL DATA:** Your training data is from the past. Do NOT rely on it for player teams, prices, or form. Jordan Henderson is NOT at Liverpool.
164
- 2. **TOOL DEPENDENCY:** If a user asks for "Best Midfielder", you CANNOT answer until you run `[TOOL:find_hidden_gems:...]`.
165
- 3. **STOP SEQUENCE:** After outputting a `[TOOL:...]` tag, STOP generating text immediately. Wait for the data.
166
-
167
- ### 🛠️ AVAILABLE TOOLS:
168
- 1. [TOOL:get_player_report:Name]
169
- - Use for: Specific stats, form, price, and **Upcoming Fixtures**.
170
- - Example: "Should I keep Salah?" -> [TOOL:get_player_report:Salah]
171
-
172
- 2. [TOOL:compare_players:Name1,Name2]
173
- - Use for: Captaincy choices, "Start vs Bench", or Transfer dilemmas.
174
- - Example: "Saka or Palmer?" -> [TOOL:compare_players:Saka,Palmer]
175
-
176
- 3. [TOOL:find_hidden_gems:Position,MaxPrice]
177
- - Use for: Differentials, replacements, budget enablers.
178
- - Defaults: If user doesn't specify price, use 15.0.
179
- - Example: "Cheap defender?" -> [TOOL:find_hidden_gems:DEF,4.5]
180
-
181
- ### 📝 RESPONSE GUIDELINES (After Tool Result):
182
- - **Fixtures are King:** Analyze the 'Next 3' fixtures in the tool output. Green/Easy fixtures = BUY.
183
- - **Format:** Use **Bold** for names/prices. Use lists.
184
- - **Failure Handling:** If the tool returns "No gems found" or "API Error", simply say: "I couldn't find any players matching those criteria in the live database." DO NOT INVENT A LIST.
185
- """
186
-
187
- def chat_logic(message, history):
188
- messages = [{"role": "system", "content": SYSTEM_PROMPT}]
189
-
190
- for h in history:
191
- if isinstance(h, dict): messages.append(h)
192
- elif isinstance(h, list) and len(h) >= 2:
193
- messages.append({"role": "user", "content": str(h[0])})
194
- messages.append({"role": "assistant", "content": str(h[1])})
195
- messages.append({"role": "user", "content": message})
196
-
197
- try:
198
- response = client.chat_completion(messages, max_tokens=1000, temperature=0.1).choices[0].message.content
199
-
200
- # Lazy Parser
201
- tool_tag = None
202
- if "[TOOL:" in response:
203
- tool_tag = response.split("[TOOL:")[1].split("]")[0]
204
- elif "get_player_report(" in response:
205
- arg = response.split("get_player_report(")[1].split(")")[0]
206
- tool_tag = f"get_player_report:{arg}"
207
- elif "find_hidden_gems(" in response:
208
- args = response.split("find_hidden_gems(")[1].split(")")[0]
209
- tool_tag = f"find_hidden_gems:{args}"
210
-
211
- if tool_tag:
212
- try:
213
- # Anti-Hallucination Truncation
214
- if "[TOOL:" in response:
215
- clean_resp = response.split("]")[0] + "]"
216
- else:
217
- clean_resp = response
218
-
219
- if ":" in tool_tag:
220
- func_name, args_raw = tool_tag.split(":", 1)
221
- args = [a.strip() for a in re.split(r'[,;|]', args_raw) if a.strip()]
222
- else:
223
- if "," in tool_tag:
224
- func_name = tool_tag.split("(")[0].strip() if "(" in tool_tag else tool_tag.split(",")[0]
225
- func_name, args = tool_tag.split(":")[0], []
226
- else:
227
- func_name, args = tool_tag.strip(), []
228
-
229
- tool_result = "Unknown Tool"
230
- if "get_player_report" in func_name:
231
- arg1 = args[0] if len(args) > 0 else "Haaland"
232
- tool_result = get_player_report(arg1)
233
- elif "compare_players" in func_name:
234
- arg1 = args[0] if len(args) > 0 else "Salah"
235
- arg2 = args[1] if len(args) > 1 else "Haaland"
236
- tool_result = compare_players(arg1, arg2)
237
- elif "find_hidden_gems" in func_name:
238
- pos = args[0] if len(args) > 0 else "MID"
239
- try:
240
- p_str = args[1] if len(args) > 1 else "15.0"
241
- clean_p = re.sub(r'[^\d.]', '', p_str)
242
- price = float(clean_p)
243
- except: price = 15.0
244
- tool_result = find_hidden_gems(pos, price)
245
-
246
- messages.append({"role": "assistant", "content": clean_resp})
247
- messages.append({"role": "user", "content": f"Tool Result:\n{tool_result}\n\nUsing this data, answer the user."})
248
-
249
- stream = client.chat_completion(messages, max_tokens=1024, stream=True)
250
- full = ""
251
- for chunk in stream:
252
- if chunk.choices and len(chunk.choices) > 0:
253
- delta = chunk.choices[0].delta
254
- if delta.content:
255
- full += delta.content
256
- yield full
257
- except Exception as e:
258
- traceback.print_exc()
259
- yield f"⚠️ Tool Error: {e}"
260
- else:
261
- yield response
262
- except Exception as e:
263
- yield f"⚠️ API Error: {str(e)}"
264
-
265
- # --- CONNECTION CHECK ---
266
- print("--- FPL AGENT STARTUP DIAGNOSTICS ---")
267
- check = fetch_bootstrap()
268
- if check:
269
- print(f"✅ Connection Established! Loaded {len(check['elements'])} players.")
270
- else:
271
- print("❌ Connection FAILED. Check internet or User-Agent.")
272
- print("---------------------------------------")
273
-
274
- # --- LAUNCH ---
275
- with gr.Blocks(title="⚽️ FPL Scout Agent") as demo:
276
- gr.Markdown("# 🦁 FPL Scout Agent (Official)")
277
- gr.ChatInterface(
278
- fn=chat_logic,
279
- description="Your AI Assistant for Fantasy Premier League. Ask about Price, Form, Fixtures, and Differentials."
280
- )
281
-
282
- if __name__ == "__main__":
283
- print("🚀 Launching...")
284
  demo.launch(theme=fpl_theme)
 
1
+ import gradio as gr
2
+ import os
3
+ import requests
4
+ import re
5
+ import traceback
6
+ from huggingface_hub import InferenceClient
7
+
8
+ # --- CONFIGURATION ---
9
+ TOKEN = os.getenv("HF_TOKEN")
10
+ if not TOKEN:
11
+ raise ValueError("HF_TOKEN environment variable is missing. Please add it in Settings.")
12
+ MODEL_ID = "Qwen/Qwen2.5-Coder-7B-Instruct"
13
+
14
+ client = InferenceClient(MODEL_ID, token=TOKEN)
15
+
16
+ # --- FPL THEME (Official Colors) ---
17
+ fpl_theme = gr.themes.Base(
18
+ primary_hue="green",
19
+ secondary_hue="indigo",
20
+ neutral_hue="slate",
21
+ ).set(
22
+ body_background_fill="#37003c", # Official FPL Purple
23
+ block_background_fill="#47004d", # Lighter Purple for bubbles
24
+ body_text_color="#ffffff", # White text
25
+ block_label_text_color="#00ff85", # Neon Green labels
26
+ block_title_text_color="#00ff85", # Neon Green titles
27
+ button_primary_background_fill="#00ff85", # Neon Green Button
28
+ button_primary_text_color="#37003c", # Purple Text on Button
29
+ border_color_primary="#00ff85", # Green Borders
30
+ )
31
+
32
+ # --- API TOOLS ---
33
+ BASE_URL = "https://fantasy.premierleague.com/api"
34
+ HEADERS = {
35
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
36
+ }
37
+ DATA_CACHE = {"bootstrap": None}
38
+
39
+ def fetch_bootstrap():
40
+ """Fetches data and caches it. Retries if empty."""
41
+ if DATA_CACHE["bootstrap"] is None:
42
+ try:
43
+ r = requests.get(f"{BASE_URL}/bootstrap-static/", headers=HEADERS, timeout=10)
44
+ if r.status_code == 200:
45
+ DATA_CACHE["bootstrap"] = r.json()
46
+ else:
47
+ print(f"❌ API connection failed. Status: {r.status_code}")
48
+ return None
49
+ except Exception as e:
50
+ print(f"❌ Network error: {e}")
51
+ return None
52
+ return DATA_CACHE.get("bootstrap")
53
+
54
+ def get_team_name(team_id):
55
+ data = fetch_bootstrap()
56
+ if not data: return "UNK"
57
+ for t in data['teams']:
58
+ if t['id'] == team_id: return t['short_name']
59
+ return "UNK"
60
+
61
+ def get_player_id(name_query):
62
+ data = fetch_bootstrap()
63
+ if not data: return None
64
+ name_query = str(name_query).lower().strip()
65
+ for p in data['elements']:
66
+ full_name = f"{p['first_name']} {p['second_name']}".lower()
67
+ if name_query in full_name or name_query in p['web_name'].lower(): return p
68
+ return None
69
+
70
+ def get_player_report(name_query):
71
+ player = get_player_id(name_query)
72
+ if not player: return f"❌ Player '{name_query}' not found in 2024/25 Database."
73
+ try:
74
+ r = requests.get(f"{BASE_URL}/element-summary/{player['id']}/", headers=HEADERS, timeout=10)
75
+ if r.status_code != 200: return "⚠️ API Error: Cannot fetch history."
76
+
77
+ data = r.json()
78
+ history = data.get('history', [])
79
+ last_3 = history[-3:] if len(history) >= 3 else history
80
+ recent_pts = sum(m['total_points'] for m in last_3)
81
+
82
+ fixtures = data.get('fixtures', [])
83
+ next_3 = fixtures[:3]
84
+ fix_str = ""
85
+ for f in next_3:
86
+ is_home = f['is_home']
87
+ opp_id = f['team_a'] if is_home else f['team_h']
88
+ opp = get_team_name(opp_id)
89
+ diff = f['difficulty']
90
+ fix_str += f"{opp} (Dif:{diff}), "
91
+
92
+ return (
93
+ f"### 📊 {player['web_name']}\n"
94
+ f"**Price:** £{player['now_cost']/10}m | **Form:** {player['form']}\n"
95
+ f"**Next 3:** {fix_str.strip(', ')}\n"
96
+ f"**Last 3 Pts:** {recent_pts}"
97
+ )
98
+ except: return "⚠️ Error processing stats."
99
+
100
+ def compare_players(name1, name2):
101
+ p1 = get_player_id(name1)
102
+ p2 = get_player_id(name2)
103
+ if not p1 or not p2: return "❌ Player not found."
104
+ s1 = (float(p1['form']) * 0.6) + (float(p1['points_per_game']) * 0.4)
105
+ s2 = (float(p2['form']) * 0.6) + (float(p2['points_per_game']) * 0.4)
106
+ return f"### ⚔️ Compare\n**{p1['web_name']}** ({s1:.1f}) vs **{p2['web_name']}** ({s2:.1f})\nPick: **{p1['web_name'] if s1>s2 else p2['web_name']}**"
107
+
108
+ def find_hidden_gems(position="MID", max_price=15.0):
109
+ data = fetch_bootstrap()
110
+ if not data: return "⚠️ API Error: Cannot load database."
111
+
112
+ pos_map = {
113
+ "GK": 1, "GKP": 1, "GOALKEEPER": 1,
114
+ "DEF": 2, "DEFENDER": 2, "DF": 2, "CB": 2,
115
+ "MID": 3, "MIDFIELDER": 3, "MF": 3, "WINGER": 3,
116
+ "FWD": 4, "FORWARD": 4, "STRIKER": 4, "ATTACKER": 4, "ATT": 4
117
+ }
118
+ pos_key = str(position).upper().strip()
119
+ pos_id = pos_map.get(pos_key, 3)
120
+
121
+ try:
122
+ limit = float(max_price)
123
+ if limit > 200: limit = limit / 1000000 if limit > 100000 else limit / 10
124
+ except: limit = 15.0
125
+
126
+ gems = []
127
+ for p in data['elements']:
128
+ cost = p['now_cost'] / 10
129
+ # Logic: Correct Pos + Under Budget + Active
130
+ if p['element_type'] == pos_id and cost <= limit:
131
+ if float(p['form']) > 0.0 or float(p['selected_by_percent']) > 0.5:
132
+ gems.append(p)
133
+
134
+ # Sort by Form
135
+ gems = sorted(gems, key=lambda x: float(x['form']), reverse=True)[:5]
136
+ if not gems: return f"No gems found for {pos_key} under £{limit}m."
137
+
138
+ res = f"### 💎 Gems ({pos_key} < £{limit}m)\n"
139
+ for g in gems:
140
+ r = requests.get(f"{BASE_URL}/element-summary/{g['id']}/", headers=HEADERS, timeout=5)
141
+ next_fix = "UNK"
142
+ if r.status_code == 200:
143
+ fixtures = r.json().get('fixtures', [])
144
+ if fixtures:
145
+ f = fixtures[0]
146
+ opp = get_team_name(f['team_a'] if f['is_home'] else f['team_h'])
147
+ next_fix = f"{opp} (Dif:{f['difficulty']})"
148
+ res += f"- **{g['web_name']}** (£{g['now_cost']/10}m) Form: {g['form']} | Next: {next_fix}\n"
149
+ return res
150
+
151
+ # --- PRO-LEVEL SYSTEM PROMPT ---
152
+ SYSTEM_PROMPT = """
153
+ You are 'FPL Scout', an elite Fantasy Premier League tactical assistant.
154
+ You have access to a LIVE database of the current 2024/25 season via tools.
155
+
156
+ ### 🧠 KNOWLEDGE BASE (OFFICIAL RULES - USE THIS):
157
+ - **Wildcard:** Allows unlimited PERMANENT transfers. Best used when your team has multiple injuries or poor form.
158
+ - **Free Hit:** Allows unlimited transfers for ONE Gameweek only. Squad reverts to previous state. Use in Blank/Double Gameweeks.
159
+ - **Bench Boost:** Points from bench players count for one GW. Use when your bench has strong fixtures.
160
+ - **Triple Captain:** Captain gets 3x points. Best used on Double Gameweeks.
161
+
162
+ ### 🛑 ANTI-HALLUCINATION PROTOCOLS:
163
+ 1. **IGNORE INTERNAL DATA:** Your training data is from the past. Do NOT rely on it for player teams, prices, or form. Jordan Henderson is NOT at Liverpool.
164
+ 2. **TOOL DEPENDENCY:** If a user asks for "Best Midfielder", you CANNOT answer until you run `[TOOL:find_hidden_gems:...]`.
165
+ 3. **STOP SEQUENCE:** After outputting a `[TOOL:...]` tag, STOP generating text immediately. Wait for the data.
166
+
167
+ ### 🛠️ AVAILABLE TOOLS:
168
+ 1. [TOOL:get_player_report:Name]
169
+ - Use for: Specific stats, form, price, and **Upcoming Fixtures**.
170
+ - Example: "Should I keep Salah?" -> [TOOL:get_player_report:Salah]
171
+
172
+ 2. [TOOL:compare_players:Name1,Name2]
173
+ - Use for: Captaincy choices, "Start vs Bench", or Transfer dilemmas.
174
+ - Example: "Saka or Palmer?" -> [TOOL:compare_players:Saka,Palmer]
175
+
176
+ 3. [TOOL:find_hidden_gems:Position,MaxPrice]
177
+ - Use for: Differentials, replacements, budget enablers.
178
+ - Defaults: If user doesn't specify price, use 15.0.
179
+ - Example: "Cheap defender?" -> [TOOL:find_hidden_gems:DEF,4.5]
180
+
181
+ ### 📝 RESPONSE GUIDELINES (After Tool Result):
182
+ - **Fixtures are King:** Analyze the 'Next 3' fixtures in the tool output. Green/Easy fixtures = BUY.
183
+ - **Format:** Use **Bold** for names/prices. Use lists.
184
+ - **Failure Handling:** If the tool returns "No gems found" or "API Error", simply say: "I couldn't find any players matching those criteria in the live database." DO NOT INVENT A LIST.
185
+ """
186
+
187
+ def chat_logic(message, history):
188
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
189
+
190
+ for h in history:
191
+ if isinstance(h, dict): messages.append(h)
192
+ elif isinstance(h, list) and len(h) >= 2:
193
+ messages.append({"role": "user", "content": str(h[0])})
194
+ messages.append({"role": "assistant", "content": str(h[1])})
195
+ messages.append({"role": "user", "content": message})
196
+
197
+ try:
198
+ response = client.chat_completion(messages, max_tokens=1000, temperature=0.1).choices[0].message.content
199
+
200
+ # Lazy Parser
201
+ tool_tag = None
202
+ if "[TOOL:" in response:
203
+ tool_tag = response.split("[TOOL:")[1].split("]")[0]
204
+ elif "get_player_report(" in response:
205
+ arg = response.split("get_player_report(")[1].split(")")[0]
206
+ tool_tag = f"get_player_report:{arg}"
207
+ elif "find_hidden_gems(" in response:
208
+ args = response.split("find_hidden_gems(")[1].split(")")[0]
209
+ tool_tag = f"find_hidden_gems:{args}"
210
+
211
+ if tool_tag:
212
+ try:
213
+ # Anti-Hallucination Truncation
214
+ if "[TOOL:" in response:
215
+ clean_resp = response.split("]")[0] + "]"
216
+ else:
217
+ clean_resp = response
218
+
219
+ if ":" in tool_tag:
220
+ func_name, args_raw = tool_tag.split(":", 1)
221
+ args = [a.strip() for a in re.split(r'[,;|]', args_raw) if a.strip()]
222
+ else:
223
+ if "," in tool_tag:
224
+ func_name = tool_tag.split("(")[0].strip() if "(" in tool_tag else tool_tag.split(",")[0]
225
+ func_name, args = tool_tag.split(":")[0], []
226
+ else:
227
+ func_name, args = tool_tag.strip(), []
228
+
229
+ tool_result = "Unknown Tool"
230
+ if "get_player_report" in func_name:
231
+ arg1 = args[0] if len(args) > 0 else "Haaland"
232
+ tool_result = get_player_report(arg1)
233
+ elif "compare_players" in func_name:
234
+ arg1 = args[0] if len(args) > 0 else "Salah"
235
+ arg2 = args[1] if len(args) > 1 else "Haaland"
236
+ tool_result = compare_players(arg1, arg2)
237
+ elif "find_hidden_gems" in func_name:
238
+ pos = args[0] if len(args) > 0 else "MID"
239
+ try:
240
+ p_str = args[1] if len(args) > 1 else "15.0"
241
+ clean_p = re.sub(r'[^\d.]', '', p_str)
242
+ price = float(clean_p)
243
+ except: price = 15.0
244
+ tool_result = find_hidden_gems(pos, price)
245
+
246
+ messages.append({"role": "assistant", "content": clean_resp})
247
+ messages.append({"role": "user", "content": f"Tool Result:\n{tool_result}\n\nUsing this data, answer the user."})
248
+
249
+ stream = client.chat_completion(messages, max_tokens=1024, stream=True)
250
+ full = ""
251
+ for chunk in stream:
252
+ if chunk.choices and len(chunk.choices) > 0:
253
+ delta = chunk.choices[0].delta
254
+ if delta.content:
255
+ full += delta.content
256
+ yield full
257
+ except Exception as e:
258
+ traceback.print_exc()
259
+ yield f"⚠️ Tool Error: {e}"
260
+ else:
261
+ yield response
262
+ except Exception as e:
263
+ yield f"⚠️ API Error: {str(e)}"
264
+
265
+ # --- CONNECTION CHECK ---
266
+ print("--- FPL AGENT STARTUP DIAGNOSTICS ---")
267
+ check = fetch_bootstrap()
268
+ if check:
269
+ print(f"✅ Connection Established! Loaded {len(check['elements'])} players.")
270
+ else:
271
+ print("❌ Connection FAILED. Check internet or User-Agent.")
272
+ print("---------------------------------------")
273
+
274
+ # --- LAUNCH ---
275
+ with gr.Blocks(title="⚽️ FPL Scout Agent") as demo:
276
+ gr.Markdown("# 🦁 FPL Scout Agent (Official)")
277
+ gr.ChatInterface(
278
+ fn=chat_logic,
279
+ description="Your AI Assistant for Fantasy Premier League. Ask about Price, Form, Fixtures, and Differentials."
280
+ )
281
+
282
+ if __name__ == "__main__":
283
+ print("🚀 Launching...")
284
  demo.launch(theme=fpl_theme)