karl0706 commited on
Commit
5b658b5
Β·
1 Parent(s): 5e3a323

first push

Browse files
Files changed (5) hide show
  1. Dockerfile +10 -12
  2. README.md +0 -19
  3. app.py +238 -0
  4. requirements.txt +6 -2
  5. src/streamlit_app.py +0 -40
Dockerfile CHANGED
@@ -1,20 +1,18 @@
1
- FROM python:3.13.5-slim
2
 
3
  WORKDIR /app
4
 
5
- RUN apt-get update && apt-get install -y \
6
- build-essential \
7
- curl \
8
- git \
9
- && rm -rf /var/lib/apt/lists/*
10
 
11
- COPY requirements.txt ./
12
- COPY src/ ./src/
13
 
14
- RUN pip3 install -r requirements.txt
 
15
 
 
16
  EXPOSE 8501
17
 
18
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
-
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
1
+ FROM python:3.10-slim
2
 
3
  WORKDIR /app
4
 
5
+ # Copy dependency file
6
+ COPY requirements.txt .
 
 
 
7
 
8
+ # Install dependencies
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
 
11
+ # Copy the rest of the project
12
+ COPY . .
13
 
14
+ # Expose Streamlit default port
15
  EXPOSE 8501
16
 
17
+ # Run Streamlit
18
+ CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
README.md DELETED
@@ -1,19 +0,0 @@
1
- ---
2
- title: GameBot
3
- emoji: πŸš€
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: GameBot is an AI assistant that helps with board game rules
12
- ---
13
-
14
- # Welcome to Streamlit!
15
-
16
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
17
-
18
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
19
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import warnings
4
+ from dotenv import load_dotenv
5
+ from google import genai
6
+ from typing import Annotated, Literal
7
+ from typing_extensions import TypedDict
8
+ from langgraph.graph.message import add_messages
9
+ from langgraph.graph import StateGraph, START, END
10
+ from langgraph.prebuilt import ToolNode
11
+ from langchain_google_genai import ChatGoogleGenerativeAI
12
+ from langchain_core.tools import tool
13
+ from langchain_core.messages import HumanMessage, AIMessage
14
+ from google.genai import types
15
+
16
+ # Suppress warnings
17
+ warnings.filterwarnings('ignore')
18
+
19
+ # Load environment variables
20
+ load_dotenv()
21
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
22
+
23
+ if GOOGLE_API_KEY:
24
+ os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY
25
+
26
+ # Initialize Google client (will be done after API key check)
27
+ client = None
28
+
29
+ # Define State
30
+ class GameState(TypedDict):
31
+ messages: Annotated[list, add_messages]
32
+ order: list[str]
33
+ finished: bool
34
+
35
+ # System instruction
36
+ GAMEBOT_SYSINT = (
37
+ "system",
38
+ "You are GameBot, an interactive board game expert system. A human will talk to you about "
39
+ "various board games and you will answer any questions about game rules, strategies, and history "
40
+ "(and only about board games - no off-topic discussion, but you can chat about the games and their history).\n\n"
41
+
42
+ "You have access to the function `get_list_game()` to list available games.\n"
43
+ "If a user asks about a game not in the list, do not tell them it's unavailable. "
44
+ "Instead, print the game_name and call the `search_game_online(game_name)` tool to search online and help them.\n"
45
+
46
+ "If the game is in your list (for example: 'Chess'), feel free to answer detailed questions about its rules, pieces, or strategy using your own knowledge.\n\n"
47
+
48
+ "Be proactive and helpful. Respond in the user's language.\n"
49
+ "Keep your responses concise and friendly.\n\n"
50
+
51
+ "If any of the tools are unavailable, you can break the fourth wall and tell the user "
52
+ "that they have not implemented them yet and should keep reading to do so."
53
+ )
54
+
55
+ WELCOME_MSG = "Welcome to the GameBot expert system! How may I assist you with board games today?"
56
+
57
+ # Define tools
58
+ @tool
59
+ def get_list_game() -> str:
60
+ """Provide the latest up-to-date menu."""
61
+ return """
62
+ Game:
63
+ Monopoly
64
+ Scrabble
65
+ Chess
66
+ Cluedo
67
+ Uno
68
+ Mahjong
69
+ Mikado
70
+ """
71
+
72
+ @tool
73
+ def search_game_online(game_name: str) -> str:
74
+ """Search online information about a board game if not found in the local list."""
75
+ prompt = f"Can you give me a short description of the board game '{game_name}'?"
76
+ response = client.models.generate_content(
77
+ model='gemini-2.0-flash',
78
+ contents=prompt,
79
+ config=types.GenerateContentConfig(
80
+ tools=[types.Tool(google_search=types.GoogleSearch())],
81
+ ),
82
+ )
83
+ return response.candidates[0].content.parts[0].text
84
+
85
+ # Initialize LLM and tools
86
+ @st.cache_resource
87
+ def initialize_graph():
88
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash")
89
+ tools = [get_list_game, search_game_online]
90
+ tool_node = ToolNode(tools)
91
+ llm_with_tools = llm.bind_tools(tools)
92
+
93
+ def maybe_route_to_tools(state: GameState) -> Literal["tools", END]:
94
+ if not (msgs := state.get("messages", [])):
95
+ raise ValueError(f"No messages found when parsing state: {state}")
96
+ msg = msgs[-1]
97
+ if hasattr(msg, "tool_calls") and len(msg.tool_calls) > 0:
98
+ return "tools"
99
+ else:
100
+ return END
101
+
102
+ def chatbot_with_tools(state: GameState) -> GameState:
103
+ defaults = {"order": [], "finished": False}
104
+ if state["messages"]:
105
+ new_output = llm_with_tools.invoke([GAMEBOT_SYSINT] + state["messages"])
106
+ else:
107
+ new_output = AIMessage(content=WELCOME_MSG)
108
+ return defaults | state | {"messages": [new_output]}
109
+
110
+ graph_builder = StateGraph(GameState)
111
+ graph_builder.add_node("chatbot", chatbot_with_tools)
112
+ graph_builder.add_node("tools", tool_node)
113
+
114
+ graph_builder.add_conditional_edges("chatbot", maybe_route_to_tools)
115
+ graph_builder.add_edge("tools", "chatbot")
116
+ graph_builder.add_edge(START, "chatbot")
117
+
118
+ return graph_builder.compile()
119
+
120
+ # Page config
121
+ st.set_page_config(
122
+ page_title="GameBot - Board Game Expert",
123
+ page_icon="🎲",
124
+ layout="centered"
125
+ )
126
+
127
+ # Custom CSS
128
+ st.markdown("""
129
+ <style>
130
+ .main {
131
+ background-color: #f0f2f6;
132
+ }
133
+ .stChatMessage {
134
+ background-color: blue;
135
+ border-radius: 10px;
136
+ padding: 10px;
137
+ margin: 5px 0;
138
+ }
139
+ </style>
140
+ """, unsafe_allow_html=True)
141
+
142
+ # Header
143
+ st.title("🎲 GameBot - Board Game Expert")
144
+ st.markdown("*Your AI assistant for board game rules, strategies, and history*")
145
+
146
+ # Check for API key early
147
+ if not GOOGLE_API_KEY:
148
+ st.error("⚠️ Please set your GOOGLE_API_KEY in the .env file")
149
+ st.info("Create a `.env` file with: `GOOGLE_API_KEY=your_api_key_here`")
150
+ st.stop()
151
+
152
+ # Initialize client after API key check
153
+ client = genai.Client(api_key=GOOGLE_API_KEY)
154
+
155
+ # Initialize session state
156
+ if "messages" not in st.session_state:
157
+ st.session_state.messages = []
158
+ st.session_state.graph_state = {"messages": [], "order": [], "finished": False}
159
+
160
+ # Initialize graph
161
+ try:
162
+ graph = initialize_graph()
163
+ except Exception as e:
164
+ st.error(f"Error initializing the chatbot: {str(e)}")
165
+ st.stop()
166
+
167
+ # Sidebar
168
+ with st.sidebar:
169
+ st.header("ℹ️ About GameBot")
170
+ st.markdown("""
171
+ GameBot can help you with:
172
+ - πŸ“‹ List of available games
173
+ - πŸ“– Game rules and instructions
174
+ - 🎯 Strategies and tips
175
+ - πŸ” Search for games online
176
+
177
+ **Available Games:**
178
+ - Monopoly
179
+ - Scrabble
180
+ - Chess
181
+ - Cluedo
182
+ - Uno
183
+ - Mahjong
184
+ - Mikado
185
+ """)
186
+
187
+ if st.button("πŸ”„ Clear Chat", use_container_width=True):
188
+ st.session_state.messages = []
189
+ st.session_state.graph_state = {"messages": [], "order": [], "finished": False}
190
+ st.rerun()
191
+
192
+ # Display welcome message if no messages
193
+ if len(st.session_state.messages) == 0:
194
+ with st.chat_message("assistant", avatar="🎲"):
195
+ st.markdown(WELCOME_MSG)
196
+
197
+ # Display chat messages
198
+ for message in st.session_state.messages:
199
+ avatar = "🎲" if message["role"] == "assistant" else "πŸ‘€"
200
+ with st.chat_message(message["role"], avatar=avatar):
201
+ st.markdown(message["content"])
202
+
203
+ # Chat input
204
+ if prompt := st.chat_input("Ask me about board games..."):
205
+ # Add user message to chat history
206
+ st.session_state.messages.append({"role": "user", "content": prompt})
207
+ with st.chat_message("user", avatar="πŸ‘€"):
208
+ st.markdown(prompt)
209
+
210
+ # Add user message to graph state
211
+ st.session_state.graph_state["messages"].append(HumanMessage(content=prompt))
212
+
213
+ # Get bot response
214
+ with st.chat_message("assistant", avatar="🎲"):
215
+ with st.spinner("Thinking..."):
216
+ try:
217
+ config = {"recursion_limit": 100}
218
+ result = graph.invoke(st.session_state.graph_state, config)
219
+ st.session_state.graph_state = result
220
+
221
+ # Get the last AI message
222
+ last_message = result["messages"][-1]
223
+ response = last_message.content
224
+
225
+ st.markdown(response)
226
+ st.session_state.messages.append({"role": "assistant", "content": response})
227
+
228
+ except Exception as e:
229
+ error_msg = f"Sorry, I encountered an error: {str(e)}"
230
+ st.error(error_msg)
231
+ st.session_state.messages.append({"role": "assistant", "content": error_msg})
232
+
233
+ # Footer
234
+ st.markdown("---")
235
+ st.markdown(
236
+ "<div style='text-align: center; color: gray;'> First try on AI agent ! Have fun testing this application.😏</div>",
237
+ unsafe_allow_html=True
238
+ )
requirements.txt CHANGED
@@ -1,3 +1,7 @@
1
- altair
2
- pandas
 
 
 
 
3
  streamlit
 
1
+ python-dotenv
2
+ # Install langgraph and the packages used in this lab.
3
+ langgraph==0.3.21
4
+ langchain-google-genai==2.1.2
5
+ langgraph-prebuilt==0.1.7
6
+ google-genai
7
  streamlit
src/streamlit_app.py DELETED
@@ -1,40 +0,0 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
- import streamlit as st
5
-
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))