From 972d6f9bb3110a6540c4b2fb3784ef78cc1571d2 Mon Sep 17 00:00:00 2001 From: Steve Nyemba Date: Fri, 23 Oct 2026 18:16:25 -0500 Subject: [PATCH] bug fixes with proxy --- qcms-manifest.json | 2 +- www/html/_assets/js/studio.js | 30 +++++++---------- www/html/_plugins/agent.py | 37 +++++++++++++++------ www/html/_plugins/readme.md | 42 ------------------------ www/html/about.html | 6 ++-- www/html/features.html | 16 ++++----- www/html/index.html | 62 +++++++++++++++++------------------ www/html/menu.html | 18 ++++------ www/html/search.html | 3 +- www/html/setup-ai.html | 6 ++-- www/html/wizard.html | 4 +-- 11 files changed, 94 insertions(+), 132 deletions(-) delete mode 100644 www/html/_plugins/readme.md diff --git a/qcms-manifest.json b/qcms-manifest.json index 4223b35..21d6ed7 100644 --- a/qcms-manifest.json +++ b/qcms-manifest.json @@ -6,7 +6,7 @@ "threaded": true, "host": "0.0.0.0" }, - "context": "", + "context": "studio", "logo": "_assets/images/logo.png", "source": { "id": "disk", diff --git a/www/html/_assets/js/studio.js b/www/html/_assets/js/studio.js index 5318632..463f993 100644 --- a/www/html/_assets/js/studio.js +++ b/www/html/_assets/js/studio.js @@ -15,11 +15,11 @@ studio.defaults = { 'mariadb':'SHOW TABLES','files':'{"query":"index > -1"}' } studio.init = function (_context){ - studio._context = _context + qcms.context = _context } studio.dbe = {} // studio.dbe.get = function (){ -// var uri = [studio._context,'api/dbe/get'] +// var uri = [qcms.context,'api/dbe/get'] // uri = uri.join('/') // var http = HttpClient.instance() // http.get(uri,function(x){ @@ -30,7 +30,7 @@ studio.dbe = {} // } studio.dbe.providers = function (_render){ - var uri = [studio._context,'api/transport/providers'] + var uri = [qcms.context,'api/transport/providers'] uri = uri.join('/') var http = HttpClient.instance() http.get(uri,function(x){ @@ -44,7 +44,7 @@ studio.dbe.providers = function (_render){ // studio.dbe.apply = function (label,_query){ -// var uri = [studio._context,'api/transport/apply'] +// var uri = [qcms.context,'api/transport/apply'] // uri = uri.join('/') // var http = HttpClient.instance() @@ -205,7 +205,7 @@ studio.grid = function (){ // } // ] - spreadsheet.saveUrl = studio._context+'api/io/write' + spreadsheet.saveUrl = qcms.context+'api/io/write' spreadsheet.appendTo(_id) $(_id)[0].spreadsheet = spreadsheet @@ -230,8 +230,8 @@ studio.frame = function (_args){ // Render the template this.read = function(_args,_query,_compute){ var _label = _args.label - var uri = [studio._context,'api/transport/apply'] - uri = uri.join('/') + //var uri = [qcms.context,'api/transport/apply'] + var uri = `${qcms.context}api/transport/apply` //'api/transport/apply'] if (_args.AI && sessionStorage.AI !="0"){ uri = uri.replace(/transport/g,'agent') } @@ -250,7 +250,6 @@ studio.frame = function (_args){ $(_id).empty() $(_id).append($('')) if(x.status == 200 && x.readyState == 4){ - console.log(x.responseText) _r = JSON.parse(x.responseText) // sessionStorage._data = x.responseText @@ -306,7 +305,7 @@ studio.frame = function (_args){ _message = $(doc.querySelector('title')).text().replace(/\/\/ Werkzeug Debugger/g,'').trim() sessionStorage.error_mesage = _message - dialog.show({uri:'www/html/error.html',title:'Error Found',context:studio._context}) + dialog.show({uri:'www/html/error.html',title:'Error Found',context:qcms.context}) } }) @@ -318,11 +317,7 @@ studio.frame = function (_args){ form.append('file',file) http.setData(form) var _index = $(_id)[0].spreadsheet.sheets.length - // _name = file.name - // $(_id)[0].spreadsheet.insertSheet([{name:_name,_index:1}],0) - // console.log([_index, $(_id)[0].spreadsheet.sheets.length]) - // $(_id)[0].spreadsheet.sheets[_index].name = `sheet {_index}` http.post(_uri,function(x){ if(x.status == 200 && x.readyState == 4){ var _data = JSON.parse(x.responseText) @@ -419,7 +414,7 @@ studio.frame = function (_args){ } this.export = function (_label,spreadsheet){ // spreadsheet.save({filename:_label+'-export.xlsx'}) - uri = studio._context+'api/io/write' + uri = qcms.context+'api/io/write' filename = _label+'.xlsx' spreadsheet.saveAsJson().then(response => { @@ -469,8 +464,7 @@ studio.frame = function (_args){ var _args = this._args var _importSheet = this.open var _exportSheet = this.export - - var _icon = $('').attr('src',this._args.icon) + var _icon = $('').attr('src',`${qcms.context}${this._args.icon}`) _text = _args.provider if (_args.AI){ // @@ -521,7 +515,7 @@ studio.frame = function (_args){ sessionStorage._label = _args.label sessionStorage._query = _query - var _kwargs = {uri:'www/html/python.html',title:'Generated Python Code',context:studio._context} + var _kwargs = {uri:'www/html/python.html',title:'Generated Python Code',context:qcms.context} dialog.show(_kwargs) }) @@ -543,7 +537,7 @@ studio.frame = function (_args){ }) _wizButton = $('
') _wizButton.on('click',function(){ - dialog.show({uri:'www/html/wizard.html',title:'Create New Connection',context:studio._context}) + dialog.show({uri:'www/html/wizard.html',title:'Create New Connection',context:qcms.context}) }) _expandButton = $('
') diff --git a/www/html/_plugins/agent.py b/www/html/_plugins/agent.py index 692a342..c531615 100644 --- a/www/html/_plugins/agent.py +++ b/www/html/_plugins/agent.py @@ -52,9 +52,11 @@ class QueryOutput(BaseModel): return v class Tool: - def __init__(self,_dbr) : + _cache = {} + def __init__(self,_dbr,label) : self._dbr = _dbr self._provider = _dbr._agent._engine.name + self._label = label def catalog(self): @@ -63,19 +65,34 @@ class Tool: """ global _map _query = _map.get(self._provider) + # - return self.read(_query) + if self._label not in Tool._cache : + Tool._cache[self._label] = {'catalog':self.read(_query),'schema':None} + # + # We hope to gain something by caching this kind of request + _catalog = Tool._cache[self._label].get('catalog',None) + return _catalog def schema(self,table:str) ->dict: """ This function returns the schema for a given database, if table is not provided the entire catalog will be returned with the list of tables """ - _query = f""" {_map[self._provider]} WHERE name = '{table}' """ - if self._provider in ['sqlite','sqlite3'] : - _info = self.read(f"select * from {table} limit 20").dtypes - return pd.DataFrame({"column_name":_info.dtypes.index.tolist()[1:],"data_type": [x.name for x in _info.dtypes.tolist()[1:]]}).to_dict(orient='records') - pass - else: - return self.read(_query) + _schema = None + if self._label not in Tool._cache : + Tool._cache[self._label] = {'catalog':None,'schema':None} + elif Tool._cache[self._label].get('schema') : + _schema = Tool._cache[self._label].get('schema') + else : + _query = f""" {_map[self._provider]} WHERE name = '{table}' """ + + if self._provider in ['sqlite','sqlite3'] : + _info = self.read(f"select * from {table} limit 20").dtypes + _schema = pd.DataFrame({"column_name":_info.dtypes.index.tolist()[1:],"data_type": [x.name for x in _info.dtypes.tolist()[1:]]}).to_dict(orient='records') + + else: + _schema = self.read(_query) + Tool._cache[self._label]['schema'] = _schema + return _schema def read(self,query:str)->dict: """ This function will execute a query against a given label/database. The query must adhere to the schema of the tables involved. @@ -117,7 +134,7 @@ class TransportAgent: return ChatOllama(**kwargs) def get(self,label,query): dbr = transport.get.reader(label=label) - tools = Tool(dbr) + tools = Tool(dbr,label) # self.chatbot.bind_tools ([tools.catalog,tools.read]) _message = f""" diff --git a/www/html/_plugins/readme.md b/www/html/_plugins/readme.md deleted file mode 100644 index e6a3f4c..0000000 --- a/www/html/_plugins/readme.md +++ /dev/null @@ -1,42 +0,0 @@ -# Transport Agent Notebook - -This notebook provides a way to interact with the **data-transport MCP server** using LangChain and Marimo. - -## Overview - -The `notebook.py` file implements a `TransportAgent` class that bridges the gap between Large Language Models (LLMs) and the capabilities of the data-transport system via the **Model Context Protocol (MCP)**. It also demonstrates how to perform direct data access using the `transport` library. - -## How it Works - -The `notebook.py` file demonstrates how to integrate MCP with LangChain through the following steps: - -1. **Dynamic Tool Discovery**: Instead of hard-coding tool definitions, the `TransportAgent` fetches the OpenAPI specification from the MCP server (at `http://localhost:8090/openapi.json`). -2. **Schema Mapping**: It automatically parses the OpenAPI `components` and `paths` to generate LangChain `StructuredTool` instances. This ensures that the agent always has the latest tools available as defined by the MCP server. -3. **Agent Construction**: These tools are then passed into a LangChain agent. The agent uses these tools to fulfill user requests, such as listing databases or performing data analysis. -4. **Interactive Interface**: The Marimo framework is used to provide a clean, interactive UI where users can type questions and see the agent's thought process and results. - -## Code Breakdown - -### `TransportAgent` Class -The heart of the notebook. It manages: -- **LLM Configuration**: Supports `ollama`, `openai`, and `azure_openai` backends. -- **Tool Initialization (`_init_mcp_tools`)**: - - Fetches the `openapi.json` from the local MCP server. - - Maps API types (string, integer, etc.) to Python types. - - Dynamically creates `StructuredTool` objects with proper descriptions and argument schemas. -- **Agent Initialization (`_init_agent`)**: Combines the chosen LLM with the discovered tools into a functional agent. -- **Query Handling (`get`)**: Takes a natural language prompt and returns the final response from the agent's execution chain. - -## How to leverage MCP with LangChain (Summary) - -To integrate MCP with LangChain in this project: -1. **Expose via OpenAPI**: The MCP server must provide an `openapi.json` endpoint. -2. **Automate Tool Creation**: Use a helper function (like `_init_mcp_tools` in this notebook) to iterate over the OpenAPI paths and generate `StructuredTool` objects. -3. **Bind to Agent**: Pass the list of generated tools to a LangChain agent (e.g., `create_agent`). -4. **Dynamic Updates**: Because the tools are loaded at runtime from the OpenAPI spec, any update to the MCP server's capabilities is automatically reflected in the agent. - -## Usage -To run this notebook, ensure the MCP server is running on `localhost:8090` and then execute the marimo notebook: -```bash -marimo edit www/html/_plugins/notebook.py -``` diff --git a/www/html/about.html b/www/html/about.html index e9b0837..efd6d49 100644 --- a/www/html/about.html +++ b/www/html/about.html @@ -54,8 +54,8 @@ // }) $(document).ready(()=>{ var layout = {on:{load:{}}} - layout.on.load.intro = ['www/html/features.html'] - bootup.init('{{system.context}}',layout) + layout.on.load.intro = ['features.html'] + bootup.init(layout) }) @@ -124,4 +124,4 @@ $(document).ready(()=>{ - \ No newline at end of file + diff --git a/www/html/features.html b/www/html/features.html index b834046..3c57c38 100644 --- a/www/html/features.html +++ b/www/html/features.html @@ -1,11 +1,11 @@ - - - - - - - + + + + + + +