Marylene commited on
Commit
133e275
·
verified ·
1 Parent(s): dddb3d7

Mise en forme

Browse files
Files changed (1) hide show
  1. quick_deploy_agent.py +110 -0
quick_deploy_agent.py CHANGED
@@ -129,6 +129,116 @@ class OFFtoCOICOP(Tool):
129
 
130
  return json.dumps({"candidates": c})
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
 
134
 
 
129
 
130
  return json.dumps({"candidates": c})
131
 
132
+ # ---- OFFByEAN : robuste + sortie normalisée + step3_inputs ----
133
+ class OFFByEAN(Tool):
134
+ name = "openfoodfacts_product_by_ean"
135
+ description = "Open Food Facts /api/v0|v2/product/{ean} (name, brands, categories...)."
136
+ inputs = {"ean": {"type": "string", "description": "EAN à interroger sur l'API OFF."}}
137
+ output_type = "string"
138
+ requirements = ["requests"] # urllib3 vient via requests
139
+
140
+ def forward(self, ean: str) -> str:
141
+ import re, json, requests
142
+ from requests.adapters import HTTPAdapter
143
+ try:
144
+ from urllib3.util.retry import Retry
145
+ except Exception:
146
+ Retry = None # environnement minimal
147
+
148
+ def _to_list(x):
149
+ if x is None:
150
+ return []
151
+ if isinstance(x, list):
152
+ # stringifier proprement les éléments non-str
153
+ return [str(t).strip() for t in x if str(t).strip()]
154
+ if isinstance(x, str):
155
+ parts = [p.strip() for p in re.split(r"[,\|;]", x) if p.strip()]
156
+ return parts
157
+ return [str(x).strip()]
158
+
159
+ def _first(*vals):
160
+ for v in vals:
161
+ if isinstance(v, str) and v.strip():
162
+ return v.strip()
163
+ return ""
164
+
165
+ code = re.sub(r"\D", "", ean or "")
166
+ if not code:
167
+ return json.dumps({"ok": False, "status": 0, "code": "", "error": "EAN vide"})
168
+
169
+ sess = requests.Session()
170
+ sess.headers.update({
171
+ "User-Agent": "insee-coicop-agent/1.0",
172
+ "Accept": "application/json",
173
+ })
174
+ if Retry:
175
+ retry = Retry(
176
+ total=3, backoff_factor=0.5,
177
+ status_forcelist=[429, 500, 502, 503, 504],
178
+ allowed_methods=frozenset(["GET"]),
179
+ raise_on_status=False,
180
+ )
181
+ sess.mount("https://", HTTPAdapter(max_retries=retry))
182
+
183
+ urls = [
184
+ f"https://world.openfoodfacts.org/api/v0/product/{code}.json",
185
+ "https://world.openfoodfacts.org/api/v2/product/"
186
+ f"{code}?lc=fr&fields=code,product_name,product_name_fr,brands,"
187
+ "categories_tags,categories_tags_fr,ingredients_text,ingredients_text_fr,"
188
+ "stores,status,status_verbose",
189
+ f"https://world.openfoodfacts.net/api/v0/product/{code}.json",
190
+ ]
191
+
192
+ last_err = None
193
+ for u in urls:
194
+ try:
195
+ r = sess.get(u, timeout=15)
196
+ if not r.ok:
197
+ last_err = f"HTTP {r.status_code}"
198
+ continue
199
+ data = r.json()
200
+ product = data.get("product")
201
+ status = data.get("status", 1 if product else 0)
202
+ if status == 1 or product:
203
+ p = product or {}
204
+ # Normalisation stricte des champs
205
+ product_name = _first(p.get("product_name_fr"), p.get("product_name"))
206
+ categories_tags = (
207
+ p.get("categories_tags_fr")
208
+ or p.get("categories_tags")
209
+ or p.get("categories")
210
+ )
211
+ categories_tags = _to_list(categories_tags)
212
+ ingredients_text = _first(p.get("ingredients_text_fr"), p.get("ingredients_text"))
213
+ brands = _first(p.get("brands"), None)
214
+ stores = _first(p.get("stores"), None)
215
+
216
+ out = {
217
+ "ok": True,
218
+ "status": status,
219
+ "status_verbose": data.get("status_verbose"),
220
+ "code": code,
221
+ "used_url": u,
222
+ # Champs lisibles directement par le LLM
223
+ "product_name": product_name,
224
+ "categories_tags": categories_tags, # toujours list[str]
225
+ "ingredients_text": ingredients_text,
226
+ "brands": brands, # string (OFF est souvent "brand1, brand2")
227
+ "brands_list": _to_list(brands), # list[str] pratique
228
+ "stores": stores,
229
+ "stores_list": _to_list(stores),
230
+ # 🔑 Bloc prêt pour l'étape 3
231
+ "step3_inputs": {
232
+ "product_name": product_name,
233
+ "categories_tags": categories_tags,
234
+ "ingredients_text": ingredients_text,
235
+ },
236
+ }
237
+ return json.dumps(out)
238
+ except Exception as e:
239
+ last_err = str(e)
240
+
241
+ return json.dumps({"ok": False, "status": 0, "code": code, "error": last_err or "not found"})
242
 
243
 
244