philippotiger commited on
Commit
6f03249
ยท
verified ยท
1 Parent(s): fd5712e

Upload inference_local.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. inference_local.py +137 -0
inference_local.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import unicodedata
3
+ from llama_cpp import Llama
4
+
5
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
6
+ # CONFIG
7
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
+ GGUF_PATH = "./football-extractor-q4.gguf"
9
+
10
+ SYSTEM_PROMPT = (
11
+ "You are a football data extraction assistant. "
12
+ "Extract structured data from the message and return ONLY a valid JSON array. "
13
+ "Each object in the array must have exactly these keys: "
14
+ "league, team_1, team_2, prediction, date, odds. "
15
+ "If a field is missing, use null. No extra text, no markdown."
16
+ )
17
+
18
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
19
+ # LOAD MODEL (runs on Mac Metal / CPU)
20
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
21
+ llm = Llama(
22
+ model_path=GGUF_PATH,
23
+ n_ctx=2048, # context window
24
+ n_gpu_layers=-1, # offload all layers to Metal GPU
25
+ verbose=False,
26
+ )
27
+ print("โœ… Model loaded")
28
+
29
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
30
+ # HELPERS
31
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
32
+ def clean_input(text: str) -> str:
33
+ """Strip bold unicode characters (e.g. Telegram bold)."""
34
+ return ''.join(
35
+ c for c in unicodedata.normalize('NFKD', text)
36
+ if not unicodedata.combining(c)
37
+ )
38
+
39
+ def fix_keys(results: list) -> list:
40
+ """Fix 'match' key โ†’ team_1 / team_2 if model returns it."""
41
+ for item in results:
42
+ if "match" in item and "team_1" not in item:
43
+ parts = item.pop("match").split(" - ", 1)
44
+ item["team_1"] = parts[0].strip() if len(parts) > 0 else None
45
+ item["team_2"] = parts[1].strip() if len(parts) > 1 else None
46
+ return results
47
+
48
+ def normalize(result: list) -> list:
49
+ keys = ["league", "team_1", "team_2", "prediction", "date", "odds"]
50
+ if result and not isinstance(result[0], (dict, list)):
51
+ return [dict(zip(keys, result))]
52
+ normalized = []
53
+ for item in result:
54
+ if isinstance(item, str):
55
+ try:
56
+ item = json.loads(item)
57
+ except:
58
+ continue
59
+ if isinstance(item, list):
60
+ item = dict(zip(keys, item))
61
+ if isinstance(item, dict):
62
+ normalized.append(item)
63
+ return normalized
64
+
65
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
66
+ # INFERENCE
67
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
68
+ def extract(text: str, debug: bool = False) -> list:
69
+ text = clean_input(text)
70
+
71
+ response = llm.create_chat_completion(
72
+ messages=[
73
+ {"role": "system", "content": SYSTEM_PROMPT},
74
+ {"role": "user", "content": text},
75
+ ],
76
+ temperature=0.0,
77
+ max_tokens=512,
78
+ stop=["<|im_end|>", "<|endoftext|>"],
79
+ )
80
+
81
+ raw = response["choices"][0]["message"]["content"].strip()
82
+
83
+ if debug:
84
+ print(f"[raw] {repr(raw)}")
85
+
86
+ try:
87
+ result = json.loads(raw)
88
+ result = normalize(result if isinstance(result, list) else [result])
89
+ result = fix_keys(result)
90
+ return result
91
+ except json.JSONDecodeError:
92
+ print(f"[!] Could not parse JSON:\n{raw}")
93
+ return []
94
+
95
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
96
+ # TEST
97
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
98
+ if __name__ == "__main__":
99
+ tests = [
100
+ # single tip
101
+ """โšฝ๏ธ Prediction of the Day โšฝ๏ธ
102
+ Date: 24/03/2026
103
+ League: Eerste divisie Netherlands
104
+ Match: FC Emmen - SC Cambuur
105
+ Kick off: 20:00 WAT
106
+ โœ…Over 1.5
107
+ โœ…Odds @1.13 on BETANO""",
108
+
109
+ # multi tip real format
110
+ """โšฝ๏ธ ๐๐ซ๐ž๐๐ข๐œ๐ญ๐ข๐จ๐ง ๐จ๐Ÿ ๐ญ๐ก๐ž ๐ƒ๐š๐ฒ โšฝ๏ธ
111
+ ๐ƒ๐š๐ญ๐ž: 24/03/2026
112
+ ๐‹๐ž๐š๐ ๐ฎ๐ž: League 1 England
113
+ ๐Œ๐š๐ญ๐œ๐ก: Doncaster Rovers - Port Vale
114
+ ๐Š๐ข๐œ๐ค ๐จ๐Ÿ๐Ÿ: 20:45 WAT
115
+ โœ…Under 3.5
116
+ โœ…Odds @1.36 on BETANO
117
+ โšฝ๏ธ ๐—™๐—ผ๐—ผ๐˜๐—ฏ๐—ฎ๐—น๐—น ๐—ง๐—ถ๐—ฝ ๐Ÿฎ โšฝ๏ธ
118
+ ๐ƒ๐š๐ญ๐ž: 24/03/2026
119
+ ๐‹๐ž๐š๐ ๐ฎ๐ž: La Liga
120
+ ๐Œ๐š๐ญ๐œ๐ก: Real Madrid - Barcelona
121
+ ๐Š๐ข๐œ๐ค ๐จ๐Ÿ๐Ÿ: 21:00 WAT
122
+ โœ…1X
123
+ โœ…Odds @1.42 on BETANO""",
124
+
125
+ # noisy missing date
126
+ """wow predictions
127
+ MATCH: Juventus VS Napoli
128
+ League: Serie A
129
+ we forecast Over 2.5
130
+ Odds 1.75""",
131
+ ]
132
+
133
+ for i, test in enumerate(tests, 1):
134
+ print(f"\n{'='*50}")
135
+ print(f"TEST {i}: {test[:80]}...")
136
+ result = extract(test)
137
+ print(json.dumps(result, indent=2, ensure_ascii=False))