aedupuga commited on
Commit
cf0fbb1
·
verified ·
1 Parent(s): 310dc85

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +256 -0
app.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import smolagents
3
+ import llama_cpp
4
+ import pandas as pd
5
+ import numpy as np
6
+ import gradio as gr
7
+
8
+ # 1. LLM Model Definition
9
+ MODEL_REPO = "bartowski/Qwen_Qwen3-4B-Instruct-2507-GGUF"
10
+ MODEL_FILE = "Qwen_Qwen3-4B-Instruct-2507-Q4_K_M.gguf"
11
+
12
+ llm = llama_cpp.Llama.from_pretrained(
13
+ repo_id=MODEL_REPO,
14
+ filename=MODEL_FILE,
15
+ n_ctx=4096*4,
16
+ n_threads=8,
17
+ n_layers=-1,
18
+ verbose=False
19
+ )
20
+
21
+ # 2. Data Loading and Processing
22
+ SHEET_ID = '1h6gl0reY5iT2Q3_hb8pemP5Hi4OMDYcE'
23
+ BASE_URL = f'https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&gid='
24
+
25
+ COURSES_GID = 1645090689 # GID for the 'Courses' tab
26
+ TOOLS_GID = 2123942435 # GID for the 'Tools' tab
27
+
28
+ courses_url = f'{BASE_URL}{COURSES_GID}'
29
+ tools_url = f'{BASE_URL}{TOOLS_GID}'
30
+
31
+ courses_df = pd.read_csv(courses_url)
32
+ tools_df = pd.read_csv(tools_url)
33
+
34
+ course_info = {
35
+ str(row["Code"]): {
36
+ "name": row["Name"],
37
+ "description": row["Description"]
38
+ }
39
+ for _, row in courses_df.iterrows()
40
+ }
41
+
42
+ machine_to_course = {}
43
+ for _, row in tools_df.iterrows():
44
+ machine_name = row["Name"]
45
+ required_course = row["Required Course"]
46
+
47
+ if pd.isna(required_course):
48
+ machine_to_course[machine_name] = "No_Training_Required"
49
+ else:
50
+ machine_to_course[machine_name] = str(int(required_course))
51
+
52
+ # 3. LlamaCppModel and MachineTrainingTool Class Definitions
53
+ class LlamaCppModel(smolagents.Model):
54
+ """
55
+ Thin wrapper for a llama.cpp OpenAI-compatible client.
56
+ Pass in an object exposing `create_chat_completion(...)` (e.g., from llama_cpp).
57
+ """
58
+
59
+ def __init__(self, llm, model_id: str = "llama", **gen_defaults):
60
+ super().__init__()
61
+ self.llm = llm
62
+ self.model_id = model_id
63
+ self.gen_defaults = {"max_tokens": 4096, "temperature": 0.2}
64
+ self.gen_defaults.update(gen_defaults)
65
+
66
+ @staticmethod
67
+ def _content_to_str(content) -> str:
68
+ if content is None:
69
+ return ""
70
+ if isinstance(content, str):
71
+ return content
72
+
73
+ if isinstance(content, list):
74
+ parts = []
75
+ for p in content:
76
+ if isinstance(p, str):
77
+ parts.append(p)
78
+ elif isinstance(p, dict):
79
+ if p.get("type") == "text" and "text" in p:
80
+ parts.append(str(p["text"]))
81
+ elif "text" in p:
82
+ parts.append(str(p["text"]))
83
+ else:
84
+ parts.append(json.dumps(p, ensure_ascii=False))
85
+ else:
86
+ parts.append(str(p))
87
+ return "\n".join([s for s in parts if s])
88
+
89
+ if isinstance(content, dict):
90
+ if content.get("type") == "text" and "text" in content:
91
+ return str(content["text"])
92
+ if "content" in content and isinstance(content["content"], str):
93
+ return content["content"]
94
+ return json.dumps(content, ensure_ascii=False)
95
+
96
+ return str(content)
97
+
98
+ @staticmethod
99
+ def _safe_get(obj, *keys, default=None):
100
+ if isinstance(obj, dict):
101
+ for k in keys:
102
+ if k in obj:
103
+ return obj[k]
104
+ return default
105
+ for k in keys:
106
+ if hasattr(obj, k):
107
+ return getattr(obj, k)
108
+ return default
109
+
110
+ def _to_openai_messages(self, messages: list[smolagents.ChatMessage]) -> list[dict]:
111
+ oa = []
112
+ for m in messages:
113
+ role = getattr(m, "role", None) or (m.get("role") if isinstance(m, dict) else None) or "user"
114
+ content = getattr(m, "content", None) or (m.get("content") if isinstance(m, dict) else None)
115
+ text = self._content_to_str(content)
116
+
117
+ images = getattr(m, "images", None) or (m.get("images") if isinstance(m, dict) else None)
118
+ if images:
119
+ text = (text + f"\n[Note: {len(images)} image(s) omitted]").strip()
120
+
121
+ oa.append({"role": role, "content": text})
122
+ return oa
123
+
124
+ def _from_openai_message(self, msg) -> smolagents.ChatMessage:
125
+ role = self._safe_get(msg, "role", default="assistant")
126
+ content = self._safe_get(msg, "content", default="")
127
+ return smolagents.ChatMessage(role=role, content=content)
128
+
129
+ def generate(
130
+ self,
131
+ messages: list[smolagents.ChatMessage],
132
+ stop_sequences: list[str] | None = None,
133
+ response_format: dict[str, str] | None = None,
134
+ tools_to_call_from: list[smolagents.Tool] | None = None,
135
+ **kwargs,
136
+ ) -> smolagents.ChatMessage:
137
+ oa_msgs = self._to_openai_messages(messages)
138
+
139
+ params = dict(self.gen_defaults)
140
+ params.update(kwargs)
141
+ if stop_sequences:
142
+ params["stop"] = stop_sequences
143
+ if response_format:
144
+ params["response_format"] = response_format
145
+
146
+ resp = self.llm.create_chat_completion(
147
+ model=self.model_id,
148
+ messages=oa_msgs,
149
+ **params,
150
+ )
151
+
152
+ choices = self._safe_get(resp, "choices", default=[])
153
+ if not choices:
154
+ text = self._safe_get(resp, "content", default=str(resp))
155
+ return smolagents.ChatMessage(role="assistant", content=text)
156
+
157
+ first = choices[0]
158
+ message = self._safe_get(first, "message", default={})
159
+ return self._from_openai_message(message)
160
+
161
+ class MachineTrainingTool(smolagents.tools.Tool):
162
+ name = "get_machine_training_info"
163
+ description = (
164
+ "Retrieves training information for a specific machine. The `machine_name` argument should exactly match the machine's name as listed in the system (e.g., 'Laser Cutters', '3D Printers')."
165
+ )
166
+ inputs = {
167
+ "machine_name": {"type": "string", "description": "Name of the machine for which to retrieve training information"},
168
+ }
169
+ output_type = "string"
170
+
171
+ def forward(self, machine_name: str) -> str:
172
+ if machine_name in machine_to_course:
173
+ course_code = machine_to_course[machine_name]
174
+ if course_code in course_info:
175
+ course_name = course_info[course_code]['name']
176
+ return f"For {machine_name}, the required training is: '{course_name}' (Course Code: {course_code})."
177
+ else:
178
+ return f"No detailed course information found for course code '{course_code}' associated with {machine_name}."
179
+ else:
180
+ return f"No specific training information available for machine: {machine_name}."
181
+
182
+ # 4. Agent Instantiation
183
+ machine_training_tool_instance = MachineTrainingTool()
184
+
185
+ llama_cpp_model_instance = LlamaCppModel(llm)
186
+ llama_cpp_model_instance.gen_defaults['response_format'] = {'type': 'json_object'}
187
+
188
+ from smolagents.agents import ToolCallingAgent
189
+
190
+ machine_agent = ToolCallingAgent(
191
+ model=llama_cpp_model_instance,
192
+ name="MachineAgent",
193
+ instructions=(
194
+ "Your ONLY purpose is to call the 'get_machine_training_info' tool ONCE per user query. "
195
+ "You MUST then output the EXACT string provided by the tool's observation. "
196
+ "You MUST NOT add any text before or after the tool's observation. "
197
+ "You MUST NOT summarize, paraphrase, or change the tool's output in any way. "
198
+ "You MUST NOT call any other tool, especially 'final_answer'. "
199
+ "The 'machine_name' argument MUST be an exact match from the available machine list."
200
+ ),
201
+ tools=[machine_training_tool_instance],
202
+ verbosity_level=2
203
+ )
204
+
205
+ # 5. Gradio Interface Code
206
+ with gr.Blocks(title="Techspark Machine Training Agent") as demo:
207
+ gr.Markdown("## Techspark Machine Training Agent — Custom Tool Selection (smolagents + llama.cpp)")
208
+ chat = gr.Chatbot(height=420)
209
+ inp = gr.Textbox(
210
+ placeholder="Ask about machine training (e.g., 'What training do I need for the Laser Cutters?').",
211
+ label="Your question"
212
+ )
213
+
214
+ def respond(message, history):
215
+ try:
216
+ result_object = machine_agent.run(message, return_full_result=True)
217
+ tool_observation = None
218
+
219
+ if isinstance(result_object, smolagents.RunResult):
220
+ for step_dict in result_object.steps:
221
+ if isinstance(step_dict, dict):
222
+ if 'tool_calls' in step_dict and step_dict['tool_calls']:
223
+ for tool_call_info in step_dict['tool_calls']:
224
+ if tool_call_info.get('function') and tool_call_info['function'].get('name') == 'get_machine_training_info':
225
+ if 'observations' in step_dict:
226
+ tool_observation = step_dict['observations']
227
+ break
228
+ if tool_observation:
229
+ break
230
+
231
+ if tool_observation:
232
+ out = tool_observation
233
+ else:
234
+ out = result_object.text if hasattr(result_object, 'text') else str(result_object)
235
+
236
+ except Exception as e:
237
+ out = f"[Error] {e}"
238
+
239
+ history = (history or []) + [(message, out)]
240
+ return "", history
241
+
242
+ gr.Examples(
243
+ fn=respond,
244
+ examples=[
245
+ "What training do I need for the Laser Cutters?",
246
+ "What are the training requirements for the 3D Printer?",
247
+ "Can you tell me about training for Metal CNC?"
248
+ ],
249
+ inputs=[inp],
250
+ outputs=[chat]
251
+ )
252
+
253
+ inp.submit(respond, [inp, chat], [inp, chat])
254
+
255
+ # 6. Modified demo.launch() for Hugging Face Spaces
256
+ demo.launch(server_name="0.0.0.0", server_port=7860, debug=False)