julesbonnard commited on
Commit
1e22cda
·
1 Parent(s): d5653aa

works with context

Browse files
Files changed (4) hide show
  1. .gitignore +2 -0
  2. README.md +8 -6
  3. app.py +279 -0
  4. requirements.txt +5 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ env
2
+ .env
README.md CHANGED
@@ -1,12 +1,14 @@
1
  ---
2
  title: Asknews
3
- emoji: 📉
4
- colorFrom: green
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 5.47.1
8
  app_file: app.py
9
  pinned: false
 
 
 
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Asknews
3
+ emoji: 💬
4
+ colorFrom: yellow
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.42.0
8
  app_file: app.py
9
  pinned: false
10
+ hf_oauth: true
11
+ hf_oauth_scopes:
12
+ - inference-api
13
  ---
14
+ An example chatbot using [Gradio](https://gradio.app), [`huggingface_hub`](https://huggingface.co/docs/huggingface_hub/v0.22.2/en/index), and the [Hugging Face Inference API](https://huggingface.co/docs/api-inference/index).
 
app.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import os
4
+ import datetime
5
+ import logging
6
+ from typing import List, Dict, Optional
7
+
8
+ from dotenv import load_dotenv
9
+ load_dotenv()
10
+
11
+ import gradio as gr
12
+ from huggingface_hub import InferenceClient, InferenceTimeoutError
13
+ from asknews_sdk import AskNewsSDK
14
+
15
+ DEFAULT_MODEL = "openai/gpt-oss-20b"
16
+
17
+
18
+ LOG_LEVEL = os.getenv("ASKNEWS_LOG_LEVEL", "INFO").upper()
19
+ logging.basicConfig(level=getattr(logging, LOG_LEVEL, logging.INFO))
20
+ logger = logging.getLogger("asknews_app")
21
+
22
+ # ---- AskNews setup ----
23
+ def get_asknews_sdk() -> Optional[AskNewsSDK]:
24
+ """
25
+ Initialize AskNews SDK using environment variables.
26
+ Returns None if missing credentials.
27
+ """
28
+ client_id = os.getenv("ASKNEWS_CLIENT_ID", "").strip()
29
+ client_secret = os.getenv("ASKNEWS_CLIENT_SECRET", "").strip()
30
+ if not client_id or not client_secret:
31
+ logger.warning("AskNews credentials are missing; skipping SDK init.")
32
+ return None
33
+ try:
34
+ sdk = AskNewsSDK(
35
+ client_id=client_id,
36
+ client_secret=client_secret,
37
+ scopes=["news"]
38
+ )
39
+ logger.info("AskNews SDK initialised successfully.")
40
+ return sdk
41
+ except Exception as exc:
42
+ logger.exception("Failed to initialise AskNews SDK: %s", exc)
43
+ return None
44
+
45
+
46
+ def safe_iso_date(dt: Optional[str]) -> str:
47
+ """Format date string safely for display."""
48
+ if not dt:
49
+ return ""
50
+ try:
51
+ # Attempt parsing common formats; if fails, return as-is
52
+ # AskNews typically returns ISO timestamps
53
+ d = datetime.datetime.fromisoformat(dt.replace("Z", "+00:00"))
54
+ return d.strftime("%Y-%m-%d")
55
+ except Exception:
56
+ return dt
57
+
58
+
59
+ def fetch_asknews_context(
60
+ sdk: AskNewsSDK,
61
+ query: str,
62
+ hours_back: int,
63
+ n_articles: int,
64
+ domains: List[str],
65
+ ) -> str:
66
+ """
67
+ Récupère le contexte texte directement depuis AskNews (return_type="string").
68
+ Retourne context_text
69
+ """
70
+ logger.info(
71
+ "Fetching AskNews context: query=%s, hours_back=%s, n_articles=%s, domains=%s",
72
+ query,
73
+ hours_back,
74
+ n_articles,
75
+ domains,
76
+ )
77
+ try:
78
+ response = sdk.news.search_news(
79
+ query=query,
80
+ hours_back=hours_back,
81
+ n_articles=n_articles,
82
+ historical=True,
83
+ premium=True,
84
+ method="nl",
85
+ domain_url=domains if domains else None,
86
+ return_type="string", # Demande le contexte déjà formaté
87
+ ).as_string
88
+ # response est une chaîne de caractères contenant le contexte
89
+ context = response if isinstance(response, str) else ""
90
+ logger.info("AskNews context received (%s chars)", len(context))
91
+ return context
92
+ except Exception:
93
+ logger.exception("AskNews context fetch failed.")
94
+ return ""
95
+
96
+
97
+ # ---- Chat respond function ----
98
+ def respond(
99
+ message: str,
100
+ history: List[Dict[str, str]],
101
+ system_message: str,
102
+ max_tokens: int,
103
+ temperature: float,
104
+ top_p: float,
105
+ hf_token: gr.OAuthToken,
106
+ model_name: str = DEFAULT_MODEL,
107
+ use_asknews: bool = True,
108
+ asknews_hours_back: int = 24*30,
109
+ asknews_n_articles: int = 10,
110
+ asknews_domains_csv: str = "afp.com",
111
+ ):
112
+ """
113
+ Stream chat responses from HF, enriching with AskNews context when enabled.
114
+ """
115
+ # Validate OAuth token for HF
116
+ if hf_token is None or hf_token.token is None or hf_token.token.strip() == "":
117
+ yield "Veuillez vous connecter à Hugging Face via le bouton Login dans la barre latérale."
118
+ return
119
+
120
+ # Prepare AskNews SDK if requested
121
+ sdk = get_asknews_sdk() if use_asknews else None
122
+ asknews_context = ""
123
+ if sdk is not None:
124
+ domains = [d.strip() for d in asknews_domains_csv.split(",") if d.strip()]
125
+ logger.info(
126
+ "AskNews enabled; fetching context with hours_back=%s, n_articles=%s, domains=%s",
127
+ asknews_hours_back,
128
+ asknews_n_articles,
129
+ domains,
130
+ )
131
+ asknews_context = fetch_asknews_context(
132
+ sdk=sdk,
133
+ query=message,
134
+ hours_back=asknews_hours_back,
135
+ n_articles=asknews_n_articles,
136
+ domains=domains,
137
+ )
138
+ if asknews_context:
139
+ logger.info("AskNews context will be injected (chars=%s)", len(asknews_context))
140
+ else:
141
+ logger.warning("AskNews context is empty after fetch.")
142
+
143
+ # Build messages
144
+ messages: List[Dict[str, str]] = []
145
+ base_system = system_message.strip() if system_message else "You are a helpful assistant."
146
+ messages.append({"role": "system", "content": base_system})
147
+
148
+ # If we have AskNews context, inject it as an additional system guidance
149
+ if asknews_context:
150
+ messages.append({
151
+ "role": "system",
152
+ "content": (
153
+ "Use the following news context when answering. If the user's query is unrelated, ignore it.\n\n"
154
+ f"{asknews_context}"
155
+ ),
156
+ })
157
+
158
+ # Include history (roles should be valid)
159
+ for m in history or []:
160
+ role = m.get("role")
161
+ content = m.get("content", "")
162
+ if role in ("user", "assistant", "system") and content is not None:
163
+ messages.append({"role": role, "content": str(content)})
164
+
165
+ # Current user message
166
+ if message is None or str(message).strip() == "":
167
+ yield "Veuillez saisir un message."
168
+ return
169
+ messages.append({"role": "user", "content": str(message).strip()})
170
+
171
+ # Initialize HF client
172
+ try:
173
+ client = InferenceClient(token=hf_token.token, model=model_name)
174
+ except Exception as e:
175
+ yield f"Échec d'initialisation du client d'inférence HF: {e}"
176
+ return
177
+
178
+ response_accum = ""
179
+ # Optional prefix informing about context usage (not counted by model, only displayed)
180
+ if sdk is None and use_asknews:
181
+ response_accum = "[AskNews non configuré: définissez ASKNEWS_CLIENT_ID et ASKNEWS_CLIENT_SECRET dans l'environnement.]\n"
182
+ yield response_accum
183
+
184
+ # if sdk is not None:
185
+ # context_display = asknews_context.strip()
186
+ # if context_display:
187
+ # if len(context_display) > 4000:
188
+ # context_display = context_display[:4000] + "\n[Contexte AskNews tronqué pour affichage]"
189
+ # else:
190
+ # context_display = "[Vide]"
191
+ # response_accum += "[Contexte AskNews]\n" + context_display + "\n\n"
192
+ # yield response_accum
193
+
194
+ try:
195
+ for chunk in client.chat_completion(
196
+ messages=messages,
197
+ max_tokens=max_tokens,
198
+ stream=True,
199
+ temperature=temperature,
200
+ top_p=top_p,
201
+ ):
202
+ try:
203
+ choices = getattr(chunk, "choices", [])
204
+ token = ""
205
+ if choices and getattr(choices[0], "delta", None) is not None:
206
+ token_piece = getattr(choices[0].delta, "content", None)
207
+ if token_piece:
208
+ token = token_piece
209
+ if token:
210
+ response_accum += token
211
+ yield response_accum
212
+ except Exception:
213
+ continue
214
+ except InferenceTimeoutError:
215
+ yield response_accum + "\n\n[Temps dépassé. Réessayez ou réduisez 'Max new tokens'.]"
216
+ except Exception as e:
217
+ if response_accum:
218
+ yield response_accum + f"\n\n[Erreur: {e}]"
219
+ else:
220
+ yield f"Erreur de l'API d'inférence: {e}"
221
+
222
+
223
+ # ---- Gradio UI ----
224
+ chatbot = gr.ChatInterface(
225
+ fn=respond,
226
+ type="messages",
227
+ additional_inputs=[
228
+ gr.Textbox(value="""Tu es un assistant virtuel conçu pour aider des journalistes d’agence (Agence France-Presse) dans leurs recherches d’information.
229
+
230
+ Sources :
231
+ - Tu disposes d’un agent de recherche en langage naturel (Asknews) qui interroge en temps réel le flux des dépêches AFP.
232
+ - Tu dois répondre uniquement avec des informations issues de ces dépêches.
233
+
234
+ Mission :
235
+ - Comprendre les requêtes d’un journaliste (souvent courtes, imprécises, ou en langage naturel).
236
+ - Transformer ces requêtes en recherches efficaces dans les dépêches AFP, avec Asknews.
237
+ - Résumer les résultats en style journalistique : factuel, concis, hiérarchisé, neutre.
238
+ - Proposer, si pertinent, des angles complémentaires (ex. contexte historique, réactions, comparaisons, chiffres clés).
239
+ - Permettre au journaliste de raffiner la recherche (par période, sujet, acteurs, pays).
240
+ - Citer les dépêches AFP en retour (référence et date/heure).
241
+
242
+ Contraintes :
243
+ - Toujours rester factuel, éviter toute spéculation.
244
+ - Si la question est ambiguë, demander des précisions.
245
+ - Si aucun résultat n’est trouvé, proposer des formulations alternatives de recherche.
246
+ - Résumer les informations de manière actionnable (pour rédaction immédiate).
247
+
248
+ Style :
249
+ - Réponses brèves et efficaces.
250
+ - Donner un résumé clair d’abord (les 2–3 points clés).
251
+ - Ajouter ensuite plus de détails, ou des pistes pour approfondir.
252
+ - Toujours indiquer les sources/dépêches AFP d’où viennent les infos.
253
+ """, label="System message"),
254
+ gr.Slider(minimum=1, maximum=4096, value=512, step=1, label="Max new tokens"),
255
+ gr.Slider(minimum=0.0, maximum=2.0, value=0.7, step=0.1, label="Temperature"),
256
+ gr.Slider(minimum=0.05, maximum=1.0, value=0.95, step=0.05, label="Top-p"),
257
+ gr.Textbox(value=DEFAULT_MODEL, label="Model name"),
258
+ gr.Checkbox(value=True, label="Utiliser AskNews pour le contexte"),
259
+ gr.Slider(minimum=96, maximum=24*30, value=24*30, step=24, label="AskNews: heures en arrière"),
260
+ gr.Slider(minimum=1, maximum=10, value=10, step=1, label="AskNews: nombre d'articles"),
261
+ gr.Textbox(value="afp.com", label="AskNews: domaines (CSV)"),
262
+ ]
263
+ )
264
+
265
+ with gr.Blocks() as demo:
266
+ gr.Markdown("# Chatbot HF avec contexte AskNews")
267
+ with gr.Sidebar():
268
+ gr.LoginButton()
269
+ gr.Markdown(
270
+ "Connectez-vous avec votre compte Hugging Face.\n\n"
271
+ "Pour activer AskNews, définissez les variables d'environnement:\n"
272
+ "- ASKNEWS_CLIENT_ID\n"
273
+ "- ASKNEWS_CLIENT_SECRET\n\n"
274
+ "Ajustez les paramètres pour contrôler le contexte (heures, domaines, nombre d'articles)."
275
+ )
276
+ chatbot.render()
277
+
278
+ if __name__ == "__main__":
279
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ gradio[oauth]
3
+ google-genai
4
+ dotenv
5
+ asknews