ApiLogicServer 14.4.0__py3-none-any.whl → 14.5.3__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/add_cust/add_cust.py +269 -0
- api_logic_server_cli/api_logic_server.py +18 -238
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/cli.py +38 -28
- 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/__pycache__/ont_build.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/api_logic_server_utils.py +47 -0
- api_logic_server_cli/create_from_model/dbml.py +113 -58
- api_logic_server_cli/create_from_model/ont_build.py +83 -60
- api_logic_server_cli/create_from_model/ont_create.py +2 -1
- api_logic_server_cli/database/basic_demo.sqlite +0 -0
- api_logic_server_cli/database/basic_demo.txt +1 -0
- api_logic_server_cli/database/basic_demo_wg.sqlite +0 -0
- api_logic_server_cli/manager.py +3 -2
- api_logic_server_cli/prototypes/base/.vscode/launch.json +3 -2
- api_logic_server_cli/prototypes/base/config/config.py +66 -11
- api_logic_server_cli/prototypes/base/config/default.env +7 -1
- api_logic_server_cli/prototypes/base/database/test_data/readme.md +2 -1
- api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +5 -2
- api_logic_server_cli/prototypes/base/integration/n8n/n8n_producer.py +68 -21
- api_logic_server_cli/prototypes/base/integration/n8n/n8n_readme.md +19 -0
- api_logic_server_cli/prototypes/base/test/basic/server_test.py +1 -1
- api_logic_server_cli/prototypes/basic_demo/README.md +29 -52
- api_logic_server_cli/prototypes/basic_demo/customizations/api/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/mcp_discovery.py +139 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/openapi.py +92 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/config/default.env +13 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/config/server_setup.py +388 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/models.py +131 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +136 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/README_mcp.md +15 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py +350 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_schema.txt +47 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_server_discovery.json +9 -0
- api_logic_server_cli/prototypes/{nw_no_cust/integration/mcp → basic_demo/customizations/integration/openai_function}/3_executor_test_agent.py +20 -6
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/README_functon.md +201 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json +17 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/nw-swagger_3.json +1731 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/snippets.txt +5 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3 genai_demo_with_get.json +1731 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3.json +1782 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo.json +264 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo_with_update.json +1782 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +79 -41
- api_logic_server_cli/prototypes/basic_demo/customizations/security/declare_security.py +11 -12
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/admin.yaml +166 -0
- api_logic_server_cli/prototypes/basic_demo/iteration/api/{customize_api.py → api_discovery/order_b2b.py} +17 -23
- api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderB2B.py +6 -5
- api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderShipping.py +4 -4
- api_logic_server_cli/prototypes/basic_demo/iteration/logic/declare_logic.py +69 -43
- api_logic_server_cli/prototypes/basic_demo/iteration/ui/admin/admin.yaml +125 -50
- api_logic_server_cli/prototypes/manager/README.md +4 -0
- api_logic_server_cli/prototypes/nw/logic/declare_logic.py +2 -2
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/app.json +1 -0
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/appearance.json +1 -0
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/core-plugins.json +31 -0
- api_logic_server_cli/prototypes/nw_no_cust/.obsidian/workspace.json +166 -0
- api_logic_server_cli/prototypes/nw_no_cust/Tutorial.md +45 -26
- api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/openapi.py +130 -0
- api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/proper_update_def.json +71 -0
- api_logic_server_cli/prototypes/nw_no_cust/config/default.env +13 -0
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/package-lock.json +9725 -1180
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/package.json +3 -6
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/app/shared/app.services.config.ts +1 -1
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/css/app.scss +4 -0
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/en.json +1 -1
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/es.json +14 -12
- api_logic_server_cli/prototypes/ont_app/templates/app_config.jinja +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/date_template.html +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/textarea_template.html +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/timestamp_template.html +1 -1
- api_logic_server_cli/prototypes/sample_ai/logic/declare_logic.py +30 -13
- apilogicserver-14.5.3.dist-info/METADATA +168 -0
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/RECORD +84 -61
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/WHEEL +1 -1
- api_logic_server_cli/prototypes/basic_demo/apply_customizations.ps1 +0 -17
- api_logic_server_cli/prototypes/basic_demo/apply_customizations.sh +0 -14
- api_logic_server_cli/prototypes/basic_demo/apply_iteration.ps1 +0 -20
- api_logic_server_cli/prototypes/basic_demo/apply_iteration.sh +0 -15
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/1_langchain_loader.py +0 -19
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/2_gpt_mcp_prompt.txt +0 -19
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/README.md +0 -17
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/curl.txt +0 -4
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/resources/nw_swagger_3.yaml +0 -16660
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/run_executor.py +0 -23
- apilogicserver-14.4.0.dist-info/METADATA +0 -76
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/entry_points.txt +0 -0
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.3.dist-info}/top_level.txt +0 -0
api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This simulates the MCP Client Executor,
|
|
3
|
+
which takes a natural language query and converts it into a tool context block:
|
|
4
|
+
|
|
5
|
+
1. Discovers MCP servers (from config)
|
|
6
|
+
2. Queries OpenAI's GPT-4 model to obtain the tool context based on a provided schema and a natural language query
|
|
7
|
+
3. Processes the tool context (calls the indicated MCP (als) endpoints)
|
|
8
|
+
|
|
9
|
+
Notes:
|
|
10
|
+
* See: integration/mcp/README_mcp.md
|
|
11
|
+
* python api_logic_server_run.py
|
|
12
|
+
|
|
13
|
+
ToDo - email example is incomplete:
|
|
14
|
+
1. Add email event handler (ala nw_sample/logic/declare_logic.py#send_n8n_message())
|
|
15
|
+
2. And, respect the customer email_opt_out
|
|
16
|
+
3. Needs to use date range
|
|
17
|
+
4. Data incomplete
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import openai
|
|
25
|
+
import requests
|
|
26
|
+
|
|
27
|
+
# Set your OpenAI API key
|
|
28
|
+
openai.api_key = os.getenv("APILOGICSERVER_CHATGPT_APIKEY")
|
|
29
|
+
|
|
30
|
+
server_url = os.getenv("APILOGICSERVER_URL", "http://localhost:5656/api")
|
|
31
|
+
|
|
32
|
+
# debug settings
|
|
33
|
+
test_type = 'orchestration' # 'simple_get' or 'orchestration'
|
|
34
|
+
create_tool_context_from_llm = True
|
|
35
|
+
''' set to False to bypass LLM call and save 2-3 secs in testing '''
|
|
36
|
+
use_test_schema = False
|
|
37
|
+
''' True means bypass discovery, use hard-coded schedma file '''
|
|
38
|
+
|
|
39
|
+
def discover_mcp_servers():
|
|
40
|
+
""" Discover the MCP servers by calling the /api/.well-known/mcp.json endpoint.
|
|
41
|
+
This function retrieves the list of available MCP servers and their capabilities.
|
|
42
|
+
"""
|
|
43
|
+
global server_url, use_test_schema
|
|
44
|
+
|
|
45
|
+
# create schema_text (for prompt), by reading integration/mcp/mcp_schema.txt
|
|
46
|
+
if use_test_schema:
|
|
47
|
+
schema_file_path = os.path.join(os.path.dirname(__file__), "mcp_schema.txt")
|
|
48
|
+
try:
|
|
49
|
+
with open(schema_file_path, "r") as schema_file:
|
|
50
|
+
schema_text = schema_file.read()
|
|
51
|
+
except FileNotFoundError:
|
|
52
|
+
print(f"Schema file not found at {schema_file_path}.")
|
|
53
|
+
exit(1)
|
|
54
|
+
finally:
|
|
55
|
+
print(f"Schema file loaded from {schema_file_path}.")
|
|
56
|
+
return schema_text
|
|
57
|
+
|
|
58
|
+
# find the servers - read the mcp_server_discovery.json file
|
|
59
|
+
discovery_file_path = os.path.join(os.path.dirname(__file__), "mcp_server_discovery.json")
|
|
60
|
+
try:
|
|
61
|
+
with open(discovery_file_path, "r") as discovery_file:
|
|
62
|
+
discovery_data = json.load(discovery_file)
|
|
63
|
+
print(f"\nDiscovered MCP servers from config file: {discovery_file_path}:" + json.dumps(discovery_data, indent=4))
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
print(f"Discovery file not found at {discovery_file_path}.")
|
|
66
|
+
except json.JSONDecodeError as e:
|
|
67
|
+
print(f"Error decoding JSON from {discovery_file_path}: {e}")
|
|
68
|
+
|
|
69
|
+
for each_server in discovery_data["servers"]:
|
|
70
|
+
discovery_url = each_server["schema_url"]
|
|
71
|
+
|
|
72
|
+
# Call the discovery_url to get the MCP/API schema
|
|
73
|
+
try:
|
|
74
|
+
response = requests.get(discovery_url)
|
|
75
|
+
if response.status_code == 200:
|
|
76
|
+
api_schema = response.json()
|
|
77
|
+
print()
|
|
78
|
+
print(f"\n1. API Schema from discovery schema_url: {discovery_url}:\n" +json.dumps(api_schema, indent=4))
|
|
79
|
+
else:
|
|
80
|
+
print(f"Failed to retrieve API schema from {discovery_url}: {response.status_code}")
|
|
81
|
+
except requests.RequestException as e:
|
|
82
|
+
print(f"Error calling OpenAPI URL: {e}")
|
|
83
|
+
return json.dumps(api_schema)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_user_nl_query():
|
|
87
|
+
""" Get the natural language query from the user.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
global test_type
|
|
92
|
+
|
|
93
|
+
default_request = "List the orders created more than 30 days ago, and send a discount email to the customer for each one."
|
|
94
|
+
# eg, curl -qg 'http://localhost:5656/api/Order?filter=[{"name":"date_shipped","op":"gt","val":"2023-07-14"}]'
|
|
95
|
+
# eg, curl -qg 'http://localhost:5656/api/Order?filter=[{"name":"date_shipped","op":"eq","val":null}]'
|
|
96
|
+
# eg, curl -qg 'http://localhost:5656/api/Order?filter=[{"name":"date_shipped","op":"eq","val":null},{"name":"CreatedOn","op":"lt","val":"2023-07-14"}]'
|
|
97
|
+
# eg, curl -qg 'http://localhost:5656/api/Customer?filter=[{"name":"credit_limit","op":"gt","val":"1000"}]'
|
|
98
|
+
|
|
99
|
+
# curl -qg 'http://localhost:5656/api/Order?filter=[{"name":%20"date_shipped",%20"op":%20"eq",%20"val":%20null},%20{"name":%20"CreatedOn",%20"op":%20"lt",%20"val":%20"2023-07-14"}]'
|
|
100
|
+
|
|
101
|
+
default_request = "List the orders for customer 5, and send a discount email to the customer for each one."
|
|
102
|
+
default_request = "List the unshipped orders created before 2023-07-14, and send a discount email to the customer for each one."
|
|
103
|
+
|
|
104
|
+
if test_type != 'orchestration':
|
|
105
|
+
default_request = "List customers with credit over 1000"
|
|
106
|
+
|
|
107
|
+
query = sys.argv[1] if len(sys.argv) > 1 else default_request
|
|
108
|
+
|
|
109
|
+
query += """
|
|
110
|
+
Respond with a JSON array of tool context blocks using:
|
|
111
|
+
- tool: 'json-api'
|
|
112
|
+
- JSON:API custom filtering (e.g., filter=[{"name":"date_shipped","op":"gt","val":"2023-07-14"}])
|
|
113
|
+
- Use {{ order.customer_id }} as a placeholder in the second step.
|
|
114
|
+
- Include method, url, query_params or body, headers, expected_output.
|
|
115
|
+
"""
|
|
116
|
+
return query
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def query_llm_with_nl(nl_query):
|
|
120
|
+
"""
|
|
121
|
+
Query the LLM with a natural language query and schema text to generate a tool context block.
|
|
122
|
+
|
|
123
|
+
It handles both orchestration and simple GET requests.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
global test_type, create_tool_context_from_llm
|
|
127
|
+
|
|
128
|
+
messages = [
|
|
129
|
+
{
|
|
130
|
+
"role": "system",
|
|
131
|
+
"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."
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"role": "user",
|
|
135
|
+
"content": f"Schema:\n{schema_text}\n\nNatural language query: '{nl_query}'"
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
# setup default tool_context to bypass LLM call and save 2-3 secs in testing
|
|
140
|
+
if test_type == 'orchestration': # orchestration: emails to pending orders
|
|
141
|
+
tool_context = \
|
|
142
|
+
[
|
|
143
|
+
{
|
|
144
|
+
"tool": "json-api",
|
|
145
|
+
"method": "GET",
|
|
146
|
+
"url": "http://localhost:5656/api/Order",
|
|
147
|
+
"query_params": {
|
|
148
|
+
"filter": [
|
|
149
|
+
{
|
|
150
|
+
"name": "date_shipped",
|
|
151
|
+
"op": "eq",
|
|
152
|
+
"val": None
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "date_created",
|
|
156
|
+
"op": "lt",
|
|
157
|
+
"val": "2023-07-14"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
"headers": {
|
|
162
|
+
"Content-Type": "application/json"
|
|
163
|
+
},
|
|
164
|
+
"expected_output": "JSON array of unshipped orders created before 2023-07-14"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"tool": "email",
|
|
168
|
+
"method": "POST",
|
|
169
|
+
"url": "http://localhost:5656/api/sendEmail",
|
|
170
|
+
"body": {
|
|
171
|
+
"customer_id": "{{ order.customer_id }}",
|
|
172
|
+
"subject": "Discount Offer",
|
|
173
|
+
"message": "You have a new discount offer for your unshipped order."
|
|
174
|
+
},
|
|
175
|
+
"headers": {
|
|
176
|
+
"Content-Type": "application/json"
|
|
177
|
+
},
|
|
178
|
+
"expected_output": "Confirmation of email sent"
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
else: # simple get request - list customers with credit over 4000
|
|
182
|
+
tool_context = \
|
|
183
|
+
[
|
|
184
|
+
{
|
|
185
|
+
"tool": "json-api",
|
|
186
|
+
"method": "GET",
|
|
187
|
+
"url": "http://localhost:5656/api/Customer",
|
|
188
|
+
"query_params": {
|
|
189
|
+
"filter[credit_limit][gt]": 1000
|
|
190
|
+
},
|
|
191
|
+
"headers": {
|
|
192
|
+
"Content-Type": "application/vnd.api+json"
|
|
193
|
+
},
|
|
194
|
+
"expected_output": "JSON array of customers with credit limit over 1000"
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
# Call the OpenAI API to generate the tool context
|
|
199
|
+
if create_tool_context_from_llm: # saves 2-3 seconds...
|
|
200
|
+
response = openai.chat.completions.create(
|
|
201
|
+
model="gpt-4",
|
|
202
|
+
messages=messages,
|
|
203
|
+
temperature=0.2
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
tool_context_str = response.choices[0].message.content
|
|
207
|
+
try:
|
|
208
|
+
tool_context = json.loads(tool_context_str)
|
|
209
|
+
except json.JSONDecodeError:
|
|
210
|
+
print("Failed to decode JSON from response:", tool_context_str)
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
print("\n2. generated tool context from LLM:\n", json.dumps(tool_context, indent=4))
|
|
214
|
+
return tool_context
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def process_tool_context(tool_context):
|
|
218
|
+
""" Process the orchestration request by executing multiple tool context blocks.
|
|
219
|
+
This function simulates the MCP Client Executor by executing the tool context blocks
|
|
220
|
+
against a live JSON:API server. It handles both GET and POST requests, and it can
|
|
221
|
+
orchestrate multiple requests based on the provided tool context.
|
|
222
|
+
|
|
223
|
+
Note the orchestration is processed by the client executor (here), not the server executor.
|
|
224
|
+
|
|
225
|
+
Research:
|
|
226
|
+
|
|
227
|
+
1. How is this a "USB", since the request was specific about JSON:API?
|
|
228
|
+
2. How is it clear to loop through the tool_context[0] and call tool_context[1]?
|
|
229
|
+
"""
|
|
230
|
+
global server_url
|
|
231
|
+
|
|
232
|
+
def get_query_param_filter(query_params):
|
|
233
|
+
""" return json:api filter
|
|
234
|
+
|
|
235
|
+
see api_logic_server_cli/prototypes/base/api/system/expression_parser.py (doc?)
|
|
236
|
+
|
|
237
|
+
eg
|
|
238
|
+
curl -qg 'http://localhost:5656/api/Order?filter=[{"name":"date_shipped","op":"eq","val":null},{"name":"CreatedOn","op":"gt","val":"2023-07-14"}]'
|
|
239
|
+
|
|
240
|
+
query_params might be simple:
|
|
241
|
+
"query_params": {
|
|
242
|
+
"filter[credit_limit][gt]": 1000 }
|
|
243
|
+
==> ?filter=[{"name":"credit_limit","op":"gt","val":"1000"}]
|
|
244
|
+
or a list:
|
|
245
|
+
"query_params": {
|
|
246
|
+
"filter": [
|
|
247
|
+
{
|
|
248
|
+
"name": "date_shipped",
|
|
249
|
+
"op": "eq",
|
|
250
|
+
"val": null
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"name": "date_created",
|
|
254
|
+
"op": "lt",
|
|
255
|
+
"val": "2023-07-14"
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
added_rows = 0
|
|
262
|
+
|
|
263
|
+
query_param_filter = ''
|
|
264
|
+
if isinstance(query_params, dict):
|
|
265
|
+
if "filter" not in query_params: # simple - "query_params": {"filter[credit_limit][gt]": 1000 }
|
|
266
|
+
for each_key, each_value in query_params.items():
|
|
267
|
+
assert not isinstance(each_value, dict), "Unexpected dict in simple query_params"
|
|
268
|
+
# convert {"filter[credit_limit][gt]": 1000 } to ?filter=[{"name":"credit_limit","op":"gt","val":"1000"}]
|
|
269
|
+
match = re.match(r"filter\[(\w+)\]\[(\w+)\]", each_key)
|
|
270
|
+
if match:
|
|
271
|
+
name, op = match.groups()
|
|
272
|
+
filter_json = json.dumps([{"name": name, "op": op, "val": str(each_value)}])
|
|
273
|
+
query_param_filter += f"&filter={filter_json}"
|
|
274
|
+
else:
|
|
275
|
+
query_param_filter += f"&{each_key}={each_value}"
|
|
276
|
+
|
|
277
|
+
else: # complex - "query_params": {"filter": ...
|
|
278
|
+
assert isinstance(query_params["filter"], list), "Query Params filter expected to be a list"
|
|
279
|
+
query_param_filter = 'filter=' + str(query_params["filter"])
|
|
280
|
+
# use urlencode to convert to JSON:API format...
|
|
281
|
+
# val urllib.parse.quote() or urllib.parse.urlencode()
|
|
282
|
+
# tool instructions... filtering, email etc
|
|
283
|
+
query_param_filter = query_param_filter.replace("'", '"') # convert single quotes to double quotes
|
|
284
|
+
query_param_filter = query_param_filter.replace("None", 'null')
|
|
285
|
+
query_param_filter = query_param_filter.replace("date_created", 'CreatedOn') # TODO - why this name?
|
|
286
|
+
# query_params = ''
|
|
287
|
+
else:
|
|
288
|
+
assert False, "Query Params not a dict"
|
|
289
|
+
return query_param_filter
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if isinstance(tool_context, dict):
|
|
293
|
+
query_params = tool_context["query_params"]
|
|
294
|
+
query_param_filter = get_query_param_filter(query_params)
|
|
295
|
+
mcp_response = requests.get(
|
|
296
|
+
tool_context["url"],
|
|
297
|
+
headers=tool_context["headers"],
|
|
298
|
+
params=query_param_filter
|
|
299
|
+
)
|
|
300
|
+
elif isinstance(tool_context, list):
|
|
301
|
+
context_data = {}
|
|
302
|
+
added_rows = 0
|
|
303
|
+
|
|
304
|
+
for each_block in tool_context:
|
|
305
|
+
if each_block["tool"] in ["json-api", "email"]:
|
|
306
|
+
if each_block["method"] == "GET":
|
|
307
|
+
query_param_filter = get_query_param_filter(each_block["query_params"])
|
|
308
|
+
mcp_response = requests.get(
|
|
309
|
+
url = each_block["url"],
|
|
310
|
+
headers=each_block["headers"],
|
|
311
|
+
params=query_param_filter
|
|
312
|
+
)
|
|
313
|
+
context_data = mcp_response.json()['data'] # result rows...
|
|
314
|
+
elif each_block["method"] in ["POST"]:
|
|
315
|
+
for each_order in context_data:
|
|
316
|
+
url = each_block["url"]
|
|
317
|
+
url = url.replace("sendEmail", "Email") # TODO name fix
|
|
318
|
+
json_update_data = { 'data': {"type": "Email", 'attributes': {} } }
|
|
319
|
+
json_update_data_attributes = json_update_data["data"]["attributes"]
|
|
320
|
+
json_update_data_attributes["customer_id"] = context_data[0]['attributes']["customer_id"] # TODO - fix
|
|
321
|
+
json_update_data_attributes["message"] = each_block["body"]["message"]
|
|
322
|
+
# 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.'}}}}
|
|
323
|
+
mcp_response = requests.post(
|
|
324
|
+
url=url,
|
|
325
|
+
headers=each_block["headers"],
|
|
326
|
+
json=json_update_data
|
|
327
|
+
)
|
|
328
|
+
added_rows += 1
|
|
329
|
+
pass
|
|
330
|
+
else:
|
|
331
|
+
print("Invalid tool context format. Expected a dictionary or a list.")
|
|
332
|
+
return None
|
|
333
|
+
print("\n3. MCP Server (als) Response:\n", mcp_response.text)
|
|
334
|
+
if added_rows > 0:
|
|
335
|
+
print(f"...Added {added_rows} rows to the database; last row (only) shown above.")
|
|
336
|
+
return mcp_response
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == "__main__":
|
|
340
|
+
import sys
|
|
341
|
+
|
|
342
|
+
schema_text = discover_mcp_servers() # see: 1-discovery-from-als
|
|
343
|
+
|
|
344
|
+
query = get_user_nl_query()
|
|
345
|
+
|
|
346
|
+
tool_context = query_llm_with_nl(query) # see: 2-tool-context-from-LLM
|
|
347
|
+
|
|
348
|
+
mcp_response = process_tool_context(tool_context) # see: 3-MCP-server response
|
|
349
|
+
|
|
350
|
+
print("\nTest complete.\n")
|
|
@@ -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'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import requests
|
|
1
|
+
import requests, json
|
|
2
2
|
|
|
3
3
|
# MCP-style tool_context
|
|
4
|
-
|
|
4
|
+
# does not like this pagination
|
|
5
5
|
tool_context = {
|
|
6
6
|
"method": "GET",
|
|
7
7
|
"url": "http://localhost:5656/api/Customer",
|
|
@@ -13,8 +13,8 @@ tool_context = {
|
|
|
13
13
|
"Accept": "application/vnd.api+json"
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
tool_context = {
|
|
16
|
+
|
|
17
|
+
tool_context = { # use this for nw
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"url": "http://localhost:5656/api/Customer",
|
|
20
20
|
"query_params": {
|
|
@@ -22,12 +22,24 @@ tool_context = {
|
|
|
22
22
|
},
|
|
23
23
|
"headers": {
|
|
24
24
|
"Accept": "application/vnd.api+json"
|
|
25
|
+
, "Authorization": "Bearer your_token"
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
tool_context = { # use this for genai_demo
|
|
30
|
+
"method": "GET",
|
|
31
|
+
"url": "http://localhost:5656/api/Customer",
|
|
32
|
+
"query_params": {
|
|
33
|
+
"filter[name]": "Alice"
|
|
34
|
+
},
|
|
35
|
+
"headers": {
|
|
36
|
+
"Accept": "application/vnd.api+json"
|
|
37
|
+
, "Authorization": "Bearer your_token"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
28
40
|
|
|
29
41
|
# Execute as a simulated MCP executor
|
|
30
|
-
response = requests.get(
|
|
42
|
+
response = requests.get( # use this for genai_demo
|
|
31
43
|
tool_context["url"],
|
|
32
44
|
headers=tool_context["headers"],
|
|
33
45
|
params=tool_context["query_params"]
|
|
@@ -35,4 +47,6 @@ response = requests.get(
|
|
|
35
47
|
|
|
36
48
|
# Display result
|
|
37
49
|
print(response.status_code)
|
|
38
|
-
|
|
50
|
+
# Print the response, format it as JSON with indent
|
|
51
|
+
print(json.dumps(response.json(), indent=4))
|
|
52
|
+
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
**OpenAI functions** are tools that extend the capabilities of ChatGPT by allowing it to access real-time data, perform actions, or connect with external services via APIs. .
|
|
2
|
+
|
|
3
|
+
Instead of being limited to its pre-trained knowledge, ChatGPT can use plugins to retrieve up-to-date information (like live weather, stock prices, or databases) or perform tasks (like booking a flight or running a query).
|
|
4
|
+
|
|
5
|
+
The goal is to turn ChatGPT into a more useful, interactive assistant that can bridge AI language understanding with real-world actions and live data.
|
|
6
|
+
|
|
7
|
+
>For example, in large companies, it can be remarkably hard to find corporate systems via an Intranet, and use different user interfaces. ChatGPT can simplify finding these, and interacting with Natural Language.
|
|
8
|
+
|
|
9
|
+
This is to explore:
|
|
10
|
+
|
|
11
|
+
| Explore | Status |
|
|
12
|
+
| ------------------------------------------ | -------------------- |
|
|
13
|
+
| Nat Lang ALS Access using OpenAI Functions | Initial Test Running |
|
|
14
|
+
| | |
|
|
15
|
+
|
|
16
|
+
A value prop might be summarized: *instantly expose legacy DBs to Natural Language, including critical business logic and security, to simplify user discovery and operation.*
|
|
17
|
+
|
|
18
|
+
<br>
|
|
19
|
+
|
|
20
|
+
## Status: Technology Exploration
|
|
21
|
+
|
|
22
|
+
This is an initial experiment, without automation. Many substantive issues need to be addressed, including but not limited to security, update, etc.
|
|
23
|
+
|
|
24
|
+
We welcome participation in this exploration. Please contact us via [discord](https://discord.gg/HcGxbBsgRF).
|
|
25
|
+
|
|
26
|
+
This exploration is changing rapidly. For updates, replace `integration/mcp` from [integration/msp](https://github.com/ApiLogicServer/ApiLogicServer-src/tree/main/api_logic_server_cli/prototypes/nw_no_cust/integration/openai_plugin)
|
|
27
|
+
|
|
28
|
+
<br>
|
|
29
|
+
|
|
30
|
+
## Nat Lang ALS Access using OpenAI Plugin
|
|
31
|
+
|
|
32
|
+
Requires tunnel to local host such as [ngrok](https://ngrok.com/downloads/mac-os?tab=download), then
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
ngrok config add-authtoken <obtain from https://dashboard.ngrok.com/get-started/setup/macos>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
then start the tunnel
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
ngrok http 5656
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
You should see:
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
and note the url like: `https://42da-2601-644-4900-etc.ngrok-free.app -> http://localhost:5656`
|
|
49
|
+
|
|
50
|
+
We'll call it `tunnel_url`
|
|
51
|
+
|
|
52
|
+
Enter this into `config/default.env`
|
|
53
|
+
|
|
54
|
+
<br>
|
|
55
|
+
|
|
56
|
+
### Use GenAI_Demo
|
|
57
|
+
|
|
58
|
+
See `mcp/README_mcp.md`
|
|
59
|
+
|
|
60
|
+
<br>
|
|
61
|
+
|
|
62
|
+
### Obtain swagger_3
|
|
63
|
+
|
|
64
|
+
This has already been done using the procedure described below.
|
|
65
|
+
|
|
66
|
+
Obtain swagger 2 from API Logic Server, eg, http://localhost:5656/api/swagger.json)
|
|
67
|
+
|
|
68
|
+
Convert to 3: https://converter.swagger.io or other.
|
|
69
|
+
|
|
70
|
+
<br>
|
|
71
|
+
|
|
72
|
+
#### Reduce to 30 Operations
|
|
73
|
+
|
|
74
|
+
Reduce down to 30 operations (genai_demo has 69).
|
|
75
|
+
|
|
76
|
+
For testing, you can copy `integration/openai_plugin/swagger_3_genai_demo.json` or `integration/openai_plugin/nw-swagger_3.json` over `integration/openai_plugin/swagger_3.json`.
|
|
77
|
+
|
|
78
|
+
This was obtained using ChatGPT with prompts like:
|
|
79
|
+
|
|
80
|
+
1. Optionally collapse GET by ID and GET collection into a single endpoint using query params
|
|
81
|
+
2. remove POST from relationship endpoints
|
|
82
|
+
3. remove delete
|
|
83
|
+
4. collapse relationship endpoints further
|
|
84
|
+
|
|
85
|
+
then fix the result:
|
|
86
|
+
|
|
87
|
+
1. ensure servers and paths is retained (got deleted for me), and includes https:
|
|
88
|
+
2. version 3.1.0
|
|
89
|
+
|
|
90
|
+
Still seeing (fix with Chat):
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
In path /Customer, method get is missing operationId; skipping
|
|
94
|
+
In path /Customer, method post is missing operationId; skipping
|
|
95
|
+
In path /Order, method get is missing operationId; skipping
|
|
96
|
+
In path /Order, method post is missing operationId; skipping
|
|
97
|
+
In path /Item, method get is missing operationId; skipping
|
|
98
|
+
In path /Item, method post is missing operationId; skipping
|
|
99
|
+
In path /Product, method get is missing operationId; skipping
|
|
100
|
+
In path /Product, method post is missing operationId; skipping
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
<br>
|
|
104
|
+
|
|
105
|
+
### Custom endpoint for openapi
|
|
106
|
+
|
|
107
|
+
OpenAI requires a openai document, so observe the custom endpoint - `api/api_discovery/openapi` - eg, to test locally: `http://localhost:5656/api/openai.json`
|
|
108
|
+
|
|
109
|
+
Note: the url for use in ChatGPT is the tunnelled version, from the env variable.
|
|
110
|
+
|
|
111
|
+
<br>
|
|
112
|
+
|
|
113
|
+
### Configure in ChatGPT
|
|
114
|
+
|
|
115
|
+
Then, upload it to the **Web** version of ChatGPT:
|
|
116
|
+
|
|
117
|
+
1. Explore GPTs
|
|
118
|
+
2. Create
|
|
119
|
+
3. Configure
|
|
120
|
+
4. Create New Action
|
|
121
|
+
|
|
122
|
+
Provide the url of the openai endpoint:
|
|
123
|
+
|
|
124
|
+
https://tunnel_url.ngrok-free.app/api/openapi.json
|
|
125
|
+
|
|
126
|
+
<br>
|
|
127
|
+
|
|
128
|
+
### Retrieval worked:
|
|
129
|
+
|
|
130
|
+
- list customers
|
|
131
|
+
- list the items of order 1 with their product names
|
|
132
|
+
|
|
133
|
+
<br>
|
|
134
|
+
|
|
135
|
+
### Update: Resoved, pending verification
|
|
136
|
+
|
|
137
|
+
We also experimented with update, using `integration/openai_plugin/swagger_3.json`.
|
|
138
|
+
|
|
139
|
+
> It initially failed to load, which we repaired as noted in Appendix 2.
|
|
140
|
+
|
|
141
|
+
> It then failed to generate proper update API, evidently due to bad OpenAPI spec as noted in Appendix 3.
|
|
142
|
+
|
|
143
|
+
<br>
|
|
144
|
+
|
|
145
|
+
## Appendices
|
|
146
|
+
|
|
147
|
+
<br>
|
|
148
|
+
|
|
149
|
+
### Appendix 1: Create ai_plug_in.json
|
|
150
|
+
|
|
151
|
+
We also looked at openai plugins. These appear to be discontinued.
|
|
152
|
+
|
|
153
|
+
Prepare `ai_plug_in.json` as shown in this directory. Observe that it It identifies the url for finding the openapi through the tunnel.
|
|
154
|
+
|
|
155
|
+
Note: both ALS and and `ai_plug_in.json` presume the swagger and api are consistent:
|
|
156
|
+
|
|
157
|
+
- swagger is at `http://localhost:5656/api/swagger.json`,
|
|
158
|
+
- typical API at `http://localhost:5656/api/Category`
|
|
159
|
+
|
|
160
|
+
Not required for function - **Settings / Beta / Plugins > Plugin install → expects the ai-plugin.json manifest URL**
|
|
161
|
+
|
|
162
|
+
This appears to be unavailable for ChatGPT 4o
|
|
163
|
+
|
|
164
|
+
<br>
|
|
165
|
+
|
|
166
|
+
### Appendix 2: Updateable openapi
|
|
167
|
+
|
|
168
|
+
It initially failed to load with
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
In context=('paths', '/Customer/{CustomerId}/', 'patch', 'requestBody', 'content', 'application/json', 'schema'), reference to unknown component Customer_inst; using empty schema
|
|
172
|
+
|
|
173
|
+
In path /Customer/{CustomerId}/, method patch, operationId UpdateCustomer_0, request body schema is not an object schema; skipping
|
|
174
|
+
|
|
175
|
+
In path /Customer/{CustomerId}/, method patch, operationId UpdateCustomer_0, skipping function due to errors
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
We requested a revised jasonapi from ChatGPT to clear these errors, which loaded.
|
|
179
|
+
|
|
180
|
+
<br>
|
|
181
|
+
|
|
182
|
+
### Appendix 3: Invalid Data Object
|
|
183
|
+
|
|
184
|
+
This appears to be caused by improper JSON:API openAPI spec, which caused ChatGPT to generate an improper json PATCH payload:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
chatgpt_request_json = {
|
|
188
|
+
"credit_limit": 25000,
|
|
189
|
+
}
|
|
190
|
+
standard_request_json = {
|
|
191
|
+
"data": {
|
|
192
|
+
"type": "Customer",
|
|
193
|
+
"id": "ALFKI",
|
|
194
|
+
"attributes": {
|
|
195
|
+
"name": "Alice",
|
|
196
|
+
"credit_limit": 25000,
|
|
197
|
+
"balance": 12345
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "v1",
|
|
3
|
+
"name_for_human": "ALS NW-",
|
|
4
|
+
"name_for_model": "ALS NW",
|
|
5
|
+
"description_for_human": "Access and manage categories, customers, and more.",
|
|
6
|
+
"description_for_model": "Plugin for interacting with the API Logic Server.",
|
|
7
|
+
"auth": {
|
|
8
|
+
"type": "none"
|
|
9
|
+
},
|
|
10
|
+
"api": {
|
|
11
|
+
"type": "openapi",
|
|
12
|
+
"url": "https://tunnel_url.ngrok-free.app/api/openapi"
|
|
13
|
+
},
|
|
14
|
+
"logo_url": "https://your-logo-url/logo.png",
|
|
15
|
+
"contact_email": "support@yourdomain.com",
|
|
16
|
+
"legal_info_url": "https://yourdomain.com/legal"
|
|
17
|
+
}
|