ravimohan19 commited on
Commit
7b161f7
Β·
verified Β·
1 Parent(s): 876196f

Upload llm_parser.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. llm_parser.py +215 -0
llm_parser.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LLM-based datasheet parser using HuggingFace Inference API (LLaMA 3.1).
3
+
4
+ Takes raw web content or uploaded text and extracts structured polymer
5
+ datasheet properties.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import re
13
+ from typing import Optional
14
+
15
+ from huggingface_hub import InferenceClient
16
+
17
+ import config
18
+ from models import DatasheetRecord
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # ── System prompt for structured extraction ──────────────────────────────────
23
+
24
+ SYSTEM_PROMPT = """\
25
+ You are an expert polymer materials scientist and data extraction specialist.
26
+ Your task is to extract technical datasheet properties from the provided raw text
27
+ and return them as a JSON object.
28
+
29
+ RULES:
30
+ 1. Extract ONLY information explicitly stated in the source text.
31
+ 2. If a property is not found, leave the value as an empty string "".
32
+ 3. Include units in the value where available (e.g., "65 MPa", "1.14 g/cmΒ³").
33
+ 4. For properties with ranges, format as "min - max unit" (e.g., "220 - 260 Β°C").
34
+ 5. If multiple grades/variants exist, pick the one that best matches the query.
35
+ 6. Return ONLY valid JSON β€” no markdown, no extra text, no code blocks.
36
+
37
+ Return a JSON object with exactly these keys:
38
+
39
+ {
40
+ "material_name": "",
41
+ "trade_name": "",
42
+ "manufacturer": "",
43
+ "polymer_family": "",
44
+ "grade": "",
45
+ "description": "",
46
+ "processing_method": "",
47
+ "features": "",
48
+ "applications": "",
49
+ "tensile_strength_mpa": "",
50
+ "tensile_modulus_mpa": "",
51
+ "elongation_at_break_pct": "",
52
+ "flexural_strength_mpa": "",
53
+ "flexural_modulus_mpa": "",
54
+ "impact_strength_charpy_kj_m2": "",
55
+ "impact_strength_izod_j_m": "",
56
+ "hardness_shore_d": "",
57
+ "hardness_rockwell": "",
58
+ "compressive_strength_mpa": "",
59
+ "melting_temperature_c": "",
60
+ "glass_transition_temperature_c": "",
61
+ "heat_deflection_temperature_c": "",
62
+ "vicat_softening_temperature_c": "",
63
+ "continuous_service_temperature_c": "",
64
+ "thermal_conductivity_w_mk": "",
65
+ "coefficient_of_thermal_expansion_um_mk": "",
66
+ "flammability_rating": "",
67
+ "density_g_cm3": "",
68
+ "melt_flow_index_g_10min": "",
69
+ "water_absorption_pct": "",
70
+ "moisture_absorption_pct": "",
71
+ "specific_gravity": "",
72
+ "transparency": "",
73
+ "color": "",
74
+ "dielectric_strength_kv_mm": "",
75
+ "dielectric_constant": "",
76
+ "volume_resistivity_ohm_cm": "",
77
+ "surface_resistivity_ohm": "",
78
+ "dissipation_factor": "",
79
+ "acid_resistance": "",
80
+ "alkali_resistance": "",
81
+ "solvent_resistance": "",
82
+ "uv_resistance": "",
83
+ "weatherability": "",
84
+ "fda_approved": "",
85
+ "rohs_compliant": "",
86
+ "reach_compliant": "",
87
+ "ul94_rating": ""
88
+ }
89
+ """
90
+
91
+
92
+ def parse_datasheet(
93
+ raw_content: str,
94
+ manufacturer: str = "",
95
+ polymer_family: str = "",
96
+ grade: str = "",
97
+ source_url: str = "",
98
+ ) -> tuple[Optional[DatasheetRecord], list[str]]:
99
+ """
100
+ Send raw content to LLaMA 3.1 via HuggingFace Inference API and
101
+ parse the response into a DatasheetRecord.
102
+
103
+ Returns (record, errors).
104
+ """
105
+ errors: list[str] = []
106
+
107
+ if not raw_content.strip():
108
+ errors.append("No raw content to parse.")
109
+ return None, errors
110
+
111
+ # Build the user prompt
112
+ context_hint = ""
113
+ if manufacturer or polymer_family or grade:
114
+ context_hint = (
115
+ f"\nThe user is looking for: Manufacturer={manufacturer}, "
116
+ f"Polymer Family={polymer_family}, Grade={grade}.\n"
117
+ "Focus extraction on this specific material.\n"
118
+ )
119
+
120
+ user_prompt = (
121
+ f"{context_hint}\n"
122
+ f"Extract the polymer datasheet properties from the following raw text:\n\n"
123
+ f"{raw_content}"
124
+ )
125
+
126
+ # Call HuggingFace Inference API
127
+ try:
128
+ client = InferenceClient(
129
+ model=config.HF_MODEL_ID,
130
+ token=config.HF_TOKEN,
131
+ )
132
+
133
+ response = client.chat_completion(
134
+ messages=[
135
+ {"role": "system", "content": SYSTEM_PROMPT},
136
+ {"role": "user", "content": user_prompt},
137
+ ],
138
+ max_tokens=config.LLM_MAX_NEW_TOKENS,
139
+ temperature=config.LLM_TEMPERATURE,
140
+ )
141
+
142
+ raw_response = response.choices[0].message.content
143
+ logger.info("LLM response length: %d chars", len(raw_response))
144
+
145
+ except Exception as exc:
146
+ errors.append(f"LLM inference failed: {exc}")
147
+ logger.error("LLM inference failed: %s", exc)
148
+ return None, errors
149
+
150
+ # Parse JSON from response
151
+ record = _extract_json_to_record(raw_response, source_url, errors)
152
+ return record, errors
153
+
154
+
155
+ def _extract_json_to_record(
156
+ raw_response: str,
157
+ source_url: str,
158
+ errors: list[str],
159
+ ) -> Optional[DatasheetRecord]:
160
+ """
161
+ Extract JSON from the LLM response (handles markdown code blocks)
162
+ and convert to a DatasheetRecord.
163
+ """
164
+ # Try to find JSON in the response
165
+ json_str = raw_response.strip()
166
+
167
+ # Remove markdown code block wrappers if present
168
+ code_block_match = re.search(
169
+ r"```(?:json)?\s*\n?(.*?)\n?```", json_str, re.DOTALL
170
+ )
171
+ if code_block_match:
172
+ json_str = code_block_match.group(1).strip()
173
+
174
+ # Try to find a JSON object
175
+ brace_match = re.search(r"\{.*\}", json_str, re.DOTALL)
176
+ if brace_match:
177
+ json_str = brace_match.group(0)
178
+
179
+ try:
180
+ data = json.loads(json_str)
181
+ except json.JSONDecodeError as exc:
182
+ errors.append(f"Failed to parse JSON from LLM response: {exc}")
183
+ logger.error("JSON parse error: %s\nRaw response:\n%s", exc, raw_response[:500])
184
+ return None
185
+
186
+ if not isinstance(data, dict):
187
+ errors.append("LLM response is not a JSON object.")
188
+ return None
189
+
190
+ # Set source URL
191
+ data["source_url"] = source_url
192
+
193
+ # Build DatasheetRecord, ignoring unknown fields
194
+ valid_fields = set(DatasheetRecord.model_fields.keys())
195
+ filtered = {k: str(v) for k, v in data.items() if k in valid_fields}
196
+
197
+ try:
198
+ record = DatasheetRecord(**filtered)
199
+ return record
200
+ except Exception as exc:
201
+ errors.append(f"Failed to create DatasheetRecord: {exc}")
202
+ return None
203
+
204
+
205
+ def parse_uploaded_text(
206
+ text: str,
207
+ source_label: str = "user_upload",
208
+ ) -> tuple[Optional[DatasheetRecord], list[str]]:
209
+ """
210
+ Parse a user-uploaded datasheet text (e.g., from PDF extraction).
211
+ """
212
+ return parse_datasheet(
213
+ raw_content=text,
214
+ source_url=source_label,
215
+ )