Commit
·
6c01c87
1
Parent(s):
b47bb6e
init: clean up unnecessary components for demo
Browse files- .env.sample +12 -2
- .gitignore +2 -1
- base_chat_history.json +1 -0
- chat_history.json +1 -1
- gradio_app.py +7 -3
- langchain_mcp_client.py +45 -40
- postgre_mcp_client.py +0 -128
- postgre_mcp_server.py +77 -331
- postgre_smolagent_client.py +0 -133
.env.sample
CHANGED
|
@@ -1,2 +1,12 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MCP_SERVER_PATH=
|
| 2 |
+
TABLE_SUMMARY_PATH=
|
| 3 |
+
|
| 4 |
+
DB_URL=
|
| 5 |
+
|
| 6 |
+
GEMINI_API_KEY=
|
| 7 |
+
GEMINI_MODEL=
|
| 8 |
+
|
| 9 |
+
PANDAS_KEY=
|
| 10 |
+
|
| 11 |
+
OPENAI_MODEL=
|
| 12 |
+
OPENAI_API_KEY=
|
.gitignore
CHANGED
|
@@ -122,4 +122,5 @@ dmypy.json
|
|
| 122 |
.pytype/
|
| 123 |
|
| 124 |
# Cython debug symbols
|
| 125 |
-
cython_debug/
|
|
|
|
|
|
| 122 |
.pytype/
|
| 123 |
|
| 124 |
# Cython debug symbols
|
| 125 |
+
cython_debug/
|
| 126 |
+
/.gradio/
|
base_chat_history.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "# Result\nThe tables in the database are:\n* dim\\_agreement\n* dim\\_customer\n* dim\\_product\n* dim\\_product\\_order\\_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}]
|
chat_history.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
[{"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "# Result\nThe tables in the database are:\n* dim\\_agreement\n* dim\\_customer\n* dim\\_product\n* dim\\_product\\_order\\_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "what was my previous request ?", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "# Result\nlist all tables\n# Explanation\nThe user's previous request was to list all the tables in the database. This was determined by reviewing the previous turn in the conversation history.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "count tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\n4\n# Explanation\nThe user requested the number of tables. The previous response listed the tables, and counting them gives the answer.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "what was my last message ?", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\ncount tables\n# Explanation\nThe user's last message was \"count tables\". This was determined by reviewing the previous turn in the conversation history.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "what was my very first message in this conversation?", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\nlist all tables\n# Explanation\nThe user's very first message in this conversation was \"list all tables\". This was determined by reviewing the conversation history.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "total number of sales in 2024", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\nThe tables in the database are:\n* dim\\_agreement\n* dim\\_customer\n* dim\\_product\n* dim\\_product\\_order\\_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\nThe tables in the database are:\n* dim_agreement\n* dim_customer\n* dim_product\n* dim_product_order_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}]
|
|
|
|
| 1 |
+
[{"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "# Result\nThe tables in the database are:\n* dim\\_agreement\n* dim\\_customer\n* dim\\_product\n* dim\\_product\\_order\\_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "what was my previous request ?", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "# Result\nlist all tables\n# Explanation\nThe user's previous request was to list all the tables in the database. This was determined by reviewing the previous turn in the conversation history.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "count tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\n4\n# Explanation\nThe user requested the number of tables. The previous response listed the tables, and counting them gives the answer.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "what was my last message ?", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\ncount tables\n# Explanation\nThe user's last message was \"count tables\". This was determined by reviewing the previous turn in the conversation history.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "what was my very first message in this conversation?", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\nlist all tables\n# Explanation\nThe user's very first message in this conversation was \"list all tables\". This was determined by reviewing the conversation history.\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "total number of sales in 2024", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\nThe tables in the database are:\n* dim\\_agreement\n* dim\\_customer\n* dim\\_product\n* dim\\_product\\_order\\_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "list all tables", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "```markdown\n# Result\nThe tables in the database are:\n* dim_agreement\n* dim_customer\n* dim_product\n* dim_product_order_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "hi there. show me the largest table name", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "I am sorry, I cannot directly determine the \"largest table name\" based on the available tools. I can list the tables and show their schemas, but I do not have a tool to determine table sizes. I would need a tool to get table sizes to fulfill this request. However, I can list the tables for you.\n\n```markdown\n# Result\nThe tables in the database are:\n* dim\\_agreement\n* dim\\_customer\n* dim\\_product\n* dim\\_product\\_order\\_item\n\n# Explanation\nThe `list_tables` tool was called to retrieve a list of all available tables in the database schema. The result shows the names of these tables.\n\n# Query\n```sql\nN/A\n```", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "you are free to execute any sql query and get the size of each table and return me the count of largest one", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "I am sorry, I cannot directly determine the \"largest table\" based on the available tools. I can list the tables and show their schemas, but I do not have a tool to determine table sizes. I would need a tool to get table sizes to fulfill this request. I can list the tables for you, but I cannot provide the count of the largest one.", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}]
|
gradio_app.py
CHANGED
|
@@ -2,10 +2,9 @@ import yaml
|
|
| 2 |
from pathlib import Path
|
| 3 |
import gradio as gr
|
| 4 |
import asyncio
|
| 5 |
-
from postgre_mcp_client import pg_mcp_exec
|
| 6 |
-
from postgre_smolagent_client import pg_mcp_smolagent_exec
|
| 7 |
from langchain_mcp_client import lc_mcp_exec
|
| 8 |
|
|
|
|
| 9 |
def load_db_configs():
|
| 10 |
"""Load database configurations from configs.yaml"""
|
| 11 |
configs_path = Path("configs.yaml")
|
|
@@ -18,7 +17,8 @@ def load_db_configs():
|
|
| 18 |
|
| 19 |
return configs["db_configs"]
|
| 20 |
|
| 21 |
-
|
|
|
|
| 22 |
async def run_agent(request, history):
|
| 23 |
# configs = load_db_configs()
|
| 24 |
# final_answer, last_tool_answer, = await pg_mcp_exec(request)
|
|
@@ -27,6 +27,8 @@ async def run_agent(request, history):
|
|
| 27 |
response, message = await lc_mcp_exec(request, history)
|
| 28 |
return response
|
| 29 |
|
|
|
|
|
|
|
| 30 |
demo = gr.ChatInterface(
|
| 31 |
run_agent,
|
| 32 |
type="messages",
|
|
@@ -35,6 +37,7 @@ demo = gr.ChatInterface(
|
|
| 35 |
save_history=True,
|
| 36 |
)
|
| 37 |
|
|
|
|
| 38 |
# Gradio UI
|
| 39 |
# demo = gr.Interface(
|
| 40 |
# fn=run_agent,
|
|
@@ -53,5 +56,6 @@ demo = gr.ChatInterface(
|
|
| 53 |
# flagging_mode="never"
|
| 54 |
# )
|
| 55 |
|
|
|
|
| 56 |
if __name__ == "__main__":
|
| 57 |
demo.launch()
|
|
|
|
| 2 |
from pathlib import Path
|
| 3 |
import gradio as gr
|
| 4 |
import asyncio
|
|
|
|
|
|
|
| 5 |
from langchain_mcp_client import lc_mcp_exec
|
| 6 |
|
| 7 |
+
# ======================================= Load DB configs
|
| 8 |
def load_db_configs():
|
| 9 |
"""Load database configurations from configs.yaml"""
|
| 10 |
configs_path = Path("configs.yaml")
|
|
|
|
| 17 |
|
| 18 |
return configs["db_configs"]
|
| 19 |
|
| 20 |
+
|
| 21 |
+
# ====================================== Async-compatible wrapper
|
| 22 |
async def run_agent(request, history):
|
| 23 |
# configs = load_db_configs()
|
| 24 |
# final_answer, last_tool_answer, = await pg_mcp_exec(request)
|
|
|
|
| 27 |
response, message = await lc_mcp_exec(request, history)
|
| 28 |
return response
|
| 29 |
|
| 30 |
+
|
| 31 |
+
# ====================================== Gradio UI with history
|
| 32 |
demo = gr.ChatInterface(
|
| 33 |
run_agent,
|
| 34 |
type="messages",
|
|
|
|
| 37 |
save_history=True,
|
| 38 |
)
|
| 39 |
|
| 40 |
+
|
| 41 |
# Gradio UI
|
| 42 |
# demo = gr.Interface(
|
| 43 |
# fn=run_agent,
|
|
|
|
| 56 |
# flagging_mode="never"
|
| 57 |
# )
|
| 58 |
|
| 59 |
+
|
| 60 |
if __name__ == "__main__":
|
| 61 |
demo.launch()
|
langchain_mcp_client.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
import os.path
|
| 2 |
import json
|
| 3 |
from typing import Tuple, Any
|
| 4 |
-
|
| 5 |
from mcp import ClientSession, StdioServerParameters
|
| 6 |
from mcp.client.stdio import stdio_client
|
| 7 |
from langchain_mcp_adapters.tools import load_mcp_tools
|
|
@@ -16,9 +15,12 @@ import logging
|
|
| 16 |
from dotenv import load_dotenv
|
| 17 |
|
| 18 |
|
| 19 |
-
|
| 20 |
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
| 21 |
load_dotenv()
|
|
|
|
| 22 |
async def lc_mcp_exec(request: str, history) -> tuple[Any, Any]:
|
| 23 |
"""
|
| 24 |
Execute the full PostgreSQL MCP pipeline with persistent memory.
|
|
@@ -51,20 +53,21 @@ async def lc_mcp_exec(request: str, history) -> tuple[Any, Any]:
|
|
| 51 |
elif isinstance(msg, AIMessage):
|
| 52 |
formatted_history.append(AIMessage(content=msg.content))
|
| 53 |
|
| 54 |
-
|
| 55 |
-
table_summary = load_table_summary("table_summary.txt")
|
| 56 |
server_params = get_server_params()
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
| 61 |
|
|
|
|
| 62 |
async with stdio_client(server_params) as (read, write):
|
| 63 |
async with ClientSession(read, write) as session:
|
| 64 |
await session.initialize()
|
| 65 |
|
| 66 |
tools = await load_and_enrich_tools(session)
|
| 67 |
-
intent = classify_intent(request)
|
| 68 |
|
| 69 |
# Add new user message before processing
|
| 70 |
message_history.add_user_message(request)
|
|
@@ -85,7 +88,13 @@ async def lc_mcp_exec(request: str, history) -> tuple[Any, Any]:
|
|
| 85 |
messages.append(HumanMessage(content=request))
|
| 86 |
|
| 87 |
# Build prompt with conversation context
|
| 88 |
-
prompt = await build_prompt(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
# Invoke agent with proper message structure
|
| 91 |
agent_response = await agent.ainvoke(
|
|
@@ -119,6 +128,7 @@ def load_table_summary(path: str) -> str:
|
|
| 119 |
with open(path, 'r') as file:
|
| 120 |
return file.read()
|
| 121 |
|
|
|
|
| 122 |
def get_server_params() -> StdioServerParameters:
|
| 123 |
# TODO: give server params from config
|
| 124 |
load_dotenv()
|
|
@@ -127,41 +137,36 @@ def get_server_params() -> StdioServerParameters:
|
|
| 127 |
args=[os.environ["MCP_SERVER_PATH"]],
|
| 128 |
)
|
| 129 |
|
|
|
|
| 130 |
async def load_and_enrich_tools(session: ClientSession):
|
| 131 |
tools = await load_mcp_tools(session)
|
| 132 |
return tools
|
| 133 |
|
| 134 |
-
|
| 135 |
-
|
| 136 |
conversation_prompt = await session.read_resource("resource://base_prompt")
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
descriptions=summary,
|
| 162 |
-
chat_history=f"\nPrevious Conversation History:\n{history_str}" if history_str else "\nThis is a new conversation.",
|
| 163 |
-
system_instructions="""You are a PostgreSQL database expert assistant.
|
| 164 |
-
Use the conversation history when available to maintain context.
|
| 165 |
-
For new conversations, focus on understanding the initial request."""
|
| 166 |
-
)
|
| 167 |
|
|
|
|
| 1 |
import os.path
|
| 2 |
import json
|
| 3 |
from typing import Tuple, Any
|
|
|
|
| 4 |
from mcp import ClientSession, StdioServerParameters
|
| 5 |
from mcp.client.stdio import stdio_client
|
| 6 |
from langchain_mcp_adapters.tools import load_mcp_tools
|
|
|
|
| 15 |
from dotenv import load_dotenv
|
| 16 |
|
| 17 |
|
| 18 |
+
# Set up logging
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
# Load environment variables from .env file
|
| 22 |
load_dotenv()
|
| 23 |
+
|
| 24 |
async def lc_mcp_exec(request: str, history) -> tuple[Any, Any]:
|
| 25 |
"""
|
| 26 |
Execute the full PostgreSQL MCP pipeline with persistent memory.
|
|
|
|
| 53 |
elif isinstance(msg, AIMessage):
|
| 54 |
formatted_history.append(AIMessage(content=msg.content))
|
| 55 |
|
| 56 |
+
table_summary = load_table_summary(os.environ["TABLE_SUMMARY_PATH"])
|
|
|
|
| 57 |
server_params = get_server_params()
|
| 58 |
|
| 59 |
+
llm = init_chat_model(
|
| 60 |
+
model=os.getenv("GEMINI_MODEL"),
|
| 61 |
+
model_provider=os.getenv("GEMINI_MODEL_PROVIDER"),
|
| 62 |
+
api_key=os.getenv("GEMINI_API_KEY"),
|
| 63 |
+
)
|
| 64 |
|
| 65 |
+
# Initialize the MCP client
|
| 66 |
async with stdio_client(server_params) as (read, write):
|
| 67 |
async with ClientSession(read, write) as session:
|
| 68 |
await session.initialize()
|
| 69 |
|
| 70 |
tools = await load_and_enrich_tools(session)
|
|
|
|
| 71 |
|
| 72 |
# Add new user message before processing
|
| 73 |
message_history.add_user_message(request)
|
|
|
|
| 88 |
messages.append(HumanMessage(content=request))
|
| 89 |
|
| 90 |
# Build prompt with conversation context
|
| 91 |
+
prompt = await build_prompt(
|
| 92 |
+
session=session,
|
| 93 |
+
request=request,
|
| 94 |
+
tools=tools,
|
| 95 |
+
table_summary=table_summary,
|
| 96 |
+
chat_history=formatted_history
|
| 97 |
+
)
|
| 98 |
|
| 99 |
# Invoke agent with proper message structure
|
| 100 |
agent_response = await agent.ainvoke(
|
|
|
|
| 128 |
with open(path, 'r') as file:
|
| 129 |
return file.read()
|
| 130 |
|
| 131 |
+
|
| 132 |
def get_server_params() -> StdioServerParameters:
|
| 133 |
# TODO: give server params from config
|
| 134 |
load_dotenv()
|
|
|
|
| 137 |
args=[os.environ["MCP_SERVER_PATH"]],
|
| 138 |
)
|
| 139 |
|
| 140 |
+
|
| 141 |
async def load_and_enrich_tools(session: ClientSession):
|
| 142 |
tools = await load_mcp_tools(session)
|
| 143 |
return tools
|
| 144 |
|
| 145 |
+
|
| 146 |
+
async def build_prompt(session, request, tools, table_summary, chat_history):
|
| 147 |
conversation_prompt = await session.read_resource("resource://base_prompt")
|
| 148 |
|
| 149 |
+
template = conversation_prompt.contents[0].text
|
| 150 |
+
tools_str = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
|
| 151 |
+
|
| 152 |
+
# Handle history formatting with proper message access
|
| 153 |
+
history_str = ""
|
| 154 |
+
if chat_history:
|
| 155 |
+
history_sections = []
|
| 156 |
+
for msg in chat_history:
|
| 157 |
+
if isinstance(msg, HumanMessage):
|
| 158 |
+
history_sections.append(f"Previous Human Question:\n{msg.content}\n")
|
| 159 |
+
elif isinstance(msg, AIMessage):
|
| 160 |
+
history_sections.append(f"Previous Assistant Response:\n{msg.content}\n")
|
| 161 |
+
history_str = "\n".join(history_sections)
|
| 162 |
+
|
| 163 |
+
return template.format(
|
| 164 |
+
new_request=request,
|
| 165 |
+
tools=tools_str,
|
| 166 |
+
descriptions=table_summary,
|
| 167 |
+
chat_history=f"\nPrevious Conversation History:\n{history_str}" if history_str else "\nThis is a new conversation.",
|
| 168 |
+
system_instructions="""You are a PostgreSQL database expert assistant.
|
| 169 |
+
Use the conversation history when available to maintain context.
|
| 170 |
+
For new conversations, focus on understanding the initial request."""
|
| 171 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
postgre_mcp_client.py
DELETED
|
@@ -1,128 +0,0 @@
|
|
| 1 |
-
import os.path
|
| 2 |
-
from typing import Tuple, Any
|
| 3 |
-
|
| 4 |
-
from mcp import ClientSession, StdioServerParameters
|
| 5 |
-
from mcp.client.stdio import stdio_client
|
| 6 |
-
from langchain_mcp_adapters.tools import load_mcp_tools
|
| 7 |
-
from langgraph.prebuilt import create_react_agent
|
| 8 |
-
from langchain.chat_models import init_chat_model
|
| 9 |
-
from conversation_memory import ConversationMemory
|
| 10 |
-
from utils import parse_mcp_output, classify_intent
|
| 11 |
-
import logging
|
| 12 |
-
|
| 13 |
-
logger = logging.getLogger(__name__)
|
| 14 |
-
|
| 15 |
-
async def pg_mcp_exec(request: str) -> tuple[Any, Any]:
|
| 16 |
-
"""
|
| 17 |
-
Execute the full PostgreSQL MCP pipeline: load summary, connect session,
|
| 18 |
-
load memory and tools, build prompt, run agent, update memory.
|
| 19 |
-
|
| 20 |
-
Args:
|
| 21 |
-
request (str): User's request input.
|
| 22 |
-
llm (Any): Language model for reasoning agent.
|
| 23 |
-
|
| 24 |
-
Returns:
|
| 25 |
-
str: Agent response message.
|
| 26 |
-
"""
|
| 27 |
-
# TODO: give summary file path from config
|
| 28 |
-
table_summary = load_table_summary("table_summary.txt")
|
| 29 |
-
server_params = get_server_params()
|
| 30 |
-
|
| 31 |
-
# TODO: give key from env
|
| 32 |
-
llm = init_chat_model(model="gemini-2.0-flash", model_provider="google_genai",
|
| 33 |
-
api_key="AIzaSyAuxYmci0DVU5l5L_YcxLlxHzR5MLn70js")
|
| 34 |
-
|
| 35 |
-
async with stdio_client(server_params) as (read, write):
|
| 36 |
-
async with ClientSession(read, write) as session:
|
| 37 |
-
await session.initialize()
|
| 38 |
-
memory = await load_or_create_memory()
|
| 39 |
-
|
| 40 |
-
tools = await load_and_enrich_tools(session, table_summary)
|
| 41 |
-
past_data = get_memory_snapshot(memory)
|
| 42 |
-
|
| 43 |
-
intent = classify_intent(request)
|
| 44 |
-
prompt = await build_prompt(session, intent, request, tools, past_data)
|
| 45 |
-
|
| 46 |
-
agent = create_react_agent(llm, tools)
|
| 47 |
-
agent_response = await agent.ainvoke({"messages": prompt})
|
| 48 |
-
|
| 49 |
-
parsed_steps, final_answer, last_tool_answer, _ = parse_mcp_output(agent_response)
|
| 50 |
-
memory.update_from_parsed(parsed_steps, request)
|
| 51 |
-
|
| 52 |
-
await handle_memory_save_or_reset(memory, request)
|
| 53 |
-
|
| 54 |
-
return final_answer, last_tool_answer
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
# ---------------- Helper Functions ---------------- #
|
| 58 |
-
|
| 59 |
-
def load_table_summary(path: str) -> str:
|
| 60 |
-
with open(path, 'r') as file:
|
| 61 |
-
return file.read()
|
| 62 |
-
|
| 63 |
-
def get_server_params() -> StdioServerParameters:
|
| 64 |
-
# TODO: give server params from config
|
| 65 |
-
return StdioServerParameters(
|
| 66 |
-
command="python",
|
| 67 |
-
args=[r"/home/amirkia/Desktop/query_mcp_server/postgre_mcp_server.py"],
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
async def load_or_create_memory() -> ConversationMemory:
|
| 71 |
-
memory = ConversationMemory()
|
| 72 |
-
if os.path.exists("memory.json"):
|
| 73 |
-
return memory.load_memory()
|
| 74 |
-
return memory
|
| 75 |
-
|
| 76 |
-
async def load_and_enrich_tools(session: ClientSession, summary: str):
|
| 77 |
-
tools = await load_mcp_tools(session)
|
| 78 |
-
for tool in tools:
|
| 79 |
-
tool.description += f" {summary}"
|
| 80 |
-
return tools
|
| 81 |
-
|
| 82 |
-
def get_memory_snapshot(memory: ConversationMemory) -> dict:
|
| 83 |
-
if os.path.exists("memory.json"):
|
| 84 |
-
return {
|
| 85 |
-
"past_tools": memory.get_all_tools_used(),
|
| 86 |
-
"past_queries": memory.get_last_n_queries(),
|
| 87 |
-
"past_results": memory.get_last_n_results(),
|
| 88 |
-
"past_requests": memory.get_all_user_messages()
|
| 89 |
-
}
|
| 90 |
-
return {
|
| 91 |
-
"past_tools": "No tools found",
|
| 92 |
-
"past_queries": "No queries found",
|
| 93 |
-
"past_results": "No results found",
|
| 94 |
-
"past_requests": "No requests found"
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
async def build_prompt(session, intent, request, tools, past_data):
|
| 98 |
-
superset_prompt = await session.read_resource("resource://last_prompt")
|
| 99 |
-
conversation_prompt = await session.read_resource("resource://base_prompt")
|
| 100 |
-
# TODO: add uri's from config
|
| 101 |
-
if intent == "superset_request":
|
| 102 |
-
template = superset_prompt.contents[0].text
|
| 103 |
-
return template.format(
|
| 104 |
-
user_requests=past_data["past_requests"],
|
| 105 |
-
past_tools=past_data["past_tools"],
|
| 106 |
-
last_queries=past_data["past_queries"],
|
| 107 |
-
last_results=past_data["past_results"],
|
| 108 |
-
new_request=request
|
| 109 |
-
)
|
| 110 |
-
else:
|
| 111 |
-
template = conversation_prompt.contents[0].text
|
| 112 |
-
tools_str = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
|
| 113 |
-
return template.format(
|
| 114 |
-
user_requests=past_data["past_requests"],
|
| 115 |
-
past_tools=past_data["past_tools"],
|
| 116 |
-
last_queries=past_data["past_queries"],
|
| 117 |
-
last_results=past_data["past_results"],
|
| 118 |
-
new_request=request,
|
| 119 |
-
tools=tools_str
|
| 120 |
-
)
|
| 121 |
-
|
| 122 |
-
async def handle_memory_save_or_reset(memory: ConversationMemory, request: str):
|
| 123 |
-
if request.strip().lower() == "stop":
|
| 124 |
-
memory.reset()
|
| 125 |
-
logger.info("Conversation memory reset.")
|
| 126 |
-
else:
|
| 127 |
-
memory.save_memory()
|
| 128 |
-
logger.info("Conversation memory saved.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
postgre_mcp_server.py
CHANGED
|
@@ -4,12 +4,12 @@ from dataclasses import dataclass
|
|
| 4 |
from typing import Optional, AsyncIterator
|
| 5 |
|
| 6 |
import asyncpg
|
|
|
|
| 7 |
from mcp.server.fastmcp import FastMCP, Context
|
| 8 |
from pydantic import Field
|
| 9 |
|
| 10 |
# Constants
|
| 11 |
DEFAULT_QUERY_LIMIT = 100
|
| 12 |
-
DEFAULT_SCHEMA = "public"
|
| 13 |
|
| 14 |
|
| 15 |
# Define our own PromptMessage class if the MCP one isn't available
|
|
@@ -31,8 +31,9 @@ class DbContext:
|
|
| 31 |
async def db_lifespan(server: FastMCP) -> AsyncIterator[DbContext]:
|
| 32 |
"""Manage database connection lifecycle"""
|
| 33 |
# Initialize DB connection from environment variables
|
| 34 |
-
|
| 35 |
-
|
|
|
|
| 36 |
pool = await asyncpg.create_pool(dsn)
|
| 37 |
try:
|
| 38 |
yield DbContext(pool=pool, schema=schema)
|
|
@@ -58,149 +59,33 @@ async def base_prompt_query() -> str:
|
|
| 58 |
"""Returns a base prompt to generate sql queries and answer questions"""
|
| 59 |
|
| 60 |
base_prompt = """
|
| 61 |
-
|
| 62 |
-
==========================
|
| 63 |
-
# Your Role
|
| 64 |
-
==========================
|
| 65 |
-
|
| 66 |
-
You are an expert in generating and executing SQL queries and interacting with a PostgreSQL database using **FastMCP tools**. These tools allow you to:
|
| 67 |
-
|
| 68 |
-
- List available tables
|
| 69 |
-
- Retrieve schema details
|
| 70 |
-
- Execute SQL queries
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
Each tool may also return previews or summaries of table contents to help you better understand the data structure.
|
| 74 |
-
|
| 75 |
-
---
|
| 76 |
-
|
| 77 |
-
==========================
|
| 78 |
-
# Your Objective
|
| 79 |
-
==========================
|
| 80 |
-
|
| 81 |
-
When a user submits a request, you must:
|
| 82 |
-
|
| 83 |
-
1. **Analyze the request** to determine the required data or action.
|
| 84 |
-
2. **Use FastMCP tools** to gather any necessary information (e.g., list tables or retrieve schema).
|
| 85 |
-
3. **Generate a valid SQL SELECT query**, if needed, and clearly show the full query.
|
| 86 |
-
4. **Execute the SQL query** and return the results.
|
| 87 |
-
5. **Chain tools logically**, such as: List Tables → Get Schema → Write and Run Query.
|
| 88 |
-
6. **Explain your reasoning and each step taken** to ensure clarity and transparency.
|
| 89 |
-
|
| 90 |
-
---
|
| 91 |
-
|
| 92 |
-
==========================
|
| 93 |
-
# Your Objective
|
| 94 |
-
==========================
|
| 95 |
-
|
| 96 |
-
When a user submits a request, follow these steps:
|
| 97 |
-
|
| 98 |
-
1. Analyze the request to determine the desired data or action.
|
| 99 |
-
2. Use tools to gather any necessary information (e.g., list tables, get schema).
|
| 100 |
-
3. Generate a valid SQL query (such as **SELECT**, **COUNT**, or other read-only operations) and clearly display the full query.
|
| 101 |
-
4. Execute the query and **return the execution result of the query**.
|
| 102 |
-
5. **Chain tools logically to build toward the answer.**
|
| 103 |
-
6. Explain your reasoning at every step for clarity and transparency.
|
| 104 |
-
7. Show the result of the **execute_query** in your final answer.
|
| 105 |
-
|
| 106 |
-
---
|
| 107 |
-
|
| 108 |
-
==========================
|
| 109 |
-
# Critical Rules
|
| 110 |
-
==========================
|
| 111 |
-
|
| 112 |
-
- Only use **read-only** SQL queries such as **SELECT**, **COUNT**, or queries with **GROUP BY**, **ORDER BY**, etc.
|
| 113 |
-
- **Never** use destructive operations like **DELETE**, **UPDATE**, **INSERT**, or **DROP**.
|
| 114 |
-
- Always show the SQL query you generate along with the execution result.
|
| 115 |
-
- Validate SQL syntax before execution.
|
| 116 |
-
- Never assume table or column names. Use tools to confirm structure.
|
| 117 |
-
- Use memory efficiently. Don’t rerun a tool unless necessary.
|
| 118 |
-
- If you generate a SQL query, immediately call the **execute_query** tool using that query. Do not delay or wait for user confirmation.
|
| 119 |
-
|
| 120 |
-
---
|
| 121 |
-
|
| 122 |
-
==========================
|
| 123 |
-
# Database Description
|
| 124 |
-
==========================
|
| 125 |
-
|
| 126 |
-
{descriptions}
|
| 127 |
-
|
| 128 |
-
---
|
| 129 |
-
|
| 130 |
-
==========================
|
| 131 |
-
# Tools
|
| 132 |
-
==========================
|
| 133 |
-
|
| 134 |
-
You can use the following FastMCP tools. These allow you to create **read-only** queries, such as `SELECT`, `COUNT`, or queries with `GROUP BY`, `ORDER BY`, and similar clauses. You may chain tools together to gather the necessary information before generating your SQL query.
|
| 135 |
-
|
| 136 |
-
{tools}
|
| 137 |
-
|
| 138 |
-
---
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
### Invalid Example — DELETE Operation (Not Allowed):
|
| 142 |
-
**User Request:** "Delete all customers from Germany."
|
| 143 |
-
|
| 144 |
-
**Response Guidance:**
|
| 145 |
-
- **Do not generate or execute** destructive queries such as `DELETE`.
|
| 146 |
-
- Instead, respond with a message like:
|
| 147 |
-
> Destructive operations such as `DELETE` are not permitted. I can help you retrieve the customers from Germany using a `SELECT` query instead:
|
| 148 |
-
> ```sql
|
| 149 |
-
> SELECT * FROM customers WHERE country = 'Germany';
|
| 150 |
-
> ```
|
| 151 |
-
|
| 152 |
==========================
|
| 153 |
-
#
|
| 154 |
==========================
|
| 155 |
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
```markdown
|
| 159 |
-
# Result
|
| 160 |
-
{{Take the result from the execute_query tool and format it nicely using Markdown. Use a Markdown table for tabular data (rows and columns) including headers. Use bullet points or items in markdown for answers that include lists of names or descriptions. Use plain text for single values or simple messages. Ensure data alignment and clarity.}}
|
| 161 |
-
|
| 162 |
-
# Explanation
|
| 163 |
-
{{Provide a concise explanation or interpretation of the results in 1-3 sentences. Explain what the data in the 'Result' section represents in the context of the user's request.}}
|
| 164 |
-
|
| 165 |
-
# Query
|
| 166 |
-
```sql
|
| 167 |
-
{{Display the exact SQL query you generated and executed here to answer the user's request.}}
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
**Your final answer cannot be only a SQL query, you will have to call **execute_query** and give the result of the call with the SQL query.**
|
| 173 |
-
---
|
| 174 |
-
{chat_history}
|
| 175 |
-
---
|
| 176 |
-
=========================
|
| 177 |
-
# New User Request
|
| 178 |
-
=========================
|
| 179 |
|
| 180 |
-
Please fulfill the following request based on the above context:
|
| 181 |
|
| 182 |
-
|
| 183 |
-
"""
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
@mcp.resource(
|
| 189 |
-
uri="resource://last_prompt",
|
| 190 |
-
name="last_prompt",
|
| 191 |
-
description="A prompt that identifies the most recent SQL query related to the user's request and reformats it into ANSI SQL syntax for use in Superset."
|
| 192 |
-
)
|
| 193 |
-
async def last_prompt() -> str:
|
| 194 |
-
"""A prompt that identifies the most recent SQL query related to the user's request and reformats it into ANSI SQL syntax for use in Superset."""
|
| 195 |
-
|
| 196 |
-
base_prompt = """
|
| 197 |
|
| 198 |
==========================
|
| 199 |
-
# Your
|
| 200 |
==========================
|
| 201 |
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
---
|
| 206 |
|
|
@@ -208,12 +93,15 @@ Your task is to retrieve the **exact SQL query** that produced a previously seen
|
|
| 208 |
# Your Objective
|
| 209 |
==========================
|
| 210 |
|
| 211 |
-
When a user submits a request
|
| 212 |
|
| 213 |
-
1.
|
| 214 |
-
2.
|
| 215 |
-
3.
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
---
|
| 219 |
|
|
@@ -221,220 +109,78 @@ When a user submits a request (e.g., *"send me that table"*, *"send the last que
|
|
| 221 |
# Critical Rules
|
| 222 |
==========================
|
| 223 |
|
| 224 |
-
-
|
| 225 |
-
-
|
| 226 |
-
-
|
| 227 |
-
-
|
| 228 |
-
-
|
|
|
|
|
|
|
| 229 |
|
| 230 |
---
|
| 231 |
|
| 232 |
==========================
|
| 233 |
-
#
|
| 234 |
==========================
|
| 235 |
-
## Use the following rules as examples and guidance. You are not limited to these rules — apply your own knowledge of SQL standards and PostgreSQL to perform accurate conversions, even in cases not explicitly covered below.
|
| 236 |
-
|
| 237 |
-
### 1. String Concatenation
|
| 238 |
-
- **PostgreSQL:** || for string concatenation
|
| 239 |
-
- **ANSI SQL:** CONCAT() function
|
| 240 |
-
|
| 241 |
-
**Example:**
|
| 242 |
-
```sql
|
| 243 |
-
-- PostgreSQL
|
| 244 |
-
SELECT first_name || ' ' || last_name AS full_name FROM users;
|
| 245 |
-
|
| 246 |
-
-- ANSI SQL
|
| 247 |
-
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
|
| 248 |
-
|
| 249 |
-
### 2. LIMIT Clause
|
| 250 |
-
|
| 251 |
-
- **PostgreSQL:** Uses `LIMIT`
|
| 252 |
-
- **ANSI SQL:** Uses `FETCH FIRST n ROWS ONLY`
|
| 253 |
-
|
| 254 |
-
**Example:**
|
| 255 |
-
```sql
|
| 256 |
-
-- PostgreSQL
|
| 257 |
-
SELECT * FROM posts LIMIT 10;
|
| 258 |
-
|
| 259 |
-
-- ANSI SQL
|
| 260 |
-
SELECT * FROM posts FETCH FIRST 10 ROWS ONLY;
|
| 261 |
-
|
| 262 |
-
### 3. ILIKE (Case-insensitive LIKE)
|
| 263 |
-
|
| 264 |
-
- **PostgreSQL:** `ILIKE`
|
| 265 |
-
- **ANSI SQL:** Use `LOWER(column) LIKE` or `UPPER(column) LIKE`
|
| 266 |
|
| 267 |
-
|
| 268 |
-
```sql
|
| 269 |
-
-- PostgreSQL
|
| 270 |
-
SELECT * FROM users WHERE username ILIKE 'john%';
|
| 271 |
-
|
| 272 |
-
-- ANSI SQL
|
| 273 |
-
SELECT * FROM users WHERE LOWER(username) LIKE 'john%';
|
| 274 |
-
|
| 275 |
-
### 4. Type Casting
|
| 276 |
-
|
| 277 |
-
- **PostgreSQL:** `'123'::INTEGER`
|
| 278 |
-
- **ANSI SQL:** `CAST('123' AS INTEGER)`
|
| 279 |
-
|
| 280 |
-
**Example:**
|
| 281 |
-
```sql
|
| 282 |
-
-- PostgreSQL
|
| 283 |
-
SELECT '123'::INTEGER;
|
| 284 |
-
|
| 285 |
-
-- ANSI SQL
|
| 286 |
-
SELECT CAST('123' AS INTEGER);
|
| 287 |
-
|
| 288 |
-
### 5. INSERT ... RETURNING
|
| 289 |
-
|
| 290 |
-
- **PostgreSQL:** Supports `RETURNING` to get values immediately
|
| 291 |
-
- **ANSI SQL:** No equivalent; use a separate `SELECT` query
|
| 292 |
-
|
| 293 |
-
**Example:**
|
| 294 |
-
```sql
|
| 295 |
-
-- PostgreSQL
|
| 296 |
-
INSERT INTO users (username) VALUES ('john') RETURNING id;
|
| 297 |
-
|
| 298 |
-
-- ANSI SQL
|
| 299 |
-
-- No direct equivalent; you must SELECT after INSERT
|
| 300 |
-
INSERT INTO users (username) VALUES ('john');
|
| 301 |
-
SELECT id FROM users WHERE username = 'john';
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
### 6. UPSERT (INSERT ON CONFLICT)
|
| 305 |
-
|
| 306 |
-
- **PostgreSQL:** `INSERT ... ON CONFLICT`
|
| 307 |
-
- **ANSI SQL:** Use `MERGE` (available in ANSI SQL-2003+)
|
| 308 |
-
|
| 309 |
-
**Example:**
|
| 310 |
-
```sql
|
| 311 |
-
-- PostgreSQL
|
| 312 |
-
INSERT INTO users (id, username)
|
| 313 |
-
VALUES (1, 'john')
|
| 314 |
-
ON CONFLICT (id)
|
| 315 |
-
DO UPDATE SET username = EXCLUDED.username;
|
| 316 |
-
|
| 317 |
-
-- ANSI SQL
|
| 318 |
-
MERGE INTO users AS target
|
| 319 |
-
USING (SELECT 1 AS id, 'john' AS username) AS source
|
| 320 |
-
ON (target.id = source.id)
|
| 321 |
-
WHEN MATCHED THEN
|
| 322 |
-
UPDATE SET username = source.username
|
| 323 |
-
WHEN NOT MATCHED THEN
|
| 324 |
-
INSERT (id, username) VALUES (source.id, source.username);
|
| 325 |
-
|
| 326 |
-
### 7. SERIAL (Auto-increment IDs)
|
| 327 |
-
|
| 328 |
-
- **PostgreSQL:** `SERIAL` keyword
|
| 329 |
-
- **ANSI SQL:** Use `IDENTITY` in table creation
|
| 330 |
-
|
| 331 |
-
**Example:**
|
| 332 |
-
```sql
|
| 333 |
-
-- PostgreSQL
|
| 334 |
-
CREATE TABLE users (
|
| 335 |
-
id SERIAL PRIMARY KEY,
|
| 336 |
-
username TEXT
|
| 337 |
-
);
|
| 338 |
-
|
| 339 |
-
-- ANSI SQL
|
| 340 |
-
CREATE TABLE users (
|
| 341 |
-
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
| 342 |
-
username VARCHAR(255)
|
| 343 |
-
);
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
### 8. Arrays and Accessing Arrays
|
| 347 |
-
|
| 348 |
-
- **PostgreSQL:** Supports arrays and uses `[index]`
|
| 349 |
-
- **ANSI SQL:** No array type; use normalized relational design
|
| 350 |
-
|
| 351 |
-
**Example:**
|
| 352 |
-
```sql
|
| 353 |
-
-- PostgreSQL
|
| 354 |
-
SELECT tags[1] FROM posts;
|
| 355 |
-
|
| 356 |
-
-- ANSI SQL
|
| 357 |
-
-- No equivalent. Use a separate `post_tags` table instead.
|
| 358 |
-
|
| 359 |
-
### 9. PostgreSQL Extensions and Functions
|
| 360 |
-
|
| 361 |
-
- Replace PostgreSQL-specific functions like `NOW()`, `CURRENT_DATE`, etc., with ANSI SQL equivalents when available.
|
| 362 |
-
|
| 363 |
-
**Example:**
|
| 364 |
-
```sql
|
| 365 |
-
-- PostgreSQL
|
| 366 |
-
SELECT NOW(), CURRENT_DATE;
|
| 367 |
-
|
| 368 |
-
-- ANSI SQL
|
| 369 |
-
SELECT CURRENT_TIMESTAMP, CURRENT_DATE;
|
| 370 |
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
- **PostgreSQL:** `FROM users AS u`
|
| 374 |
-
- **ANSI SQL:** `FROM users u` (without `AS` is also valid)
|
| 375 |
-
|
| 376 |
-
**Note:** Both are valid ANSI SQL — no change needed.
|
| 377 |
-
|
| 378 |
-
### 11. Boolean Type
|
| 379 |
-
|
| 380 |
-
- **PostgreSQL:** Uses `BOOLEAN`, and accepts `TRUE` / `FALSE`
|
| 381 |
-
- **ANSI SQL:** Also supports `BOOLEAN`, but in some systems may need conversion to `CHAR(1)` with `'Y'` / `'N'`
|
| 382 |
-
|
| 383 |
-
**Example (if needed):**
|
| 384 |
-
```sql
|
| 385 |
-
-- PostgreSQL
|
| 386 |
-
SELECT * FROM flags WHERE is_enabled = TRUE;
|
| 387 |
|
| 388 |
-
|
| 389 |
-
|
|
|
|
| 390 |
|
|
|
|
| 391 |
|
| 392 |
-
|
| 393 |
|
| 394 |
-
|
| 395 |
-
- **ANSI SQL:** Use `VARCHAR(n)`
|
| 396 |
|
| 397 |
-
**Example:**
|
| 398 |
-
```sql
|
| 399 |
-
-- PostgreSQL
|
| 400 |
-
CREATE TABLE posts (content TEXT);
|
| 401 |
|
| 402 |
-
|
| 403 |
-
|
| 404 |
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
==========================
|
| 408 |
-
#
|
| 409 |
==========================
|
| 410 |
|
| 411 |
-
|
| 412 |
-
**User Request:** "send the last table"
|
| 413 |
-
**You return:**
|
| 414 |
-
```sql
|
| 415 |
-
SELECT * FROM posts INNER JOIN users ON posts.user_id = users.id;
|
| 416 |
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
```sql
|
| 421 |
-
SELECT COUNT(*) FROM users;
|
| 422 |
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
**You return:**
|
| 426 |
-
(latest known query):
|
| 427 |
-
```sql
|
| 428 |
-
SELECT * FROM posts WHERE user_id = 1;
|
| 429 |
|
| 430 |
-
|
|
|
|
|
|
|
| 431 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
=========================
|
| 433 |
# New User Request
|
| 434 |
=========================
|
| 435 |
-
{new_request}
|
| 436 |
|
| 437 |
-
|
|
|
|
|
|
|
|
|
|
| 438 |
|
| 439 |
return base_prompt
|
| 440 |
|
|
@@ -565,7 +311,7 @@ async def list_tables() -> str:
|
|
| 565 |
async def get_table_schema(table_name: str) -> str:
|
| 566 |
"""Get schema information for a specific table"""
|
| 567 |
try:
|
| 568 |
-
schema = os.
|
| 569 |
|
| 570 |
async with db_lifespan(mcp) as db_ctx:
|
| 571 |
columns = await get_table_schema_info(db_ctx.pool, schema, table_name)
|
|
@@ -588,7 +334,7 @@ def get_foreign_keys(table_name: str) -> str:
|
|
| 588 |
table_name: The name of the table to get foreign keys from
|
| 589 |
schema: The schema name (defaults to 'public')
|
| 590 |
"""
|
| 591 |
-
schema = os.
|
| 592 |
|
| 593 |
sql = """
|
| 594 |
SELECT
|
|
@@ -617,7 +363,7 @@ def get_foreign_keys(table_name: str) -> str:
|
|
| 617 |
async def get_all_schemas() -> str:
|
| 618 |
"""Get schema information for all tables in the database"""
|
| 619 |
try:
|
| 620 |
-
schema = os.
|
| 621 |
|
| 622 |
async with db_lifespan(mcp) as db_ctx:
|
| 623 |
tables = await get_all_tables(db_ctx.pool, db_ctx.schema)
|
|
|
|
| 4 |
from typing import Optional, AsyncIterator
|
| 5 |
|
| 6 |
import asyncpg
|
| 7 |
+
from flask.cli import load_dotenv
|
| 8 |
from mcp.server.fastmcp import FastMCP, Context
|
| 9 |
from pydantic import Field
|
| 10 |
|
| 11 |
# Constants
|
| 12 |
DEFAULT_QUERY_LIMIT = 100
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
# Define our own PromptMessage class if the MCP one isn't available
|
|
|
|
| 31 |
async def db_lifespan(server: FastMCP) -> AsyncIterator[DbContext]:
|
| 32 |
"""Manage database connection lifecycle"""
|
| 33 |
# Initialize DB connection from environment variables
|
| 34 |
+
load_dotenv()
|
| 35 |
+
dsn = os.getenv("DB_URL")
|
| 36 |
+
schema = os.getenv("DB_SCHEMA")
|
| 37 |
pool = await asyncpg.create_pool(dsn)
|
| 38 |
try:
|
| 39 |
yield DbContext(pool=pool, schema=schema)
|
|
|
|
| 59 |
"""Returns a base prompt to generate sql queries and answer questions"""
|
| 60 |
|
| 61 |
base_prompt = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
==========================
|
| 63 |
+
# Your Role
|
| 64 |
==========================
|
| 65 |
|
| 66 |
+
You are an expert in generating and executing SQL queries and interacting with a PostgreSQL database using **FastMCP tools**. These tools allow you to:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
+
- List available tables
|
| 69 |
+
- Retrieve schema details
|
| 70 |
+
- Execute SQL queries
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
|
|
|
| 72 |
|
| 73 |
+
Each tool may also return previews or summaries of table contents to help you better understand the data structure.
|
|
|
|
| 74 |
|
| 75 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
==========================
|
| 78 |
+
# Your Objective
|
| 79 |
==========================
|
| 80 |
|
| 81 |
+
When a user submits a request, you must:
|
| 82 |
+
|
| 83 |
+
1. **Analyze the request** to determine the required data or action.
|
| 84 |
+
2. **Use FastMCP tools** to gather any necessary information (e.g., list tables or retrieve schema).
|
| 85 |
+
3. **Generate a valid SQL SELECT query**, if needed, and clearly show the full query.
|
| 86 |
+
4. **Execute the SQL query** and return the results.
|
| 87 |
+
5. **Chain tools logically**, such as: List Tables → Get Schema → Write and Run Query.
|
| 88 |
+
6. **Explain your reasoning and each step taken** to ensure clarity and transparency.
|
| 89 |
|
| 90 |
---
|
| 91 |
|
|
|
|
| 93 |
# Your Objective
|
| 94 |
==========================
|
| 95 |
|
| 96 |
+
When a user submits a request, follow these steps:
|
| 97 |
|
| 98 |
+
1. Analyze the request to determine the desired data or action.
|
| 99 |
+
2. Use tools to gather any necessary information (e.g., list tables, get schema).
|
| 100 |
+
3. Generate a valid SQL query (such as **SELECT**, **COUNT**, or other read-only operations) and clearly display the full query.
|
| 101 |
+
4. Execute the query and **return the execution result of the query**.
|
| 102 |
+
5. **Chain tools logically to build toward the answer.**
|
| 103 |
+
6. Explain your reasoning at every step for clarity and transparency.
|
| 104 |
+
7. Show the result of the **execute_query** in your final answer.
|
| 105 |
|
| 106 |
---
|
| 107 |
|
|
|
|
| 109 |
# Critical Rules
|
| 110 |
==========================
|
| 111 |
|
| 112 |
+
- Only use **read-only** SQL queries such as **SELECT**, **COUNT**, or queries with **GROUP BY**, **ORDER BY**, etc.
|
| 113 |
+
- **Never** use destructive operations like **DELETE**, **UPDATE**, **INSERT**, or **DROP**.
|
| 114 |
+
- Always show the SQL query you generate along with the execution result.
|
| 115 |
+
- Validate SQL syntax before execution.
|
| 116 |
+
- Never assume table or column names. Use tools to confirm structure.
|
| 117 |
+
- Use memory efficiently. Don’t rerun a tool unless necessary.
|
| 118 |
+
- If you generate a SQL query, immediately call the **execute_query** tool using that query. Do not delay or wait for user confirmation.
|
| 119 |
|
| 120 |
---
|
| 121 |
|
| 122 |
==========================
|
| 123 |
+
# Database Description
|
| 124 |
==========================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
{descriptions}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
+
==========================
|
| 131 |
+
# Tools
|
| 132 |
+
==========================
|
| 133 |
|
| 134 |
+
You can use the following FastMCP tools. These allow you to create **read-only** queries, such as `SELECT`, `COUNT`, or queries with `GROUP BY`, `ORDER BY`, and similar clauses. You may chain tools together to gather the necessary information before generating your SQL query.
|
| 135 |
|
| 136 |
+
{tools}
|
| 137 |
|
| 138 |
+
---
|
|
|
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
+
### Invalid Example — DELETE Operation (Not Allowed):
|
| 142 |
+
**User Request:** "Delete all customers from Germany."
|
| 143 |
|
| 144 |
+
**Response Guidance:**
|
| 145 |
+
- **Do not generate or execute** destructive queries such as `DELETE`.
|
| 146 |
+
- Instead, respond with a message like:
|
| 147 |
+
> Destructive operations such as `DELETE` are not permitted. I can help you retrieve the customers from Germany using a `SELECT` query instead:
|
| 148 |
+
> ```sql
|
| 149 |
+
> SELECT * FROM customers WHERE country = 'Germany';
|
| 150 |
+
> ```
|
| 151 |
|
| 152 |
==========================
|
| 153 |
+
# Output Format
|
| 154 |
==========================
|
| 155 |
|
| 156 |
+
Present your final answer using the following structure **exactly**. When necessary, bold the important parts of your answer or use `` inline code blocks.:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
+
```markdown
|
| 159 |
+
# Result
|
| 160 |
+
{{Take the result from the execute_query tool and format it nicely using Markdown. Use a Markdown table for tabular data (rows and columns) including headers. Use bullet points or items in markdown for answers that include lists of names or descriptions. Use plain text for single values or simple messages. Ensure data alignment and clarity.}}
|
|
|
|
|
|
|
| 161 |
|
| 162 |
+
# Explanation
|
| 163 |
+
{{Provide a concise explanation or interpretation of the results in 1-3 sentences. Explain what the data in the 'Result' section represents in the context of the user's request.}}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
+
# Query
|
| 166 |
+
```sql
|
| 167 |
+
{{Display the exact SQL query you generated and executed here to answer the user's request.}}
|
| 168 |
|
| 169 |
+
**Reminder:**
|
| 170 |
+
**Every time you generate a SQL query, call **execute_query** right after and include the result in your final response.**
|
| 171 |
+
**If you do not execute the generated SQL query, this will be the violation of the instructions**
|
| 172 |
+
**Your final answer cannot be only a SQL query, you will have to call **execute_query** and give the result of the call with the SQL query.**
|
| 173 |
+
---
|
| 174 |
+
{chat_history}
|
| 175 |
+
---
|
| 176 |
=========================
|
| 177 |
# New User Request
|
| 178 |
=========================
|
|
|
|
| 179 |
|
| 180 |
+
Please fulfill the following request based on the above context:
|
| 181 |
+
|
| 182 |
+
{new_request}
|
| 183 |
+
"""
|
| 184 |
|
| 185 |
return base_prompt
|
| 186 |
|
|
|
|
| 311 |
async def get_table_schema(table_name: str) -> str:
|
| 312 |
"""Get schema information for a specific table"""
|
| 313 |
try:
|
| 314 |
+
schema = os.getenv("DB_SCHEMA")
|
| 315 |
|
| 316 |
async with db_lifespan(mcp) as db_ctx:
|
| 317 |
columns = await get_table_schema_info(db_ctx.pool, schema, table_name)
|
|
|
|
| 334 |
table_name: The name of the table to get foreign keys from
|
| 335 |
schema: The schema name (defaults to 'public')
|
| 336 |
"""
|
| 337 |
+
schema = os.getenv("DB_SCHEMA")
|
| 338 |
|
| 339 |
sql = """
|
| 340 |
SELECT
|
|
|
|
| 363 |
async def get_all_schemas() -> str:
|
| 364 |
"""Get schema information for all tables in the database"""
|
| 365 |
try:
|
| 366 |
+
schema = os.getenv("DB_SCHEMA")
|
| 367 |
|
| 368 |
async with db_lifespan(mcp) as db_ctx:
|
| 369 |
tables = await get_all_tables(db_ctx.pool, db_ctx.schema)
|
postgre_smolagent_client.py
DELETED
|
@@ -1,133 +0,0 @@
|
|
| 1 |
-
import os.path
|
| 2 |
-
from mcp import ClientSession, StdioServerParameters
|
| 3 |
-
from mcp.client.stdio import stdio_client
|
| 4 |
-
from langchain_mcp_adapters.tools import load_mcp_tools
|
| 5 |
-
from langgraph.prebuilt import create_react_agent
|
| 6 |
-
from langchain.chat_models import init_chat_model
|
| 7 |
-
from conversation_memory import ConversationMemory
|
| 8 |
-
from utils import parse_mcp_output, classify_intent
|
| 9 |
-
import logging
|
| 10 |
-
from smolagents import LiteLLMModel, ToolCollection, CodeAgent
|
| 11 |
-
from dotenv import load_dotenv
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
logger = logging.getLogger(__name__)
|
| 15 |
-
|
| 16 |
-
async def pg_mcp_smolagent_exec(request: str) -> str:
|
| 17 |
-
"""
|
| 18 |
-
Execute the full PostgreSQL MCP pipeline: load summary, connect session,
|
| 19 |
-
load memory and tools, build prompt, run agent, update memory.
|
| 20 |
-
|
| 21 |
-
Args:
|
| 22 |
-
request (str): User's request input.
|
| 23 |
-
llm (Any): Language model for reasoning agent.
|
| 24 |
-
|
| 25 |
-
Returns:
|
| 26 |
-
str: Agent response message.
|
| 27 |
-
"""
|
| 28 |
-
# TODO: give summary file path from config
|
| 29 |
-
table_summary = load_table_summary("table_summary.txt")
|
| 30 |
-
server_params = get_server_params()
|
| 31 |
-
|
| 32 |
-
# TODO: give key from env
|
| 33 |
-
llm = LiteLLMModel(model_id="gemini/gemini-2.0-flash", api_key="AIzaSyDRoQ8yG0Rmpt3NtfHS7fdm5hgfL9su5aY")
|
| 34 |
-
|
| 35 |
-
async with stdio_client(server_params) as (read, write):
|
| 36 |
-
async with ClientSession(read, write) as session:
|
| 37 |
-
await session.initialize()
|
| 38 |
-
memory = await load_or_create_memory()
|
| 39 |
-
|
| 40 |
-
intent = classify_intent(request)
|
| 41 |
-
with ToolCollection.from_mcp(server_params, trust_remote_code=True) as tool_collection:
|
| 42 |
-
# Create a Code agent using the LLM and tools
|
| 43 |
-
agent = CodeAgent(model=llm, tools=[*tool_collection.tools], add_base_tools=True)
|
| 44 |
-
tools = await load_and_enrich_tools(session, table_summary)
|
| 45 |
-
past_data = get_memory_snapshot(memory)
|
| 46 |
-
|
| 47 |
-
prompt = await build_prompt(session, intent, request, tools, past_data, table_summary)
|
| 48 |
-
agent_response = agent.run(task=prompt, stream=False)
|
| 49 |
-
|
| 50 |
-
# TODO: add a smolagent output parser
|
| 51 |
-
|
| 52 |
-
#parsed_steps, _ = parse_mcp_output(agent_response)
|
| 53 |
-
#memory.update_from_parsed(parsed_steps, request)
|
| 54 |
-
|
| 55 |
-
#await handle_memory_save_or_reset(memory, request)
|
| 56 |
-
|
| 57 |
-
return agent_response
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
# ---------------- Helper Functions ---------------- #
|
| 61 |
-
|
| 62 |
-
def load_table_summary(path: str) -> str:
|
| 63 |
-
with open(path, 'r') as file:
|
| 64 |
-
return file.read()
|
| 65 |
-
|
| 66 |
-
def get_server_params() -> StdioServerParameters:
|
| 67 |
-
# TODO: give server params from config
|
| 68 |
-
load_dotenv()
|
| 69 |
-
return StdioServerParameters(
|
| 70 |
-
command="python",
|
| 71 |
-
args=[os.environ["MCP_SERVER_PATH"]],
|
| 72 |
-
)
|
| 73 |
-
|
| 74 |
-
async def load_or_create_memory() -> ConversationMemory:
|
| 75 |
-
memory = ConversationMemory()
|
| 76 |
-
if os.path.exists("memory.json"):
|
| 77 |
-
return memory.load_memory()
|
| 78 |
-
return memory
|
| 79 |
-
|
| 80 |
-
async def load_and_enrich_tools(session: ClientSession, summary: str):
|
| 81 |
-
tools = await load_mcp_tools(session)
|
| 82 |
-
#for tool in tools:
|
| 83 |
-
# tool.description += f" {summary}"
|
| 84 |
-
return tools
|
| 85 |
-
|
| 86 |
-
def get_memory_snapshot(memory: ConversationMemory) -> dict:
|
| 87 |
-
if os.path.exists("memory.json"):
|
| 88 |
-
return {
|
| 89 |
-
"past_tools": memory.get_all_tools_used(),
|
| 90 |
-
"past_queries": memory.get_last_n_queries(),
|
| 91 |
-
"past_results": memory.get_last_n_results(),
|
| 92 |
-
"past_requests": memory.get_all_user_messages()
|
| 93 |
-
}
|
| 94 |
-
return {
|
| 95 |
-
"past_tools": "No tools found",
|
| 96 |
-
"past_queries": "No queries found",
|
| 97 |
-
"past_results": "No results found",
|
| 98 |
-
"past_requests": "No requests found"
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
async def build_prompt(session, intent, request, tools, past_data, summary):
|
| 102 |
-
superset_prompt = await session.read_resource("resource://last_prompt")
|
| 103 |
-
conversation_prompt = await session.read_resource("resource://base_prompt")
|
| 104 |
-
# TODO: add uri's from config
|
| 105 |
-
if intent == "superset_request":
|
| 106 |
-
template = superset_prompt.contents[0].text
|
| 107 |
-
return template.format(
|
| 108 |
-
user_requests=past_data["past_requests"],
|
| 109 |
-
past_tools=past_data["past_tools"],
|
| 110 |
-
last_queries=past_data["past_queries"],
|
| 111 |
-
last_results=past_data["past_results"],
|
| 112 |
-
new_request=request
|
| 113 |
-
)
|
| 114 |
-
else:
|
| 115 |
-
template = conversation_prompt.contents[0].text
|
| 116 |
-
tools_str = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
|
| 117 |
-
return template.format(
|
| 118 |
-
user_requests=past_data["past_requests"],
|
| 119 |
-
past_tools=past_data["past_tools"],
|
| 120 |
-
last_queries=past_data["past_queries"],
|
| 121 |
-
last_results=past_data["past_results"],
|
| 122 |
-
new_request=request,
|
| 123 |
-
tools=tools_str,
|
| 124 |
-
descriptions=summary
|
| 125 |
-
)
|
| 126 |
-
|
| 127 |
-
async def handle_memory_save_or_reset(memory: ConversationMemory, request: str):
|
| 128 |
-
if request.strip().lower() == "stop":
|
| 129 |
-
memory.reset()
|
| 130 |
-
logger.info("Conversation memory reset.")
|
| 131 |
-
else:
|
| 132 |
-
memory.save_memory()
|
| 133 |
-
logger.info("Conversation memory saved.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|