Joar Paganus commited on
Commit
2b605ad
·
1 Parent(s): b76a59a

updated agent

Browse files
Files changed (4) hide show
  1. agent.py +50 -58
  2. app.py +23 -52
  3. requirements.txt +4 -1
  4. weather.py +98 -0
agent.py CHANGED
@@ -28,14 +28,6 @@ INSTRUCTIONS FOR USING TOOL RESULTS:
28
  - Use those results to answer the user’s latest question.
29
  - Summarize the results naturally. Do NOT restate the log format.
30
  - NEVER reproduce or invent <tool_results> blocks.
31
- - NEVER output lines of the form:
32
- "You have executed the following tools..."
33
- "- tool_name(args_dict) -> result"
34
- - NEVER fabricate new tool calls or logs.
35
-
36
- WHEN THERE ARE NO TOOL RESULTS:
37
- - Answer based on weather knowledge, common sense, and conversation context.
38
- - Still avoid any mention of “tools”, “calls”, “executions”, or logs.
39
 
40
  YOUR OUTPUT:
41
  - Your entire reply must be ONLY natural language directed to the user.
@@ -111,25 +103,46 @@ def parse_tool_calls(tool_output: str):
111
  calls.append((func_name, kwargs))
112
  return calls
113
 
114
- def generate_non_stream(llm, prompt, max_tokens=256, temperature=0.2, top_p=0.95):
115
- out = llm(
116
- prompt,
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  max_tokens=max_tokens,
118
  temperature=temperature,
119
  top_p=top_p,
120
- stop=["User:", "System:"],
121
- stream=False,
122
  )
123
- return out["choices"][0]["text"]
124
 
125
- def build_prompt(system_message, history, user_message):
126
- prompt = f"System: {system_message}\n"
127
- for turn in history:
128
- role = turn["role"]
129
- content = turn["content"]
130
- prompt += f"{role.capitalize()}: {content}\n"
131
- prompt += f"User: {user_message}\nAssistant:"
132
- return prompt
 
 
 
 
 
 
 
 
 
 
133
 
134
  def select_tools_with_llm(llm, user_message: str, tools_schema: list) -> list:
135
  tool_selection_system = f"""You are an expert in composing functions. You are given a question and a set of possible functions.
@@ -137,24 +150,18 @@ Based on the question, you will need to make one or more function/tool calls to
137
  If none of the functions can be used, point it out. If the given question lacks the parameters required by the function, also point it out. You should only return the function call in tools call sections.
138
 
139
  If you decide to invoke any of the function(s), you MUST put it in the format of [func_name1(params_name1=params_value1, params_name2=params_value2...), func_name2(params)]
140
- You SHOULD NOT include any other text in the response.
141
 
142
  Here is a list of functions in JSON format that you can invoke:
143
 
144
  {json.dumps(tools_schema, indent=2)}
145
  """
146
- prompt = (
147
- f"System: {tool_selection_system}\n"
148
- f"User: {user_message}\n"
149
- f"Assistant:"
150
- )
151
- raw = generate_non_stream(
152
- llm,
153
- prompt,
154
- max_tokens=256,
155
- temperature=0.2,
156
- top_p=0.95,
157
- )
158
  return parse_tool_calls(raw), raw
159
 
160
  def call_tools(tool_calls, tool_registry):
@@ -177,7 +184,7 @@ def call_tools(tool_calls, tool_registry):
177
  results.append({"name": func_name, "args": kwargs, "result": res})
178
  return results
179
 
180
- def respond(message, history, system_message, llm, tools=None):
181
  if tools is None:
182
  tools = []
183
 
@@ -186,12 +193,8 @@ def respond(message, history, system_message, llm, tools=None):
186
  tools_schema = [function_to_json(f) for f in tools]
187
 
188
  # 2. Let the LLM select tools based on the message
189
- tool_calls, initial_message = select_tools_with_llm(llm, message, tools_schema)
190
 
191
- # We wanted to use the inital_message above in the response, for example
192
- # when parameters are missing but the model was too bad at making such responses
193
- # that it had to be omitted for now.
194
-
195
  # 3. Call tools if needed, otherwise respond
196
  if tool_calls and tools:
197
  tool_results = call_tools(tool_calls, tool_registry)
@@ -199,22 +202,11 @@ def respond(message, history, system_message, llm, tools=None):
199
  for tr in tool_results:
200
  tool_info_str += f"- {tr['name']}({tr['args']}) -> {tr['result']}\n"
201
  final_system_message = f"{system_message}{LAST_SYSTEM_MESSAGE} {tool_info_str}</tool_results>\n"
202
- else:
203
- final_system_message = system_message
204
 
205
- # Call the LLM again with the results from the tools used
206
- prompt = build_prompt(final_system_message, history, message)
207
 
208
- stream = llm(
209
- prompt,
210
- max_tokens=256,
211
- temperature=0.7,
212
- top_p=0.9,
213
- stop=["User:", "System:"],
214
- stream=True,
215
- )
216
- partial = ""
217
- for out in stream:
218
- token = out["choices"][0]["text"]
219
- partial += token
220
- yield partial
 
28
  - Use those results to answer the user’s latest question.
29
  - Summarize the results naturally. Do NOT restate the log format.
30
  - NEVER reproduce or invent <tool_results> blocks.
 
 
 
 
 
 
 
 
31
 
32
  YOUR OUTPUT:
33
  - Your entire reply must be ONLY natural language directed to the user.
 
103
  calls.append((func_name, kwargs))
104
  return calls
105
 
106
+ def add_history(user_message, history, system_message):
107
+ new_history = [{"role": "system", "content": system_message}]
108
+ if history:
109
+ for el in history:
110
+ if el["role"] == "user":
111
+ user = el["content"][0]["text"]
112
+ new_history.append({"role": "user", "content": user})
113
+ elif el["role"] == "assistant":
114
+ user = el["content"][0]["text"]
115
+ new_history.append({"role": "assistant", "content": user})
116
+ new_history.append({"role": "user", "content": user_message})
117
+ return new_history
118
+
119
+ def generate_chat(llm, messages, max_tokens=256, temperature=0.2, top_p=0.95):
120
+ completion_stream = llm.create_chat_completion(
121
+ messages=messages,
122
  max_tokens=max_tokens,
123
  temperature=temperature,
124
  top_p=top_p,
125
+ stream=True,
 
126
  )
 
127
 
128
+ answer = ""
129
+ for chunk in completion_stream:
130
+ delta = chunk["choices"][0].get("delta", {})
131
+ token = delta.get("content", None)
132
+ if token:
133
+ answer += token
134
+ yield answer
135
+
136
+ def generate_non_stream_chat(llm, messages, max_tokens=256, temperature=0.2, top_p=0.95):
137
+ res = llm.create_chat_completion(
138
+ messages=messages,
139
+ max_tokens=max_tokens,
140
+ temperature=temperature,
141
+ top_p=top_p,
142
+ stream=False,
143
+ )
144
+ # Return just the final text
145
+ return res["choices"][0]["message"]["content"]
146
 
147
  def select_tools_with_llm(llm, user_message: str, tools_schema: list) -> list:
148
  tool_selection_system = f"""You are an expert in composing functions. You are given a question and a set of possible functions.
 
150
  If none of the functions can be used, point it out. If the given question lacks the parameters required by the function, also point it out. You should only return the function call in tools call sections.
151
 
152
  If you decide to invoke any of the function(s), you MUST put it in the format of [func_name1(params_name1=params_value1, params_name2=params_value2...), func_name2(params)]
153
+ If you call a function, you SHOULD NOT include any other text in the response.
154
 
155
  Here is a list of functions in JSON format that you can invoke:
156
 
157
  {json.dumps(tools_schema, indent=2)}
158
  """
159
+
160
+ messages = [
161
+ {"role": "system", "content": tool_selection_system},
162
+ {"role": "user", "content": user_message},
163
+ ]
164
+ raw = generate_non_stream_chat(llm, messages)
 
 
 
 
 
 
165
  return parse_tool_calls(raw), raw
166
 
167
  def call_tools(tool_calls, tool_registry):
 
184
  results.append({"name": func_name, "args": kwargs, "result": res})
185
  return results
186
 
187
+ def respond(user_message, history, system_message, llm, tools=None):
188
  if tools is None:
189
  tools = []
190
 
 
193
  tools_schema = [function_to_json(f) for f in tools]
194
 
195
  # 2. Let the LLM select tools based on the message
196
+ tool_calls, initial_message = select_tools_with_llm(llm, user_message, tools_schema)
197
 
 
 
 
 
198
  # 3. Call tools if needed, otherwise respond
199
  if tool_calls and tools:
200
  tool_results = call_tools(tool_calls, tool_registry)
 
202
  for tr in tool_results:
203
  tool_info_str += f"- {tr['name']}({tr['args']}) -> {tr['result']}\n"
204
  final_system_message = f"{system_message}{LAST_SYSTEM_MESSAGE} {tool_info_str}</tool_results>\n"
 
 
205
 
206
+ messages = add_history(user_message, history, final_system_message)
 
207
 
208
+ stream = generate_chat(llm, messages, temperature=0.7, top_p=0.95)
209
+ for out in stream:
210
+ yield out
211
+ else:
212
+ return initial_message
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -20,10 +20,9 @@ except Exception as e:
20
  print(f"--- INSTALLATION FAILED: {e} ---")
21
 
22
  import gradio as gr
23
- import wikipedia # This will now work
24
- from agent import respond, build_prompt
25
  from llama_cpp import Llama
26
- import random
27
 
28
  # ---------------- CONFIG ----------------
29
  BASE_REPO_ID = "unsloth/Llama-3.2-3B-Instruct-GGUF"
@@ -31,6 +30,9 @@ BASE_FILENAME = "Llama-3.2-3B-Instruct-Q4_K_M.gguf"
31
 
32
  FT_REPO_ID = "JoarP/Llama-3.2-3B-FineTome5K-gguf"
33
  FT_FILENAME = "v1"
 
 
 
34
  N_CTX = 2048
35
  N_THREADS = 2
36
 
@@ -54,15 +56,23 @@ try:
54
  )
55
  AVAILABLE_MODELS["Llama 3.2 3B"] = llm_base
56
 
 
 
 
 
 
 
 
 
 
57
  except Exception as e:
58
  print(f"Error loading model: {e}")
59
  raise e
60
 
61
 
62
  #--------------- TO RUN LOCALLY -----------------
63
-
64
  # llm = Llama(
65
- # model_path="works.gguf",
66
  # n_ctx=N_CTX,
67
  # n_threads=None,
68
  # )
@@ -72,7 +82,7 @@ except Exception as e:
72
  # }
73
  # ------------- FAST RESPONSE WITHOUT AGENT --------------
74
 
75
- def respond_fast(message, history, model_choice):
76
  """
77
  Fast path: no tools, no agent. Just a single LLM call with the
78
  given system message and chat history.
@@ -82,52 +92,16 @@ def respond_fast(message, history, model_choice):
82
  if llm is None:
83
  llm = next(iter(AVAILABLE_MODELS.values()))
84
 
85
- # Conversion logic
86
- formatted_history = format_history(history)
87
-
88
- # Build a simple chat-style prompt
89
- prompt = build_prompt("You are a helpful assistant. Just chat with the user.", formatted_history, message)
90
 
91
  # Single streaming generation
92
- stream = llm(
93
- prompt,
94
- max_tokens=256,
95
- temperature=0.7,
96
- top_p=0.9,
97
- stop=["User:", "System:"],
98
- stream=True,
99
- )
100
-
101
- partial = ""
102
  for out in stream:
103
- token = out["choices"][0]["text"]
104
- partial += token
105
- yield partial
106
-
107
- # ------------- HELP FUNCTION ------------------
108
-
109
- def format_history(history):
110
- formatted_history = []
111
- for turn in history:
112
- role = turn.get("role", "user")
113
- raw_content = turn.get("content", "")
114
- block = raw_content[0]
115
- content = block["text"]
116
- formatted_history.append({
117
- "role": role,
118
- "content": content
119
- })
120
- return formatted_history
121
 
122
 
123
  # ------------- TOOLS DEFINITIONS --------------
124
- def get_weather(location: str) -> str:
125
- """Returns weather. Args: location (city)."""
126
- return random.choice(["cloudy", "rainy", "sunny", "foobar"])
127
-
128
- def get_temperature(location: str) -> str:
129
- """Returns temperature. Args: location (city)."""
130
- return random.choice(["-10", "0", "20", "30"])
131
 
132
  def multiply(a: int, b: int) -> int:
133
  """Multiplies two integers. Args: a, b."""
@@ -152,7 +126,7 @@ def search_wikipedia(query: str) -> str:
152
  AGENTS = {
153
  "Weather": {
154
  "system_message": "You are a helpful weather assistant",
155
- "tools": [get_weather, get_temperature]
156
  },
157
  "Math": {
158
  "system_message": "You are a helpful math assistant",
@@ -164,7 +138,7 @@ AGENTS = {
164
  },
165
  }
166
 
167
- # ------------- WRAPPER FUNCTION ----------------
168
  def app_respond(message, history, model_choice, agent_choice):
169
  """Pass the selected agent to our agentic framwork"""
170
  llm = AVAILABLE_MODELS.get(model_choice)
@@ -173,12 +147,9 @@ def app_respond(message, history, model_choice, agent_choice):
173
 
174
  agent_config = AGENTS.get(agent_choice)
175
 
176
- # Conversion logic
177
- formatted_history = format_history(history)
178
-
179
  for chunk in respond(
180
  message,
181
- formatted_history, # Pass the converted history
182
  system_message=agent_config["system_message"],
183
  llm=llm,
184
  tools=agent_config["tools"]
 
20
  print(f"--- INSTALLATION FAILED: {e} ---")
21
 
22
  import gradio as gr
23
+ import wikipedia
24
+ from agent import respond, generate_chat, add_history
25
  from llama_cpp import Llama
 
26
 
27
  # ---------------- CONFIG ----------------
28
  BASE_REPO_ID = "unsloth/Llama-3.2-3B-Instruct-GGUF"
 
30
 
31
  FT_REPO_ID = "JoarP/Llama-3.2-3B-FineTome5K-gguf"
32
  FT_FILENAME = "v1"
33
+
34
+ FT_REPO_ID_2 = "JoarP/Llama-3.2-3B-Finetuning"
35
+ FT_FILENAME_2 = "FuncCall-Synthetic-Small"
36
  N_CTX = 2048
37
  N_THREADS = 2
38
 
 
56
  )
57
  AVAILABLE_MODELS["Llama 3.2 3B"] = llm_base
58
 
59
+ print("Loading model...")
60
+ llm_ft_2 = Llama.from_pretrained(
61
+ repo_id=FT_REPO_ID_2,
62
+ filename=FT_FILENAME_2,
63
+ n_ctx=N_CTX,
64
+ n_threads=N_THREADS,
65
+ )
66
+ AVAILABLE_MODELS["FuncCall FT - Llama 3.2 3B"] = llm_ft_2
67
+
68
  except Exception as e:
69
  print(f"Error loading model: {e}")
70
  raise e
71
 
72
 
73
  #--------------- TO RUN LOCALLY -----------------
 
74
  # llm = Llama(
75
+ # model_path="works2.gguf",
76
  # n_ctx=N_CTX,
77
  # n_threads=None,
78
  # )
 
82
  # }
83
  # ------------- FAST RESPONSE WITHOUT AGENT --------------
84
 
85
+ def respond_fast(user_message, history, model_choice):
86
  """
87
  Fast path: no tools, no agent. Just a single LLM call with the
88
  given system message and chat history.
 
92
  if llm is None:
93
  llm = next(iter(AVAILABLE_MODELS.values()))
94
 
95
+ history = add_history(user_message, history, "You are a helpful assistant. Just chat with the user.")
 
 
 
 
96
 
97
  # Single streaming generation
98
+ stream = generate_chat(llm, history, max_tokens=256, temperature=0.2, top_p=0.95)
 
 
 
 
 
 
 
 
 
99
  for out in stream:
100
+ yield out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
 
103
  # ------------- TOOLS DEFINITIONS --------------
104
+ from weather import get_current_weather, get_current_temperature
 
 
 
 
 
 
105
 
106
  def multiply(a: int, b: int) -> int:
107
  """Multiplies two integers. Args: a, b."""
 
126
  AGENTS = {
127
  "Weather": {
128
  "system_message": "You are a helpful weather assistant",
129
+ "tools": [get_current_weather, get_current_temperature]
130
  },
131
  "Math": {
132
  "system_message": "You are a helpful math assistant",
 
138
  },
139
  }
140
 
141
+ # ------------- WRAPPER FUNCTION FOR AGENTS ----------------
142
  def app_respond(message, history, model_choice, agent_choice):
143
  """Pass the selected agent to our agentic framwork"""
144
  llm = AVAILABLE_MODELS.get(model_choice)
 
147
 
148
  agent_config = AGENTS.get(agent_choice)
149
 
 
 
 
150
  for chunk in respond(
151
  message,
152
+ history,
153
  system_message=agent_config["system_message"],
154
  llm=llm,
155
  tools=agent_config["tools"]
requirements.txt CHANGED
@@ -1,3 +1,6 @@
1
  gradio
2
  huggingface_hub
3
-
 
 
 
 
1
  gradio
2
  huggingface_hub
3
+ openmeteo-requests
4
+ requests-cache
5
+ retry-requests
6
+ geopy
weather.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import openmeteo_requests
2
+ import pandas as pd
3
+ import requests_cache
4
+ from retry_requests import retry
5
+ from geopy.geocoders import Nominatim
6
+
7
+ def call_openmeteo_current(city: str, variable: str) -> str:
8
+ # Setup the Open-Meteo API client with cache and retry on error
9
+ cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
10
+ retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
11
+ openmeteo = openmeteo_requests.Client(session = retry_session)
12
+
13
+ url = "https://api.open-meteo.com/v1/forecast"
14
+
15
+ lat, lon = get_coordinates(city)
16
+ params = {
17
+ "latitude": lat,
18
+ "longitude": lon,
19
+ "current": variable,
20
+ }
21
+ responses = openmeteo.weather_api(url, params=params)
22
+ response = responses[0]
23
+ current = response.Current()
24
+ variable = current.Variables(0).Value()
25
+ return variable
26
+
27
+ def get_current_weather(city: str) -> str:
28
+ """Get the summary of the current weather in a city."""
29
+ weather = call_openmeteo_current(city, "weather_code")
30
+ weather_str = weather_code_to_description(weather)
31
+ return weather_str
32
+
33
+ def get_current_temperature(city: str) -> str:
34
+ """Get the current temperature in a city."""
35
+ temperature = call_openmeteo_current(city, "temperature_2m")
36
+ return str(round(temperature))
37
+
38
+ def get_current_wind_speed(city: str) -> str:
39
+ """Get the current wind speed (10m) in a city."""
40
+ wind = call_openmeteo_current(city, "wind_speed_10m")
41
+ return str(round(wind)) + " m/s"
42
+
43
+ def get_coordinates(city: str):
44
+ geolocator = Nominatim(user_agent="NewApp")
45
+ location = geolocator.geocode(city)
46
+ return location.latitude, location.longitude
47
+
48
+
49
+ def weather_code_to_description(code: float | int) -> str:
50
+ """
51
+ Convert Open-Meteo (WMO) weather codes to human-readable descriptions.
52
+ Accepts int or float values (e.g., 1.0 -> 1).
53
+ """
54
+ code = int(code)
55
+
56
+ WEATHER_CODE_MAP = {
57
+ 0: "Clear sky",
58
+
59
+ 1: "Mainly clear",
60
+ 2: "Partly cloudy",
61
+ 3: "Overcast",
62
+
63
+ 45: "Fog",
64
+ 48: "Depositing rime fog",
65
+
66
+ 51: "Light drizzle",
67
+ 53: "Moderate drizzle",
68
+ 55: "Dense drizzle",
69
+
70
+ 56: "Light freezing drizzle",
71
+ 57: "Dense freezing drizzle",
72
+
73
+ 61: "Slight rain",
74
+ 63: "Moderate rain",
75
+ 65: "Heavy rain",
76
+
77
+ 66: "Light freezing rain",
78
+ 67: "Heavy freezing rain",
79
+
80
+ 71: "Slight snowfall",
81
+ 73: "Moderate snowfall",
82
+ 75: "Heavy snowfall",
83
+
84
+ 77: "Snow grains",
85
+
86
+ 80: "Slight rain showers",
87
+ 81: "Moderate rain showers",
88
+ 82: "Violent rain showers",
89
+
90
+ 85: "Slight snow showers",
91
+ 86: "Heavy snow showers",
92
+
93
+ 95: "Thunderstorm",
94
+ 96: "Thunderstorm with slight hail",
95
+ 99: "Thunderstorm with heavy hail",
96
+ }
97
+
98
+ return WEATHER_CODE_MAP.get(code, f"Unknown weather code: {code}")