ApiLogicServer 14.5.4__py3-none-any.whl → 14.5.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api_logic_server_cli/api_logic_server.py +4 -122
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/dbml.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/api_logic_server_utils.py +29 -10
- api_logic_server_cli/create_from_model/dbml.py +16 -12
- api_logic_server_cli/database/basic_demo.sqlite +0 -0
- api_logic_server_cli/genai/genai.py +4 -0
- api_logic_server_cli/genai/genai_svcs.py +10 -2
- api_logic_server_cli/manager.py +3 -1
- api_logic_server_cli/prototypes/base/.vscode/launch.json +40 -0
- api_logic_server_cli/prototypes/base/api/api_discovery/mcp_discovery.py +58 -0
- api_logic_server_cli/prototypes/base/database/system/SAFRSBaseX.py +68 -2
- api_logic_server_cli/prototypes/base/database/system/SAFRSBaseX.pyZ +73 -0
- api_logic_server_cli/prototypes/{basic_demo/customizations/integration → base/integration/mcp}/.DS_Store +0 -0
- api_logic_server_cli/prototypes/base/integration/mcp/README_mcp.md +15 -0
- api_logic_server_cli/prototypes/{basic_demo/customizations → base}/integration/mcp/mcp_client_executor.py +91 -103
- api_logic_server_cli/prototypes/base/integration/mcp/mcp_schema.txt +47 -0
- api_logic_server_cli/prototypes/base/integration/mcp/mcp_server_discovery.json +9 -0
- api_logic_server_cli/prototypes/base/integration/mcp/test_notes.txt +37 -0
- api_logic_server_cli/prototypes/basic_demo/README.md +251 -91
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/mcp_discovery.py +1 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/models.py +27 -12
- api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +1 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/Zmcp_client_executor.py +294 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_tool_context.json +25 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/test_notes.txt +37 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +1 -20
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/email_request.py +47 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/mcp_client_executor_request.py +320 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/simple_constraints.py +25 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/admin.yaml +39 -32
- api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/iteration/ui/admin/admin.yaml +39 -44
- api_logic_server_cli/prototypes/manager/.vscode/launch.json +21 -0
- api_logic_server_cli/prototypes/manager/{README.md → READMEz.md} +4 -4
- api_logic_server_cli/prototypes/manager/REAMDE.md +1057 -0
- api_logic_server_cli/prototypes/manager/system/genai/mcp_learning/mcp.prompt +27 -0
- api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.sh +2 -1
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/METADATA +1 -1
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/RECORD +46 -42
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/WHEEL +1 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/3_executor_test_agent.py +0 -52
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/README_functon.md +0 -201
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json +0 -17
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/nw-swagger_3.json +0 -1731
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/snippets.txt +0 -5
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3 genai_demo_with_get.json +0 -1731
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3.json +0 -1782
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo.json +0 -264
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo_with_update.json +0 -1782
- api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/auto_discovery.py +0 -52
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/entry_points.txt +0 -0
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.14.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
2
|
+
from sqlalchemy import Column, DECIMAL, Date, ForeignKey, Integer, String
|
|
3
|
+
from safrs import SAFRSBase
|
|
4
|
+
from flask_login import UserMixin
|
|
5
|
+
import safrs, flask_sqlalchemy
|
|
6
|
+
from safrs import jsonapi_attr
|
|
7
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
Base = declarative_base() # type: flask_sqlalchemy.model.DefaultMeta
|
|
11
|
+
#vh new x
|
|
12
|
+
@classmethod
|
|
13
|
+
def jsonapi_filter(cls):
|
|
14
|
+
"""
|
|
15
|
+
Use this to override SAFRS JSON:API filtering
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
_type_: SQLAlchemy query filter
|
|
19
|
+
"""
|
|
20
|
+
from sqlalchemy import text, or_, and_
|
|
21
|
+
from flask import request
|
|
22
|
+
expressions = []
|
|
23
|
+
sqlWhere = ""
|
|
24
|
+
query = cls._s_query
|
|
25
|
+
if args := request.args:
|
|
26
|
+
from api.system.expression_parser import advancedFilter
|
|
27
|
+
expressions, sqlWhere = advancedFilter(cls, args)
|
|
28
|
+
if sqlWhere != "":
|
|
29
|
+
return query.filter(text(sqlWhere))
|
|
30
|
+
else:
|
|
31
|
+
return query.filter(or_(*expressions))
|
|
32
|
+
|
|
33
|
+
class SAFRSBaseX(SAFRSBase, safrs.DB.Model):
|
|
34
|
+
__abstract__ = True
|
|
35
|
+
if do_enable_ont_advanced_filters := False:
|
|
36
|
+
jsonapi_filter = jsonapi_filter
|
|
37
|
+
|
|
38
|
+
def _s_parse_attr_value(self, attr_name: str, attr_val: any):
|
|
39
|
+
"""
|
|
40
|
+
Parse the given jsonapi attribute value so it can be stored in the db
|
|
41
|
+
:param attr_name: attribute name
|
|
42
|
+
:param attr_val: attribute value
|
|
43
|
+
:return: parsed value
|
|
44
|
+
"""
|
|
45
|
+
attr = self.__class__._s_jsonapi_attrs.get(attr_name, None)
|
|
46
|
+
if hasattr(attr, "type"): # pragma: no cover
|
|
47
|
+
|
|
48
|
+
if str(attr.type) in ["DATE", "DATETIME"] and attr_val:
|
|
49
|
+
try:
|
|
50
|
+
attr_val = attr_val.replace("T", " ")
|
|
51
|
+
datetime.strptime(attr_val, '%Y-%m-%d %H:%M')
|
|
52
|
+
attr_val += ":00"
|
|
53
|
+
except ValueError:
|
|
54
|
+
pass
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
safrs.log.warning(exc)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
return super()._s_parse_attr_value(attr_name, attr_val)
|
|
60
|
+
|
|
61
|
+
class TestBase(Base):
|
|
62
|
+
__abstract__ = True
|
|
63
|
+
def __init__(self, *args, **kwargs):
|
|
64
|
+
for name, val in kwargs.items():
|
|
65
|
+
col = getattr(self.__class__, name)
|
|
66
|
+
if 'amount_total' == name:
|
|
67
|
+
debug_stop = 'stop'
|
|
68
|
+
if val is not None:
|
|
69
|
+
if str(col.type) in ["DATE", "DATETIME"]:
|
|
70
|
+
pass
|
|
71
|
+
else:
|
|
72
|
+
kwargs[name] = col.type.python_type(val)
|
|
73
|
+
return super().__init__(*args, **kwargs)
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Model Context Protocol is a way for:
|
|
2
|
+
|
|
3
|
+
1. **Bus User ad hoc flows** using existing published mcp services (vs. hard-coding in IT as an endpoint; flows can be cached for repeated use)
|
|
4
|
+
|
|
5
|
+
* ***Natural Language access*** to corporate databases for improved user interfaces
|
|
6
|
+
|
|
7
|
+
* LLMs ***choreograph*** multiple MCP calls (to 1 or more MCP servers) in a chain of calls - an agentic workflow. MCPs support shared contexts and goals, enabling the LLM to use the result from 1 call to determine whether the goals has been reached, or which service is appropriate to call next
|
|
8
|
+
|
|
9
|
+
3. Chat agents to ***discover*** and ***call*** external servers, be they databases, APIs, file systems, etc. MCPs support shared contexts and goals, enabling the LLM
|
|
10
|
+
|
|
11
|
+
* ***Corporate database participation*** in such flows, by making key functions available as MCP calls.
|
|
12
|
+
|
|
13
|
+
This example is [explained here](https://apilogicserver.github.io/Docs/Integration-MCP/).
|
|
14
|
+
|
|
15
|
+
> Note: this sample uses multi-term filters. These are usually OR'd together, but this example requires AND. This is provided by `database/system/SAFRSBaseX.py` (see `return query.filter(operator.and_(*expressions)`) in `_s_filter()`), activated in `config/server_setup.py`.
|
|
@@ -13,10 +13,12 @@ Notes:
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
|
-
import os
|
|
17
|
-
import
|
|
16
|
+
import os, sys
|
|
17
|
+
from typing import Dict, List
|
|
18
18
|
import openai
|
|
19
19
|
import requests
|
|
20
|
+
from logic_bank.logic_bank import Rule
|
|
21
|
+
from logic_bank.util import ConstraintException
|
|
20
22
|
|
|
21
23
|
# Set your OpenAI API key
|
|
22
24
|
openai.api_key = os.getenv("APILOGICSERVER_CHATGPT_APIKEY")
|
|
@@ -37,24 +39,13 @@ def discover_mcp_servers():
|
|
|
37
39
|
global server_url, use_test_schema
|
|
38
40
|
|
|
39
41
|
# create schema_text (for prompt), by reading integration/mcp/mcp_schema.txt
|
|
40
|
-
if use_test_schema:
|
|
41
|
-
schema_file_path = os.path.join(os.path.dirname(__file__), "mcp_schema.txt")
|
|
42
|
-
try:
|
|
43
|
-
with open(schema_file_path, "r") as schema_file:
|
|
44
|
-
schema_text = schema_file.read()
|
|
45
|
-
except FileNotFoundError:
|
|
46
|
-
print(f"Schema file not found at {schema_file_path}.")
|
|
47
|
-
exit(1)
|
|
48
|
-
finally:
|
|
49
|
-
print(f"Schema file loaded from {schema_file_path}.")
|
|
50
|
-
return schema_text
|
|
51
42
|
|
|
52
43
|
# find the servers - read the mcp_server_discovery.json file
|
|
53
|
-
discovery_file_path = os.path.join(os.path.dirname(__file__), "mcp_server_discovery.json")
|
|
44
|
+
discovery_file_path = os.path.join(os.path.dirname(__file__), "../../integration/mcp/mcp_server_discovery.json")
|
|
54
45
|
try:
|
|
55
46
|
with open(discovery_file_path, "r") as discovery_file:
|
|
56
47
|
discovery_data = json.load(discovery_file)
|
|
57
|
-
print(f"\
|
|
48
|
+
print(f"\n1. Discovered MCP servers from config file: {discovery_file_path}:" + json.dumps(discovery_data, indent=4))
|
|
58
49
|
except FileNotFoundError:
|
|
59
50
|
print(f"Discovery file not found at {discovery_file_path}.")
|
|
60
51
|
except json.JSONDecodeError as e:
|
|
@@ -69,7 +60,8 @@ def discover_mcp_servers():
|
|
|
69
60
|
if response.status_code == 200:
|
|
70
61
|
api_schema = response.json()
|
|
71
62
|
print()
|
|
72
|
-
|
|
63
|
+
request_print = json.dumps(api_schema, indent=4)[0:400] + '\n... etc' # limit for readability
|
|
64
|
+
print(f"\n\nAPI Schema from discovery schema_url: {discovery_url}:\n" + request_print)
|
|
73
65
|
else:
|
|
74
66
|
print(f"Failed to retrieve API schema from {discovery_url}: {response.status_code}")
|
|
75
67
|
except requests.RequestException as e:
|
|
@@ -77,24 +69,33 @@ def discover_mcp_servers():
|
|
|
77
69
|
return json.dumps(api_schema)
|
|
78
70
|
|
|
79
71
|
|
|
80
|
-
def
|
|
72
|
+
def get_user_nl_query_and_training(query: str):
|
|
81
73
|
""" Get the natural language query from the user.
|
|
82
|
-
Add
|
|
74
|
+
Add training for the LLM to generate a tool context block.
|
|
83
75
|
|
|
84
76
|
"""
|
|
85
77
|
|
|
86
78
|
global test_type
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
# read file docs/mcp_learning/mcp.prompt
|
|
80
|
+
prompt_file_path = os.path.join(os.path.dirname(__file__), "../../docs/mcp_learning/mcp.prompt")
|
|
81
|
+
if os.path.exists(prompt_file_path):
|
|
82
|
+
with open(prompt_file_path, "r") as prompt_file:
|
|
83
|
+
training_prompt = prompt_file.read()
|
|
84
|
+
# print(f"\nLoaded training prompt from {prompt_file_path}:\n{training_prompt}")
|
|
90
85
|
else:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
request = "List customers with credit over 1000"
|
|
94
|
-
return request
|
|
86
|
+
training_prompt = ""
|
|
87
|
+
print(f"Prompt file not found at {prompt_file_path}.")
|
|
95
88
|
|
|
89
|
+
# if 1 argument, use it as the query
|
|
90
|
+
query_actual = query
|
|
91
|
+
if len(sys.argv) > 1:
|
|
92
|
+
query_actual = sys.argv[1]
|
|
93
|
+
if query_actual == '':
|
|
94
|
+
query_actual = "list customers with balance over 100."
|
|
95
|
+
return query_actual + ";\n\n" + training_prompt
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
|
|
98
|
+
def query_llm_with_nl(schema_text, nl_query):
|
|
98
99
|
"""
|
|
99
100
|
Query the LLM with a natural language query and schema text to generate a tool context block.
|
|
100
101
|
|
|
@@ -103,6 +104,7 @@ def query_llm_with_nl(nl_query):
|
|
|
103
104
|
|
|
104
105
|
global test_type, create_tool_context_from_llm
|
|
105
106
|
|
|
107
|
+
content = f"Natural language query:\n {nl_query}\nSchema:\n{schema_text}"
|
|
106
108
|
messages = [
|
|
107
109
|
{
|
|
108
110
|
"role": "system",
|
|
@@ -110,70 +112,18 @@ def query_llm_with_nl(nl_query):
|
|
|
110
112
|
},
|
|
111
113
|
{
|
|
112
114
|
"role": "user",
|
|
113
|
-
"content": f"
|
|
115
|
+
"content": f"{content}"
|
|
114
116
|
}
|
|
115
117
|
]
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"resources": [
|
|
126
|
-
{
|
|
127
|
-
"path": "/Order",
|
|
128
|
-
"method": "GET",
|
|
129
|
-
"query_params": [
|
|
130
|
-
{
|
|
131
|
-
"name": "date_shipped",
|
|
132
|
-
"op": "eq",
|
|
133
|
-
"val": None
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
"name": "date_created",
|
|
137
|
-
"op": "lt",
|
|
138
|
-
"val": "2023-07-14"
|
|
139
|
-
}
|
|
140
|
-
],
|
|
141
|
-
"headers": [],
|
|
142
|
-
"expected_output": []
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
"path": "/Email",
|
|
146
|
-
"method": "POST",
|
|
147
|
-
"body": {
|
|
148
|
-
"customer_id": "{{ order.customer_id }}",
|
|
149
|
-
"message": "You have a discount on your unshipped order."
|
|
150
|
-
},
|
|
151
|
-
"headers": [],
|
|
152
|
-
"expected_output": []
|
|
153
|
-
}
|
|
154
|
-
]
|
|
155
|
-
}
|
|
156
|
-
else: # simple get request - list customers with credit over 1000
|
|
157
|
-
tool_context = \
|
|
158
|
-
{
|
|
159
|
-
"tool_type": "json-api",
|
|
160
|
-
"schema_version": "1.0",
|
|
161
|
-
"base_url": "http://localhost:5656/api",
|
|
162
|
-
"resources": [
|
|
163
|
-
{
|
|
164
|
-
"path": "/Customer",
|
|
165
|
-
"method": "GET",
|
|
166
|
-
"query_params": [
|
|
167
|
-
{
|
|
168
|
-
"name": "credit_limit",
|
|
169
|
-
"op": "gt",
|
|
170
|
-
"val": "1000"
|
|
171
|
-
}
|
|
172
|
-
]
|
|
173
|
-
}
|
|
174
|
-
]
|
|
175
|
-
} # Call the OpenAI API to generate the tool context
|
|
176
|
-
if create_tool_context_from_llm: # saves 2-3 seconds...
|
|
119
|
+
request_print = content[0:1200] + '\n... etc' # limit for readability
|
|
120
|
+
print("\n\n2a. LLM request:\n", request_print)
|
|
121
|
+
# print("\n2b. NL Query:\n", nl_query)
|
|
122
|
+
# print("\n2c. schema_text: (truncated) \n")
|
|
123
|
+
# schema_print = json.dumps(json.loads(schema_text), indent=4)[:400] # limit for readability
|
|
124
|
+
# print(schema_print)
|
|
125
|
+
|
|
126
|
+
if create_tool_context_from_llm: # takes 2-3 seconds...
|
|
177
127
|
response = openai.chat.completions.create(
|
|
178
128
|
model="gpt-4",
|
|
179
129
|
messages=messages,
|
|
@@ -188,17 +138,17 @@ def query_llm_with_nl(nl_query):
|
|
|
188
138
|
print("Failed to decode JSON from response:", tool_context_str)
|
|
189
139
|
return None
|
|
190
140
|
|
|
191
|
-
print("\n\n2a. LLM request:\n", json.dumps(messages, indent=4))
|
|
192
|
-
print("\n2b. NL Query:\n", nl_query)
|
|
193
|
-
print("\n2c. schema_text: \n", json.dumps(json.loads(schema_text), indent=4)) #schema_text[0:100])
|
|
194
141
|
print("\n2d. generated tool context from LLM:\n", json.dumps(tool_context, indent=4))
|
|
142
|
+
|
|
143
|
+
if "resources" not in tool_context:
|
|
144
|
+
raise ConstraintException("GenAI Error - LLM response does not contain 'resources'.")
|
|
195
145
|
return tool_context
|
|
196
146
|
|
|
197
147
|
|
|
198
148
|
def process_tool_context(tool_context):
|
|
199
149
|
""" Process the orchestration request by executing multiple tool context blocks.
|
|
200
|
-
This
|
|
201
|
-
|
|
150
|
+
This executes the tool context blocks against a live JSON:API server.
|
|
151
|
+
It handles both GET and POST requests, and it can
|
|
202
152
|
orchestrate multiple requests based on the provided tool context.
|
|
203
153
|
|
|
204
154
|
Note the orchestration is processed by the client executor (here), not the server executor.
|
|
@@ -247,36 +197,72 @@ def process_tool_context(tool_context):
|
|
|
247
197
|
query_param_filter = 'filter=' + str(query_params)
|
|
248
198
|
# use urlencode to convert to JSON:API format...
|
|
249
199
|
# val urllib.parse.quote() or urllib.parse.urlencode()
|
|
250
|
-
# tool instructions... filtering, email etc
|
|
200
|
+
# tool instructions... filtering, email etc "null"
|
|
251
201
|
query_param_filter = query_param_filter.replace("'", '"') # convert single quotes to double quotes
|
|
252
202
|
query_param_filter = query_param_filter.replace("None", 'null')
|
|
253
|
-
query_param_filter = query_param_filter.replace("
|
|
203
|
+
query_param_filter = query_param_filter.replace('"null"', 'null')
|
|
204
|
+
# query_param_filter = query_param_filter.replace("date_created", 'CreatedOn') # TODO - why this name?
|
|
254
205
|
return query_param_filter # end get_query_param_filter
|
|
255
206
|
|
|
207
|
+
def move_fields(src: dict, dest: dict, context_data: dict):
|
|
208
|
+
""" Move fields from src to dest, replacing any variables with their values from context_data."""
|
|
209
|
+
for variable_name, value in src.items():
|
|
210
|
+
move_value = value
|
|
211
|
+
if move_value.startswith("{") and move_value.endswith("}"):
|
|
212
|
+
# strip the braces, and get the name after the first dot, # eg: "{Order.customer_id}" ==> "customer_id"``
|
|
213
|
+
move_name = move_value[1:-1] # strip the braces
|
|
214
|
+
if '.' in move_value:
|
|
215
|
+
move_name = move_name.split('.', 1)[1]
|
|
216
|
+
move_value = context_data['attributes'][move_name]
|
|
217
|
+
dest[variable_name] = move_value
|
|
218
|
+
return dest
|
|
219
|
+
|
|
220
|
+
def print_get_response(query_param_filter, mcp_response):
|
|
221
|
+
""" Print the response from the GET request. """
|
|
222
|
+
print("\n3. MCP Server (als) GET filter(query_param_filter):\n", query_param_filter)
|
|
223
|
+
print(" GET Response:\n", mcp_response.text)
|
|
224
|
+
results : List[Dict] = mcp_response.json()['data']
|
|
225
|
+
# print results in a table format
|
|
226
|
+
if results:
|
|
227
|
+
# Get all unique keys from all result dicts
|
|
228
|
+
keys = set()
|
|
229
|
+
for row in results:
|
|
230
|
+
if isinstance(row, dict):
|
|
231
|
+
keys.update(row.keys())
|
|
232
|
+
keys = list(keys)
|
|
233
|
+
# Print header
|
|
234
|
+
print("\n| " + " | ".join(keys) + " |")
|
|
235
|
+
print("|" + "|".join(["---"] * len(keys)) + "|")
|
|
236
|
+
# Print rows
|
|
237
|
+
for row in results:
|
|
238
|
+
print("| " + " | ".join(str(row.get(k, "")) for k in keys) + " |")
|
|
239
|
+
else:
|
|
240
|
+
print("No results found.")
|
|
241
|
+
|
|
256
242
|
assert isinstance(tool_context, (dict, list)), "Tool context expected to be a dictionary"
|
|
257
243
|
context_data = {}
|
|
258
244
|
added_rows = 0
|
|
259
245
|
|
|
260
246
|
for each_block in tool_context["resources"]:
|
|
261
|
-
if
|
|
247
|
+
if process_tool_context := True:
|
|
262
248
|
if each_block["method"] == "GET":
|
|
263
249
|
query_param_filter = get_query_param_filter(each_block["query_params"])
|
|
264
250
|
headers = {"Content-Type": "application/vnd.api+json"}
|
|
265
251
|
if "headers" in each_block:
|
|
266
252
|
headers.update(each_block["headers"])
|
|
267
253
|
mcp_response = requests.get(
|
|
268
|
-
url =
|
|
254
|
+
url = each_block["base_url"] + each_block["path"],
|
|
269
255
|
headers=headers,
|
|
270
256
|
params=query_param_filter
|
|
271
257
|
)
|
|
272
258
|
context_data = mcp_response.json()['data'] # result rows...
|
|
259
|
+
print_get_response(query_param_filter, mcp_response)
|
|
273
260
|
elif each_block["method"] in ["POST"]:
|
|
274
261
|
for each_order in context_data:
|
|
275
|
-
url =
|
|
262
|
+
url = each_block["base_url"] + each_block["path"]
|
|
276
263
|
json_update_data = { 'data': {"type": "Email", 'attributes': {} } }
|
|
277
264
|
json_update_data_attributes = json_update_data["data"]["attributes"]
|
|
278
|
-
|
|
279
|
-
json_update_data_attributes["message"] = each_block["body"]["message"]
|
|
265
|
+
move_fields( src= each_block["body"], dest=json_update_data_attributes, context_data=each_order)
|
|
280
266
|
# eg: POST http://localhost:5656/api/Email {'data': {'type': 'Email', 'attributes': {'customer_id': 5, 'message': {'to': '{{ order.customer_id }}', 'subject': 'Discount for your order', 'body': 'Dear customer, you have a discount for your recent order. Thank you for shopping with us.'}}}}
|
|
281
267
|
headers = {"Content-Type": "application/vnd.api+json"}
|
|
282
268
|
if "headers" in each_block:
|
|
@@ -288,20 +274,22 @@ def process_tool_context(tool_context):
|
|
|
288
274
|
)
|
|
289
275
|
added_rows += 1
|
|
290
276
|
pass
|
|
291
|
-
print("\n3. MCP Server (als) Response:\n", mcp_response.text)
|
|
277
|
+
print("\n3. MCP Server (als) POST Response:\n", mcp_response.text)
|
|
292
278
|
if added_rows > 0:
|
|
293
279
|
print(f"...Added {added_rows} rows to the database; last row (only) shown above.")
|
|
294
280
|
return mcp_response
|
|
295
281
|
|
|
296
282
|
|
|
297
283
|
if __name__ == "__main__":
|
|
298
|
-
|
|
284
|
+
|
|
285
|
+
# to run: Run Config > Run designated Python file
|
|
299
286
|
|
|
300
287
|
schema_text = discover_mcp_servers() # see: 1-discovery-from-als
|
|
301
288
|
|
|
302
|
-
query =
|
|
289
|
+
query = "list customers with balance over 100"
|
|
290
|
+
prompt = get_user_nl_query_and_training(query) # set breakpoint here, view log, then step
|
|
303
291
|
|
|
304
|
-
tool_context = query_llm_with_nl(
|
|
292
|
+
tool_context = query_llm_with_nl(schema_text, prompt) # see: 2-tool-context-from-LLM
|
|
305
293
|
|
|
306
294
|
mcp_response = process_tool_context(tool_context) # see: 3-MCP-server response
|
|
307
295
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Customer:
|
|
2
|
+
- id (INTEGER)
|
|
3
|
+
- name (VARCHAR)
|
|
4
|
+
- balance (DECIMAL)
|
|
5
|
+
- credit_limit (DECIMAL)
|
|
6
|
+
* Filterable: name, balance, credit_limit
|
|
7
|
+
* Example: 'List customers with credit over 5000'
|
|
8
|
+
|
|
9
|
+
Item:
|
|
10
|
+
- id (INTEGER)
|
|
11
|
+
- order_id (INTEGER)
|
|
12
|
+
- product_id (INTEGER)
|
|
13
|
+
- quantity (INTEGER)
|
|
14
|
+
- amount (DECIMAL)
|
|
15
|
+
- unit_price (DECIMAL)
|
|
16
|
+
* Filterable: order_id, product_id, unit_price
|
|
17
|
+
* Relationships:
|
|
18
|
+
↪ relates to Order
|
|
19
|
+
↪ relates to Product
|
|
20
|
+
* Example: 'Find all items for order 42'
|
|
21
|
+
|
|
22
|
+
Email:
|
|
23
|
+
- id (INTEGER)
|
|
24
|
+
- message (VARCHAR)
|
|
25
|
+
- customer_id (INTEGER)
|
|
26
|
+
* Relationships:
|
|
27
|
+
↪ relates to Customer
|
|
28
|
+
* Example: 'Post an message "text" for customer 2 means POST to this resource'
|
|
29
|
+
|
|
30
|
+
Order:
|
|
31
|
+
- id (INTEGER)
|
|
32
|
+
- notes (VARCHAR)
|
|
33
|
+
- customer_id (INTEGER)
|
|
34
|
+
- CreatedOn (DATE)
|
|
35
|
+
- date_shipped (DATE)
|
|
36
|
+
- amount_total (DECIMAL)
|
|
37
|
+
* Filterable: notes, customer_id, date_shipped, amount_total
|
|
38
|
+
* Relationships:
|
|
39
|
+
↪ relates to Customer
|
|
40
|
+
* Example: 'Get orders shipped in 2024'
|
|
41
|
+
|
|
42
|
+
Product:
|
|
43
|
+
- id (INTEGER)
|
|
44
|
+
- name (VARCHAR)
|
|
45
|
+
- unit_price (DECIMAL)
|
|
46
|
+
* Filterable: name, unit_price
|
|
47
|
+
* Example: 'Show products with price over 100'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
LLM request - new...?
|
|
2
|
+
|
|
3
|
+
[
|
|
4
|
+
{
|
|
5
|
+
"role": "system",
|
|
6
|
+
"content": "You are an API planner that converts natural language queries into MCP Tool Context blocks using JSON:API. Return only the tool context as JSON."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"role": "user",
|
|
10
|
+
"content": "Schema:\n{\"base_url\": \"http://localhost:5656/api\", \"description\": \"API Logic Project: basic_demo\", \"email_services\": \"iff email is requested, Send email by issing a POST request to the Email endpoint, setting the subject, message and customer_id in the body.\", \"expected_response\": \"Respond with a JSON object with schema_version and a resource array including: tool_type, base_url, path, method, query_params array or body, headers.\", \"query_params\": \"- JSON:API custom filtering using a filter array (e.g., filter=[{\\\"name\\\":\\\"date_shipped\\\",\\\"op\\\":\\\"gt\\\",\\\"val\\\":\\\"2023-07-14\\\"}])\", \"resources\": [{\"fields\": [\"id\", \"name\", \"balance\", \"credit_limit\"], \"filterable\": [\"id\", \"name\", \"balance\", \"credit_limit\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Customer\", \"path\": \"/Customer\"}, {\"fields\": [\"id\", \"order_id\", \"product_id\", \"quantity\", \"amount\", \"unit_price\"], \"filterable\": [\"id\", \"order_id\", \"product_id\", \"quantity\", \"amount\", \"unit_price\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Item\", \"path\": \"/Item\"}, {\"fields\": [\"id\", \"notes\", \"customer_id\", \"date_shipped\", \"amount_total\"], \"filterable\": [\"id\", \"notes\", \"customer_id\", \"date_shipped\", \"amount_total\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Order\", \"path\": \"/Order\"}, {\"fields\": [\"id\", \"name\", \"unit_price\"], \"filterable\": [\"id\", \"name\", \"unit_price\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Product\", \"path\": \"/Product\"}, {\"fields\": [\"id\", \"request\", \"request_prompt\", \"completion\"], \"filterable\": [\"id\", \"request\", \"request_prompt\", \"completion\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Mcp\", \"path\": \"/Mcp\"}], \"schema_version\": \"1.0\", \"tool_type\": \"json-api\"}\n\nNatural language query: 'List the unshipped orders created before 2023-07-14, and send a discount email (subject: 'Discount Offer') to the customer for each one.'"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
LLM request - old
|
|
15
|
+
[
|
|
16
|
+
{
|
|
17
|
+
"role": "system",
|
|
18
|
+
"content": "You are an API planner that converts natural language queries into MCP Tool Context blocks using JSON:API. Return only the tool context as JSON."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"role": "user",
|
|
22
|
+
"content": "Schema:\n{\"base_url\": \"http://localhost:5656/api\", \"description\": \"API Logic Project: basic_demo\", \"email_services\": \"iff email is requested, Send email by issing a POST request to the Email endpoint, setting the subject, message and customer_id in the body.\", \"expected_response\": \"Respond with a JSON object with schema_version and a resource array including: tool_type, base_url, path, method, query_params array or body, headers.\", \"query_params\": \"- JSON:API custom filtering using a filter array (e.g., filter=[{\\\"name\\\":\\\"date_shipped\\\",\\\"op\\\":\\\"gt\\\",\\\"val\\\":\\\"2023-07-14\\\"}])\", \"resources\": [{\"fields\": [\"id\", \"name\", \"balance\", \"credit_limit\"], \"filterable\": [\"id\", \"name\", \"balance\", \"credit_limit\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Customer\", \"path\": \"/Customer\"}, {\"fields\": [\"id\", \"order_id\", \"product_id\", \"quantity\", \"amount\", \"unit_price\"], \"filterable\": [\"id\", \"order_id\", \"product_id\", \"quantity\", \"amount\", \"unit_price\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Item\", \"path\": \"/Item\"}, {\"fields\": [\"id\", \"notes\", \"customer_id\", \"date_shipped\", \"amount_total\"], \"filterable\": [\"id\", \"notes\", \"customer_id\", \"date_shipped\", \"amount_total\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Order\", \"path\": \"/Order\"}, {\"fields\": [\"id\", \"name\", \"unit_price\"], \"filterable\": [\"id\", \"name\", \"unit_price\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Product\", \"path\": \"/Product\"}, {\"fields\": [\"id\", \"request\", \"request_prompt\", \"completion\"], \"filterable\": [\"id\", \"request\", \"request_prompt\", \"completion\"], \"methods\": [\"GET\", \"PATCH\", \"POST\", \"DELETE\"], \"name\": \"Mcp\", \"path\": \"/Mcp\"}], \"schema_version\": \"1.0\", \"tool_type\": \"json-api\"}\n\nNatural language query: 'List the unshipped orders created before 2023-07-14, and send a discount email (subject: 'Discount Offer') to the customer for each one.'"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
messages - new
|
|
28
|
+
[{'role': 'system', 'content': 'You are an API planner that converts natural language queries into MCP Tool Context b...API. Return only the tool context as JSON.'}, {'role': 'user', 'content': 'Schema:\n{"base_url": "http://localhost:5656/api", "description": "API Logic Project: ...) to the customer for each one.'"}]
|
|
29
|
+
|
|
30
|
+
messages- old
|
|
31
|
+
[{'role': 'system', 'content': 'You are an API planner that converts natural language queries into MCP Tool Context blocks using JSON:API. Return only the tool context as JSON.'}, {'role': 'user', 'content': 'Schema:\n{"base_url": "http://localhost:5656/api", "description": "API Logic Project: basic_demo", "email_services": "iff email is requested, Send email by issing a POST request to the Email endpoint, setting the subject, message and customer_id in the body.", "expected_response": "Respond with a JSON object with schema_version and a resource array including: tool_type, base_url, path, method, query_params array or body, headers.", "query_params": "- JSON:API custom filtering using a filter array (e.g., filter=[{\\"name\\":\\"date_shipped\\",\\"op\\":\\"gt\\",\\"val\\":\\"2023-07-14\\"}])", "resources": [{"fields": ["id", "name", "balance", "credit_limit"], "filterable": ["id", "name", "balance", "credit_limit"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Customer", "path": "/Customer"}, {"fields": ["id", "order_id", "product_id", "quantity", "amount", "unit_price"], "filterable": ["id", "order_id", "product_id", "quantity", "amount", "unit_price"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Item", "path": "/Item"}, {"fields": ["id", "notes", "customer_id", "date_shipped", "amount_total"], "filterable": ["id", "notes", "customer_id", "date_shipped", "amount_total"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Order", "path": "/Order"}, {"fields": ["id", "name", "unit_price"], "filterable": ["id", "name", "unit_price"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Product", "path": "/Product"}, {"fields": ["id", "request", "request_prompt", "completion"], "filterable": ["id", "request", "request_prompt", "completion"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Mcp", "path": "/Mcp"}], "schema_version": "1.0", "tool_type": "json-api"}\n\nNatural language query: \'List the unshipped orders created before 2023-07-14, and send a discount email (subject: \'Discount Offer\') to the customer for each one.\''}]
|
|
32
|
+
|
|
33
|
+
old content
|
|
34
|
+
'Schema:\n{"base_url": "http://localhost:5656/api", "description": "API Logic Project: basic_demo", "email_services": "iff email is requested, Send email by issing a POST request to the Email endpoint, setting the subject, message and customer_id in the body.", "expected_response": "Respond with a JSON object with schema_version and a resource array including: tool_type, base_url, path, method, query_params array or body, headers.", "query_params": "- JSON:API custom filtering using a filter array (e.g., filter=[{\\"name\\":\\"date_shipped\\",\\"op\\":\\"gt\\",\\"val\\":\\"2023-07-14\\"}])", "resources": [{"fields": ["id", "name", "balance", "credit_limit"], "filterable": ["id", "name", "balance", "credit_limit"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Customer", "path": "/Customer"}, {"fields": ["id", "order_id", "product_id", "quantity", "amount", "unit_price"], "filterable": ["id", "order_id", "product_id", "quantity", "amount", "unit_price"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Item", "path": "/Item"}, {"fields": ["id", "notes", "customer_id", "date_shipped", "amount_total"], "filterable": ["id", "notes", "customer_id", "date_shipped", "amount_total"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Order", "path": "/Order"}, {"fields": ["id", "name", "unit_price"], "filterable": ["id", "name", "unit_price"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Product", "path": "/Product"}, {"fields": ["id", "request", "request_prompt", "completion"], "filterable": ["id", "request", "request_prompt", "completion"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Mcp", "path": "/Mcp"}], "schema_version": "1.0", "tool_type": "json-api"}\n\nNatural language query: \'List the unshipped orders created before 2023-07-14, and send a discount email (subject: \'Discount Offer\') to the customer for each one.\''
|
|
35
|
+
|
|
36
|
+
where new content looks correct:
|
|
37
|
+
'Schema:\n{"base_url": "http://localhost:5656/api", "description": "API Logic Project: basic_demo", "email_services": "iff email is requested, Send email by issing a POST request to the Email endpoint, setting the subject, message and customer_id in the body.", "expected_response": "Respond with a JSON object with schema_version and a resource array including: tool_type, base_url, path, method, query_params array or body, headers.", "query_params": "- JSON:API custom filtering using a filter array (e.g., filter=[{\\"name\\":\\"date_shipped\\",\\"op\\":\\"gt\\",\\"val\\":\\"2023-07-14\\"}])", "resources": [{"fields": ["id", "name", "balance", "credit_limit"], "filterable": ["id", "name", "balance", "credit_limit"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Customer", "path": "/Customer"}, {"fields": ["id", "order_id", "product_id", "quantity", "amount", "unit_price"], "filterable": ["id", "order_id", "product_id", "quantity", "amount", "unit_price"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Item", "path": "/Item"}, {"fields": ["id", "notes", "customer_id", "date_shipped", "amount_total"], "filterable": ["id", "notes", "customer_id", "date_shipped", "amount_total"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Order", "path": "/Order"}, {"fields": ["id", "name", "unit_price"], "filterable": ["id", "name", "unit_price"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Product", "path": "/Product"}, {"fields": ["id", "request", "request_prompt", "completion"], "filterable": ["id", "request", "request_prompt", "completion"], "methods": ["GET", "PATCH", "POST", "DELETE"], "name": "Mcp", "path": "/Mcp"}], "schema_version": "1.0", "tool_type": "json-api"}\n\nNatural language query: \'List the unshipped orders created before 2023-07-14, and send a discount email (subject: \'Discount Offer\') to the customer for each one.\''
|