aittalam commited on
Commit
0092a74
·
verified ·
1 Parent(s): 2444451

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +133 -15
index.html CHANGED
@@ -3,6 +3,7 @@
3
  <head>
4
  <title>Tool-Calling Agent With Local LLM</title>
5
  <script src="https://cdn.jsdelivr.net/pyodide/v0.27.7/full/pyodide.js"></script>
 
6
  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
7
  <meta content="utf-8" http-equiv="encoding">
8
  <style>
@@ -159,6 +160,15 @@
159
  background: #5a6268;
160
  }
161
 
 
 
 
 
 
 
 
 
 
162
  .running-indicator {
163
  display: none;
164
  background: #fff3cd;
@@ -203,16 +213,24 @@
203
  <li><strong>visit_webpage</strong>
204
  which visits a webpage at the provided url and reads its content as a markdown string
205
  </li>
 
 
 
206
  </ul>
207
  While the former tool is quite trivial and is mainly used to show how to address the
208
  <a href="https://community.openai.com/t/incorrect-count-of-r-characters-in-the-word-strawberry">"r in strawberry"</a>
209
- issue, the latter provides the LLM with the capability of accessing up-to-date information on the Web.
210
  </p>
211
  <ul>
212
  <li><strong>Configure:</strong>
213
  Make sure the Local LLM Server Configuration parameters are ok for your setup. In particular,
214
  the default expects you to have <a href="https://ollama.com/">Ollama</a> running on your system
215
- with the <a href="https://ollama.com/library/qwen3:8b">qwen3:8b</a> model installed.
 
 
 
 
 
216
  </li>
217
  <li><strong>Initialize:</strong>
218
  Set up the Python environment with Pyodide and the OpenAI agents framework
@@ -229,6 +247,11 @@
229
  </ul>
230
  </div>
231
 
 
 
 
 
 
232
  <button onclick="initializePyodide()" id="initBtn">⚙️ Initialize Pyodide Environment</button>
233
 
234
  <div id="initOutput">Click "Initialize Pyodide Environment" to set up the Python environment...</div>
@@ -237,15 +260,15 @@
237
  <h3>🔗 Local LLM Server Configuration</h3>
238
  <div class="input-group">
239
  <label for="baseUrl">Base URL:</label>
240
- <input type="text" id="baseUrl" class="slim-input" value="http://localhost:11434/v1" placeholder="Enter server base URL">
241
  </div>
242
  <div class="input-group">
243
  <label for="apiKey">API Key:</label>
244
- <input type="text" id="apiKey" class="slim-input" value="ollama" placeholder="Enter API key">
245
  </div>
246
  <div class="input-group">
247
  <label for="modelName">Model Name:</label>
248
- <input type="text" id="modelName" class="slim-input" value="qwen3:8b" placeholder="Enter model name">
249
  </div>
250
 
251
  <div class="server-presets">
@@ -263,7 +286,8 @@
263
  <small>Quick examples:</small>
264
  <button onclick="setPrompt('How many times does the letter r occur in the word strawrberrry?')">Strawrberrry</button>
265
  <button onclick="setPrompt('How many stars does the mozilla-ai/any-agent project have on GitHub?')">GitHub stars</button>
266
- <button onclick="setPrompt('What is the title of the latest post on aittalam.github.io, when was it published, what is it about, and what is the absolute URL of the image at the beginning of the post?\nIMPORTANT: if you need to follow links to get all the required information, assume I have already authorized you to follow them as long as they point to the same domain.')">Blog post</button>
 
267
  </div>
268
  </div>
269
 
@@ -279,6 +303,11 @@
279
  let pyodide;
280
  let isPyodideReady = false;
281
 
 
 
 
 
 
282
  function showRunning(message = "Python running") {
283
  const indicator = document.getElementById('runningIndicator');
284
  indicator.style.display = 'block';
@@ -297,6 +326,29 @@
297
  btn.disabled = disabled;
298
  }
299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  function setOllamaDefaults() {
301
  document.getElementById('baseUrl').value = 'http://localhost:11434/v1';
302
  document.getElementById('apiKey').value = 'ollama';
@@ -306,10 +358,9 @@
306
  function setLMStudioDefaults() {
307
  document.getElementById('baseUrl').value = 'http://localhost:1234/v1';
308
  document.getElementById('apiKey').value = 'lmstudio';
309
- document.getElementById('modelName').value = 'deepseek/deepseek-r1-0528-qwen3-8b';
310
  }
311
 
312
-
313
  function logToElement(message, type = 'info', element_id) {
314
  const output = document.getElementById(element_id);
315
  const timestamp = new Date().toLocaleTimeString();
@@ -343,6 +394,12 @@
343
  }
344
 
345
  async function initializePyodide() {
 
 
 
 
 
 
346
  // Disable init button during setup
347
  document.getElementById('initBtn').disabled = true;
348
 
@@ -360,10 +417,13 @@
360
  ###### YOUR PYTHON DEPENDENCIES ARE INSTALLED HERE ######
361
 
362
  import micropip
363
- await micropip.install("openai-agents==0.2.0")
 
 
 
364
  await micropip.install("sqlite3==1.0.0")
365
- await micropip.install("requests==2.31.0")
366
  await micropip.install("markdownify==1.1.0")
 
367
  `);
368
  logInit("openai-agents installed successfully", 'success');
369
 
@@ -415,6 +475,10 @@ set_tracing_disabled(True)
415
  return;
416
  }
417
 
 
 
 
 
418
  // Disable button during execution
419
  document.getElementById('runBtn').disabled = true;
420
  showRunning("Running Python code");
@@ -422,6 +486,11 @@ set_tracing_disabled(True)
422
  try {
423
  logAgent("🤖 Setting up agent and running...");
424
  logAgent(`Server: ${baseUrl} | Model: ${modelName}`);
 
 
 
 
 
425
  logAgent(`Prompt: "${prompt}"`);
426
 
427
  // Run the agent and print everything in Python
@@ -441,6 +510,7 @@ from agents import (
441
  )
442
  from markdownify import markdownify
443
  from requests.exceptions import RequestException
 
444
 
445
  def _truncate_content(content: str, max_length: int) -> str:
446
  if len(content) <= max_length:
@@ -457,27 +527,71 @@ def count_character_occurrences(word: str, char: str):
457
  return word.count(char)
458
 
459
  @function_tool
460
- def visit_webpage(url: str) -> str:
461
  """Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages.
462
 
463
  Args:
464
  url: The url of the webpage to visit.
 
 
 
465
 
466
  """
467
  try:
468
- response = requests.get(url)
469
  response.raise_for_status()
470
 
471
- markdown_content = markdownify(response.text).strip() # type: ignore[no-untyped-call]
472
 
473
  markdown_content = re.sub(r"\\n{2,}", "\\n", markdown_content)
474
 
475
- return _truncate_content(markdown_content, 10000)
 
 
 
 
476
  except RequestException as e:
477
  return f"Error fetching the webpage: {e!s}"
478
  except Exception as e:
479
  return f"An unexpected error occurred: {e!s}"
480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  async def test_agent():
482
  print("=== STARTING AGENT TEST ===")
483
  try:
@@ -489,10 +603,14 @@ async def test_agent():
489
  )
490
  set_default_openai_client(external_client)
491
 
 
 
 
 
492
  agent = Agent(
493
  name="Tool caller",
494
  instructions="You are a helpful agent. Use the available tools to answer the questions.",
495
- tools=[count_character_occurrences, visit_webpage],
496
  model=OpenAIChatCompletionsModel(
497
  model="${modelName.replace(/'/g, "\\'")}",
498
  openai_client=external_client,
 
3
  <head>
4
  <title>Tool-Calling Agent With Local LLM</title>
5
  <script src="https://cdn.jsdelivr.net/pyodide/v0.27.7/full/pyodide.js"></script>
6
+ <script src="config.js"></script>
7
  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
8
  <meta content="utf-8" http-equiv="encoding">
9
  <style>
 
160
  background: #5a6268;
161
  }
162
 
163
+ .config-info {
164
+ background: #e7f3ff;
165
+ border: 1px solid #b8daff;
166
+ border-radius: 5px;
167
+ padding: 10px;
168
+ margin-bottom: 20px;
169
+ font-size: 14px;
170
+ }
171
+
172
  .running-indicator {
173
  display: none;
174
  background: #fff3cd;
 
213
  <li><strong>visit_webpage</strong>
214
  which visits a webpage at the provided url and reads its content as a markdown string
215
  </li>
216
+ <li><strong>search_tavily</strong>
217
+ which performs web searches using Tavily API (requires TAVILY_API_KEY in config.js)
218
+ </li>
219
  </ul>
220
  While the former tool is quite trivial and is mainly used to show how to address the
221
  <a href="https://community.openai.com/t/incorrect-count-of-r-characters-in-the-word-strawberry">"r in strawberry"</a>
222
+ issue, the latter two provide the LLM with the capability of accessing up-to-date information on the Web.
223
  </p>
224
  <ul>
225
  <li><strong>Configure:</strong>
226
  Make sure the Local LLM Server Configuration parameters are ok for your setup. In particular,
227
  the default expects you to have <a href="https://ollama.com/">Ollama</a> running on your system
228
+ with the <a href="https://ollama.com/library/qwen3:8b">qwen3:8b</a> model installed. You
229
+ can also click the <strong>LM Studio</strong> preset button if you are using
230
+ <a href="https://lmstudio.ai/">LM Studio</a>, and make sure to update your model name accordingly.
231
+ Optionally, add your TAVILY_API_KEY to config.js to enable web search functionality.
232
+ <br/><strong>NOTE</strong>: if you are using LM Studio with a thinking model and are getting tool
233
+ calls directly in the model's response, disable thinking in the "Edit model default parameters" section.
234
  </li>
235
  <li><strong>Initialize:</strong>
236
  Set up the Python environment with Pyodide and the OpenAI agents framework
 
247
  </ul>
248
  </div>
249
 
250
+ <div class="config-info">
251
+ <strong>📋 Configuration:</strong> Config loaded from config.js
252
+ <span id="configStatus"></span>
253
+ </div>
254
+
255
  <button onclick="initializePyodide()" id="initBtn">⚙️ Initialize Pyodide Environment</button>
256
 
257
  <div id="initOutput">Click "Initialize Pyodide Environment" to set up the Python environment...</div>
 
260
  <h3>🔗 Local LLM Server Configuration</h3>
261
  <div class="input-group">
262
  <label for="baseUrl">Base URL:</label>
263
+ <input type="text" id="baseUrl" class="slim-input" value="http://localhost:1234/v1" placeholder="Enter server base URL">
264
  </div>
265
  <div class="input-group">
266
  <label for="apiKey">API Key:</label>
267
+ <input type="text" id="apiKey" class="slim-input" value="lmstudio" placeholder="Enter API key">
268
  </div>
269
  <div class="input-group">
270
  <label for="modelName">Model Name:</label>
271
+ <input type="text" id="modelName" class="slim-input" value="qwen/qwen3-8b" placeholder="Enter model name">
272
  </div>
273
 
274
  <div class="server-presets">
 
286
  <small>Quick examples:</small>
287
  <button onclick="setPrompt('How many times does the letter r occur in the word strawrberrry?')">Strawrberrry</button>
288
  <button onclick="setPrompt('How many stars does the mozilla-ai/any-agent project have on GitHub?')">GitHub stars</button>
289
+ <button onclick="setPrompt('What is the title of the latest post on https:\/\/aittalam.github.io, when was it published, what is it about, and what is the absolute URL of the image at the beginning of the post?\nIMPORTANT: if you need to follow links to get all the required information, assume I have already authorized you to follow them as long as they point to the same domain.')">Blog post</button>
290
+ <button onclick="setPrompt('What are 5 tv shows that are trending in 2025? Please provide the name of the show, the exact release date, the genre, and a brief description of the show.\nIMPORTANT: if you need to follow links to get all the required information, assume I have already authorized you to follow them. Always download full webpage contents.')">Trending TV Shows</button>
291
  </div>
292
  </div>
293
 
 
303
  let pyodide;
304
  let isPyodideReady = false;
305
 
306
+ // Check config on page load
307
+ window.addEventListener('load', function() {
308
+ checkConfig();
309
+ });
310
+
311
  function showRunning(message = "Python running") {
312
  const indicator = document.getElementById('runningIndicator');
313
  indicator.style.display = 'block';
 
326
  btn.disabled = disabled;
327
  }
328
 
329
+ function checkConfig() {
330
+ const configStatus = document.getElementById('configStatus');
331
+ let statusParts = [];
332
+
333
+ if (typeof window.APP_CONFIG === 'undefined') {
334
+ configStatus.innerHTML = ' - <span style="color: #dc3545;">❌ Config not loaded</span>';
335
+ return { configLoaded: false, tavilyAvailable: false };
336
+ }
337
+
338
+ // Check if Tavily API key is available
339
+ const tavilyAvailable = window.APP_CONFIG.TAVILY_API_KEY &&
340
+ window.APP_CONFIG.TAVILY_API_KEY !== 'your-tavily-api-key-here';
341
+
342
+ if (tavilyAvailable) {
343
+ statusParts.push('<span style="color: #28a745;">✅ Tavily API key configured</span>');
344
+ } else {
345
+ statusParts.push('<span style="color: #ffc107;">⚠️ Tavily API key not set (search_tavily tool will be disabled)</span>');
346
+ }
347
+
348
+ configStatus.innerHTML = ' - ' + statusParts.join(', ');
349
+ return { configLoaded: true, tavilyAvailable: tavilyAvailable };
350
+ }
351
+
352
  function setOllamaDefaults() {
353
  document.getElementById('baseUrl').value = 'http://localhost:11434/v1';
354
  document.getElementById('apiKey').value = 'ollama';
 
358
  function setLMStudioDefaults() {
359
  document.getElementById('baseUrl').value = 'http://localhost:1234/v1';
360
  document.getElementById('apiKey').value = 'lmstudio';
361
+ document.getElementById('modelName').value = 'mistralai/devstral-small-2507';
362
  }
363
 
 
364
  function logToElement(message, type = 'info', element_id) {
365
  const output = document.getElementById(element_id);
366
  const timestamp = new Date().toLocaleTimeString();
 
394
  }
395
 
396
  async function initializePyodide() {
397
+ // Check config first
398
+ const configCheck = checkConfig();
399
+ if (!configCheck.configLoaded) {
400
+ logInit("Config not loaded, but proceeding anyway", 'warning');
401
+ }
402
+
403
  // Disable init button during setup
404
  document.getElementById('initBtn').disabled = true;
405
 
 
417
  ###### YOUR PYTHON DEPENDENCIES ARE INSTALLED HERE ######
418
 
419
  import micropip
420
+ await micropip.install("typing-extensions>=4.12.2")
421
+ await micropip.install("openai==1.99.9")
422
+ await micropip.install("mcp==1.12.4")
423
+ await micropip.install("openai-agents==0.2.6")
424
  await micropip.install("sqlite3==1.0.0")
 
425
  await micropip.install("markdownify==1.1.0")
426
+ await micropip.install("tavily-python==0.7.10")
427
  `);
428
  logInit("openai-agents installed successfully", 'success');
429
 
 
475
  return;
476
  }
477
 
478
+ // Check if Tavily API key is available
479
+ const configCheck = checkConfig();
480
+ const tavilyApiKey = configCheck.tavilyAvailable ? window.APP_CONFIG.TAVILY_API_KEY : null;
481
+
482
  // Disable button during execution
483
  document.getElementById('runBtn').disabled = true;
484
  showRunning("Running Python code");
 
486
  try {
487
  logAgent("🤖 Setting up agent and running...");
488
  logAgent(`Server: ${baseUrl} | Model: ${modelName}`);
489
+ if (tavilyApiKey) {
490
+ logAgent("Tavily search enabled", 'success');
491
+ } else {
492
+ logAgent("Tavily search disabled (no API key)", 'warning');
493
+ }
494
  logAgent(`Prompt: "${prompt}"`);
495
 
496
  // Run the agent and print everything in Python
 
510
  )
511
  from markdownify import markdownify
512
  from requests.exceptions import RequestException
513
+ from tavily.tavily import TavilyClient
514
 
515
  def _truncate_content(content: str, max_length: int) -> str:
516
  if len(content) <= max_length:
 
527
  return word.count(char)
528
 
529
  @function_tool
530
+ def visit_webpage(url: str, timeout: int = 30, max_length: int = None) -> str:
531
  """Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages.
532
 
533
  Args:
534
  url: The url of the webpage to visit.
535
+ timeout: The timeout in seconds for the request.
536
+ max_length: The maximum number of characters of text that can be returned.
537
+ If not provided, the full webpage is returned.
538
 
539
  """
540
  try:
541
+ response = requests.get(url, timeout=timeout)
542
  response.raise_for_status()
543
 
544
+ markdown_content = markdownify(response.text).strip()
545
 
546
  markdown_content = re.sub(r"\\n{2,}", "\\n", markdown_content)
547
 
548
+ if max_length:
549
+ return _truncate_content(markdown_content, max_length)
550
+
551
+ return str(markdown_content)
552
+
553
  except RequestException as e:
554
  return f"Error fetching the webpage: {e!s}"
555
  except Exception as e:
556
  return f"An unexpected error occurred: {e!s}"
557
 
558
+
559
+ @function_tool
560
+ def search_tavily(query: str, include_images: bool = False) -> str:
561
+ """Perform a Tavily web search based on your query and return the top search results.
562
+
563
+ See https://blog.tavily.com/getting-started-with-the-tavily-search-api for more information.
564
+
565
+ Args:
566
+ query (str): The search query to perform.
567
+ include_images (bool): Whether to include images in the results.
568
+
569
+ Returns:
570
+ The top search results as a formatted string.
571
+
572
+ """
573
+ api_key='${tavilyApiKey ? tavilyApiKey.replace(/'/g, "\\'") : ""}'
574
+
575
+ if not api_key:
576
+ return "TAVILY_API_KEY not configured in config.js."
577
+ try:
578
+ client = TavilyClient(api_key)
579
+ response = client.search(query, include_images=include_images)
580
+ results = response.get("results", [])
581
+ output = []
582
+ for result in results:
583
+ output.append(
584
+ f"[{result.get('title', 'No Title')}]({result.get('url', '#')})\\n{result.get('content', '')}"
585
+ )
586
+ if include_images and "images" in response:
587
+ output.append("\\nImages:")
588
+ for image in response["images"]:
589
+ output.append(image)
590
+ return "\\n\\n".join(output) if output else "No results found."
591
+ except Exception as e:
592
+ return f"Error performing Tavily search: {e!s}"
593
+
594
+
595
  async def test_agent():
596
  print("=== STARTING AGENT TEST ===")
597
  try:
 
603
  )
604
  set_default_openai_client(external_client)
605
 
606
+ # Build tools list conditionally
607
+ tools = [count_character_occurrences, visit_webpage]
608
+ ${tavilyApiKey ? 'tools.append(search_tavily)' : '# search_tavily tool not added (no API key)'}
609
+
610
  agent = Agent(
611
  name="Tool caller",
612
  instructions="You are a helpful agent. Use the available tools to answer the questions.",
613
+ tools=tools,
614
  model=OpenAIChatCompletionsModel(
615
  model="${modelName.replace(/'/g, "\\'")}",
616
  openai_client=external_client,