Selcan Yukcu commited on
Commit
64235d1
·
1 Parent(s): 77ec262

feat: langchain memory

Browse files
Files changed (7) hide show
  1. .env.sample +1 -0
  2. .gitignore +2 -0
  3. gradio_app.py +3 -2
  4. langchain_mcp_client.py +124 -0
  5. postgre_mcp_server.py +4 -45
  6. requirements.txt +0 -0
  7. run.sh +6 -0
.env.sample ADDED
@@ -0,0 +1 @@
 
 
1
+ API_KEY = ""
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .idea
2
+ .env
gradio_app.py CHANGED
@@ -4,6 +4,7 @@ import gradio as gr
4
  import asyncio
5
  from postgre_mcp_client import pg_mcp_exec
6
  from postgre_smolagent_clinet import pg_mcp_smolagent_exec
 
7
 
8
  def load_db_configs():
9
  """Load database configurations from configs.yaml"""
@@ -18,12 +19,12 @@ def load_db_configs():
18
  return configs["db_configs"]
19
 
20
  # Async-compatible wrapper
21
- async def run_agent(request):
22
  # configs = load_db_configs()
23
  # final_answer, last_tool_answer, = await pg_mcp_exec(request)
24
  # return final_answer, last_tool_answer
25
 
26
- result = await pg_mcp_smolagent_exec(request)
27
  return result
28
 
29
  # Gradio UI
 
4
  import asyncio
5
  from postgre_mcp_client import pg_mcp_exec
6
  from postgre_smolagent_clinet 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"""
 
19
  return configs["db_configs"]
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)
25
  # return final_answer, last_tool_answer
26
 
27
+ result = await lc_mcp_exec(request, history)
28
  return result
29
 
30
  # Gradio UI
langchain_mcp_client.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_core.prompts import PromptTemplate
9
+ from langchain_core.messages import AIMessage, HumanMessage
10
+ from langchain.chat_models import init_chat_model
11
+ from utils import parse_mcp_output, classify_intent
12
+ import logging
13
+ from dotenv import load_dotenv
14
+ from langgraph.checkpoint.memory import MemorySaver
15
+
16
+
17
+
18
+ logger = logging.getLogger(__name__)
19
+ load_dotenv()
20
+ async def lc_mcp_exec(request: str, history) -> tuple[Any, Any]:
21
+ """
22
+ Execute the full PostgreSQL MCP pipeline: load summary, connect session,
23
+ load memory and tools, build prompt, run agent, update memory.
24
+
25
+ Args:
26
+ request (str): User's request input.
27
+ llm (Any): Language model for reasoning agent.
28
+
29
+ Returns:
30
+ str: Agent response message.
31
+ """
32
+ # TODO: give summary file path from config
33
+ table_summary = load_table_summary("table_summary.txt")
34
+ server_params = get_server_params()
35
+
36
+ api_key = os.getenv("API_KEY")
37
+ llm = init_chat_model(model="gemini-2.0-flash", model_provider="google_genai",
38
+ api_key=api_key)
39
+
40
+ async with stdio_client(server_params) as (read, write):
41
+ async with ClientSession(read, write) as session:
42
+ await session.initialize()
43
+
44
+ tools = await load_and_enrich_tools(session)
45
+
46
+ intent = classify_intent(request)
47
+
48
+ messages = []
49
+ memory = MemorySaver()
50
+ agent = create_react_agent(llm, tools, checkpointer=memory)
51
+
52
+ messages.append(HumanMessage(content=request))
53
+ if history:
54
+ # Clear existing messages and rebuild from history
55
+ messages = []
56
+
57
+ # Process Gradio chat history format
58
+ for msg in history:
59
+ # Gradio format: {'role': 'user/assistant', 'metadata': None, 'content': 'message', 'options': None}
60
+ role = msg.get('role', '')
61
+ content = msg.get('content', '')
62
+
63
+ if role == 'user' and content:
64
+ messages.append(HumanMessage(content=content))
65
+ elif role == 'assistant' and content:
66
+ messages.append(AIMessage(content=content))
67
+
68
+ # Add the current query
69
+ messages.append(HumanMessage(content=request))
70
+
71
+ prompt = await build_prompt(session, intent, request, tools, table_summary, messages)
72
+ config = {"configurable": {"thread_id": "conversation_123"}}
73
+ agent_response = await agent.ainvoke(
74
+ {"messages": prompt},
75
+ config
76
+ )
77
+
78
+ if "messages" in agent_response:
79
+ response = agent_response["messages"][-1].content
80
+ else:
81
+ response = "No response generated"
82
+
83
+ messages.append(AIMessage(content=response))
84
+
85
+
86
+ return response, messages
87
+
88
+
89
+ # ---------------- Helper Functions ---------------- #
90
+
91
+ def load_table_summary(path: str) -> str:
92
+ with open(path, 'r') as file:
93
+ return file.read()
94
+
95
+ def get_server_params() -> StdioServerParameters:
96
+ # TODO: give server params from config
97
+ return StdioServerParameters(
98
+ command="python",
99
+ args=[r"C:\Users\yukcus\Desktop\query_mcp_server\postgre_mcp_server.py"],
100
+ )
101
+
102
+ async def load_and_enrich_tools(session: ClientSession):
103
+ tools = await load_mcp_tools(session)
104
+ return tools
105
+
106
+ async def build_prompt(session, intent, request, tools, summary, messages):
107
+ superset_prompt = await session.read_resource("resource://last_prompt")
108
+ conversation_prompt = await session.read_resource("resource://base_prompt")
109
+ # TODO: add uri's from config
110
+ if intent == "superset_request":
111
+ template = superset_prompt.contents[0].text
112
+ return template.format(
113
+ new_request=request
114
+ )
115
+ else:
116
+ template = conversation_prompt.contents[0].text
117
+ tools_str = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
118
+ return template.format(
119
+ new_request=request,
120
+ tools=tools_str,
121
+ descriptions=summary,
122
+ chat_history = messages
123
+ )
124
+
postgre_mcp_server.py CHANGED
@@ -71,10 +71,6 @@ async def base_prompt_query() -> str:
71
 
72
 
73
  Each tool may also return previews or summaries of table contents to help you better understand the data structure.
74
-
75
- You also have access to **short-term memory**, which stores relevant context from earlier queries. If memory contains the needed information, you **must use it** instead of repeating tool calls with the same input. Avoid redundant tool usage unless:
76
- - The memory is empty, or
77
- - A tool's output is outdated or missing
78
 
79
  ---
80
 
@@ -121,20 +117,6 @@ async def base_prompt_query() -> str:
121
  - Use memory efficiently. Don’t rerun a tool unless necessary.
122
  - If you generate a SQL query, immediately call the **execute_query** tool using that query. Do not delay or wait for user confirmation.
123
 
124
-
125
- ---
126
-
127
- ==========================
128
- # Short-Term Memory
129
- ==========================
130
-
131
- You have access to the following memory from this conversation. Use it if applicable for the current request.
132
-
133
- - Previous user requests: {user_requests}
134
- - Tools used so far: {past_tools}
135
- - Last SQL queries: {last_queries}
136
- - Last result preview: {last_results}
137
-
138
  ---
139
 
140
  ==========================
@@ -189,6 +171,8 @@ Present your final answer using the following structure **exactly**. When necess
189
  **If you do not execute the generated SQL query, this will be the violation of the instructions**
190
  **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.**
191
  ---
 
 
192
  =========================
193
  # New User Request
194
  =========================
@@ -218,8 +202,6 @@ async def last_prompt() -> str:
218
  You are an expert at reading and understanding SQL queries.
219
  Your task is to retrieve the **exact SQL query** that produced a previously seen result, convert the query to the **ANSI SQL query** and return **only the ANSI SQL query** — no explanation, reasoning, or commentary.
220
 
221
- You have access to a **short-term memory**, which stores relevant context from earlier interactions in the current conversation.
222
-
223
  ---
224
 
225
  ==========================
@@ -228,7 +210,7 @@ You have access to a **short-term memory**, which stores relevant context from e
228
 
229
  When a user submits a request (e.g., *"send me that table"*, *"send the last query"*, etc.), follow these steps:
230
 
231
- 1. Identify which previous result the user is referring to, using your short-term memory.
232
  2. Retrieve the corresponding SQL query that produced that result.
233
  3. Convert the SQL query to the ANSI SQL query
234
  3. Return **only** that ANSI SQL query.
@@ -241,35 +223,12 @@ When a user submits a request (e.g., *"send me that table"*, *"send the last que
241
 
242
  - Do **not** ask questions or request clarification.
243
  - Do **not** explain anything to the user.
244
- - Only use the **memory** to determine which query is relevant.
245
  - Respond with the **exact ANSI SQL query only**, formatted cleanly.
246
- - Do **not** guess — only retrieve queries that actually exist in memory.
247
  - If no query fits, respond with: "Query not found."
248
 
249
  ---
250
 
251
- ==========================
252
- # Short-Term Memory
253
- ==========================
254
-
255
- You have access to the following memory from this conversation:
256
-
257
- - **Previous user requests**:
258
- `{user_requests}`
259
-
260
- - **Tools used so far**:
261
- `{past_tools}`
262
-
263
- - **Recent SQL queries**:
264
- `{last_queries}`
265
-
266
- - **Result preview from last query**:
267
- `{last_results}`
268
-
269
- Use this memory to resolve any references in the user's latest request.
270
-
271
- ---
272
-
273
  ==========================
274
  # Reference Conversion Rules for PostgreSQL to ANSI SQL
275
  ==========================
 
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
 
 
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
  ==========================
 
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
  =========================
 
202
  You are an expert at reading and understanding SQL queries.
203
  Your task is to retrieve the **exact SQL query** that produced a previously seen result, convert the query to the **ANSI SQL query** and return **only the ANSI SQL query** — no explanation, reasoning, or commentary.
204
 
 
 
205
  ---
206
 
207
  ==========================
 
210
 
211
  When a user submits a request (e.g., *"send me that table"*, *"send the last query"*, etc.), follow these steps:
212
 
213
+ 1. Identify which previous result the user is referring to.
214
  2. Retrieve the corresponding SQL query that produced that result.
215
  3. Convert the SQL query to the ANSI SQL query
216
  3. Return **only** that ANSI SQL query.
 
223
 
224
  - Do **not** ask questions or request clarification.
225
  - Do **not** explain anything to the user.
 
226
  - Respond with the **exact ANSI SQL query only**, formatted cleanly.
227
+ - Do **not** guess — only retrieve queries.
228
  - If no query fits, respond with: "Query not found."
229
 
230
  ---
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  ==========================
233
  # Reference Conversion Rules for PostgreSQL to ANSI SQL
234
  ==========================
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ
 
run.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Replace 'myenv' with the name of your conda environment
4
+ conda activate myenv
5
+
6
+ python gradio_app.py