Seth0330 commited on
Commit
e1dabbf
·
verified ·
1 Parent(s): d42961d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -93
app.py CHANGED
@@ -6,13 +6,13 @@ import requests
6
  import json
7
  import re
8
 
9
- # Page config
10
- st.set_page_config(page_title="CSV-Backed AI Agent with Function Calling", layout="wide")
11
 
12
- # Title & image
13
- st.title("CSV-Backed AI Agent with Function Calling")
14
 
15
- # Load API key
16
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
17
  if not OPENAI_API_KEY:
18
  st.error("❌ OPENAI_API_KEY not set in Settings → Secrets.")
@@ -23,7 +23,7 @@ HEADERS = {
23
  "Content-Type": "application/json",
24
  }
25
 
26
- # Sidebar: CSV upload & preview
27
  st.sidebar.header("Upload CSV File")
28
  uploaded_file = st.sidebar.file_uploader("Choose a CSV file", type="csv")
29
 
@@ -42,14 +42,7 @@ else:
42
  if df is not None:
43
  st.markdown(f"**Loaded CSV:** {df.shape[0]} rows × {df.shape[1]} columns")
44
 
45
- # Prompt input
46
- prompt = st.text_area(
47
- "Enter your prompt for the agent",
48
- placeholder="e.g. Which products have price > 100?",
49
- height=150,
50
- )
51
-
52
- # — Functions for function calling
53
  def search_csv(query: str):
54
  try:
55
  result_df = df.query(query)
@@ -65,7 +58,7 @@ def count_unique(column: str):
65
  except Exception as e:
66
  return {"error": f"Column '{column}' not found or not countable. Details: {str(e)}"}
67
 
68
- # Function schemas for OpenAI
69
  function_schema = [
70
  {
71
  "name": "search_csv",
@@ -97,94 +90,122 @@ function_schema = [
97
  }
98
  ]
99
 
100
- # Map function names to Python functions
101
  function_map = {
102
  "search_csv": search_csv,
103
  "count_unique": count_unique,
104
  }
105
 
106
- # Run Agent
107
- if st.button("Run Agent"):
108
- if df is None:
109
- st.error("Please upload a CSV file first.")
110
- elif not prompt.strip():
111
- st.error("Please enter a prompt.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  else:
113
- # Build dynamic system message with column info
114
- columns = ", ".join(df.columns)
115
- system_message = {
116
- "role": "system",
117
- "content": (
118
- f"You are an AI agent helping users analyze a CSV file with these columns: {columns}. "
119
- "If you need to search or filter the CSV, call the 'search_csv' function. "
120
- "If the user wants to know how many unique values are in a column, call the 'count_unique' function. "
121
- "If you use 'search_csv', use Pandas query syntax."
122
- ),
123
- }
124
- messages = [
125
- system_message,
126
- {"role": "user", "content": prompt}
127
- ]
128
-
129
- # First OpenAI call: See if a function call is needed
130
- chat_resp = requests.post(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  "https://api.openai.com/v1/chat/completions",
132
  headers=HEADERS,
133
  json={
134
- "model": "gpt-3.5-turbo-1106", # or "gpt-4-1106-preview" if you have it
135
- "messages": messages,
136
- "functions": function_schema,
137
- "function_call": "auto",
138
  "temperature": 0,
139
- "max_tokens": 1000,
140
  },
141
  timeout=60,
142
  )
143
- chat_resp.raise_for_status()
144
- response_json = chat_resp.json()
145
- msg = response_json["choices"][0]["message"]
146
-
147
- # If OpenAI requests a function call
148
- if msg.get("function_call"):
149
- func_name = msg["function_call"]["name"]
150
- args_json = msg["function_call"]["arguments"]
151
- args = json.loads(args_json)
152
- # Call the appropriate function
153
- if func_name in function_map:
154
- function_result = function_map[func_name](**args)
155
- else:
156
- function_result = {"error": f"Unknown function: {func_name}"}
157
-
158
- # Send function result back for final answer
159
- followup_messages = [
160
- system_message,
161
- {"role": "user", "content": prompt},
162
- {
163
- "role": "function",
164
- "name": func_name,
165
- "content": json.dumps(function_result),
166
- }
167
- ]
168
-
169
- final_resp = requests.post(
170
- "https://api.openai.com/v1/chat/completions",
171
- headers=HEADERS,
172
- json={
173
- "model": "gpt-3.5-turbo-1106",
174
- "messages": followup_messages,
175
- "temperature": 0,
176
- "max_tokens": 1500,
177
- },
178
- timeout=60,
179
- )
180
- final_resp.raise_for_status()
181
- answer = final_resp.json()["choices"][0]["message"]["content"]
182
-
183
- st.subheader("✅ Agent Answer")
184
- st.markdown(answer)
185
- st.subheader("🔎 Function Output")
186
- st.json(function_result)
187
- else:
188
- # No function call: model answered directly
189
- st.subheader("✅ Agent Answer")
190
- st.markdown(msg["content"])
 
6
  import json
7
  import re
8
 
9
+ # --- Page config
10
+ st.set_page_config(page_title="CSV-Backed AI Chat Agent", layout="wide")
11
 
12
+ # --- Title & image
13
+ st.title("CSV-Backed AI Chat Agent")
14
 
15
+ # --- Load API key
16
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
17
  if not OPENAI_API_KEY:
18
  st.error("❌ OPENAI_API_KEY not set in Settings → Secrets.")
 
23
  "Content-Type": "application/json",
24
  }
25
 
26
+ # --- Sidebar: CSV upload & preview
27
  st.sidebar.header("Upload CSV File")
28
  uploaded_file = st.sidebar.file_uploader("Choose a CSV file", type="csv")
29
 
 
42
  if df is not None:
43
  st.markdown(f"**Loaded CSV:** {df.shape[0]} rows × {df.shape[1]} columns")
44
 
45
+ # --- Functions for function calling
 
 
 
 
 
 
 
46
  def search_csv(query: str):
47
  try:
48
  result_df = df.query(query)
 
58
  except Exception as e:
59
  return {"error": f"Column '{column}' not found or not countable. Details: {str(e)}"}
60
 
61
+ # --- Function schemas for OpenAI
62
  function_schema = [
63
  {
64
  "name": "search_csv",
 
90
  }
91
  ]
92
 
93
+ # --- Map function names to Python functions
94
  function_map = {
95
  "search_csv": search_csv,
96
  "count_unique": count_unique,
97
  }
98
 
99
+ # --- Conversation memory: Use Streamlit session state
100
+ if "messages" not in st.session_state:
101
+ # Initial system prompt includes column info (blank at start)
102
+ st.session_state.messages = []
103
+
104
+ # If CSV is loaded, update the system prompt with current columns
105
+ if df is not None:
106
+ columns = ", ".join(df.columns)
107
+ system_message = {
108
+ "role": "system",
109
+ "content": (
110
+ f"You are an AI agent helping users analyze a CSV file with these columns: {columns}. "
111
+ "If you need to search or filter the CSV, call the 'search_csv' function. "
112
+ "If the user wants to know how many unique values are in a column, call the 'count_unique' function. "
113
+ "If you use 'search_csv', use Pandas query syntax."
114
+ ),
115
+ }
116
+ # Ensure the system message is always at the start and up-to-date
117
+ if not st.session_state.messages or st.session_state.messages[0]["role"] != "system":
118
+ st.session_state.messages.insert(0, system_message)
119
  else:
120
+ st.session_state.messages[0] = system_message
121
+
122
+ # --- Chat interface
123
+ st.markdown("### Conversation")
124
+
125
+ # Display chat history (like ChatGPT)
126
+ for i, msg in enumerate(st.session_state.messages[1:]): # Skip the system message for display
127
+ if msg["role"] == "user":
128
+ st.markdown(f"<div style='color: #4F8BF9;'><b>User:</b> {msg['content']}</div>", unsafe_allow_html=True)
129
+ elif msg["role"] == "assistant":
130
+ st.markdown(f"<div style='color: #1C6E4C;'><b>Agent:</b> {msg['content']}</div>", unsafe_allow_html=True)
131
+ elif msg["role"] == "function":
132
+ # Optionally, display function results
133
+ try:
134
+ result = json.loads(msg["content"])
135
+ st.markdown(f"<details><summary><b>Function '{msg['name']}' output:</b></summary><pre>{json.dumps(result, indent=2)}</pre></details>", unsafe_allow_html=True)
136
+ except Exception:
137
+ st.markdown(f"<b>Function '{msg['name']}' output:</b> {msg['content']}", unsafe_allow_html=True)
138
+
139
+ # --- User input box at bottom (like ChatGPT)
140
+ if df is not None:
141
+ user_input = st.text_input("Your message:", key="user_input")
142
+ send = st.button("Send", key="send_btn")
143
+ else:
144
+ user_input = None
145
+ send = False
146
+
147
+ if send and user_input and user_input.strip():
148
+ # Append user's message to conversation
149
+ st.session_state.messages.append({"role": "user", "content": user_input})
150
+
151
+ # --- Compose messages for OpenAI (entire chat history)
152
+ chat_messages = st.session_state.messages.copy()
153
+
154
+ # --- First OpenAI call: Check for function call
155
+ chat_resp = requests.post(
156
+ "https://api.openai.com/v1/chat/completions",
157
+ headers=HEADERS,
158
+ json={
159
+ "model": "gpt-3.5-turbo-1106",
160
+ "messages": chat_messages,
161
+ "functions": function_schema,
162
+ "function_call": "auto",
163
+ "temperature": 0,
164
+ "max_tokens": 1000,
165
+ },
166
+ timeout=60,
167
+ )
168
+ chat_resp.raise_for_status()
169
+ response_json = chat_resp.json()
170
+ msg = response_json["choices"][0]["message"]
171
+
172
+ # --- If OpenAI requests a function call
173
+ if msg.get("function_call"):
174
+ func_name = msg["function_call"]["name"]
175
+ args_json = msg["function_call"]["arguments"]
176
+ args = json.loads(args_json)
177
+ # Call the correct Python function
178
+ if func_name in function_map:
179
+ function_result = function_map[func_name](**args)
180
+ else:
181
+ function_result = {"error": f"Unknown function: {func_name}"}
182
+ # Append function call and output to history
183
+ st.session_state.messages.append({
184
+ "role": "function",
185
+ "name": func_name,
186
+ "content": json.dumps(function_result),
187
+ })
188
+
189
+ # --- Second OpenAI call: Get final answer with function result
190
+ followup_messages = st.session_state.messages.copy()
191
+ final_resp = requests.post(
192
  "https://api.openai.com/v1/chat/completions",
193
  headers=HEADERS,
194
  json={
195
+ "model": "gpt-3.5-turbo-1106",
196
+ "messages": followup_messages,
 
 
197
  "temperature": 0,
198
+ "max_tokens": 1500,
199
  },
200
  timeout=60,
201
  )
202
+ final_resp.raise_for_status()
203
+ answer = final_resp.json()["choices"][0]["message"]["content"]
204
+ # Add assistant's reply to chat
205
+ st.session_state.messages.append({"role": "assistant", "content": answer})
206
+ else:
207
+ # No function call: Just add model's reply
208
+ st.session_state.messages.append({"role": "assistant", "content": msg["content"]})
209
+ # Clear input after sending
210
+ st.experimental_rerun()
211
+