danielRamon commited on
Commit
6f8b2d3
·
0 Parent(s):

first production project

Browse files
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ resources/game_readme.gif filter=lfs diff=lfs merge=lfs -text
2
+ *.gif filter=lfs diff=lfs merge=lfs -text
.github/workflows/main.yml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+
6
+ # to run this workflow manually from the Actions tab
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ sync-to-hub:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+ with:
15
+ fetch-depth: 0
16
+ lfs: true
17
+ - name: Push to hub
18
+ env:
19
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
20
+ run: git push https://HF_USERNAME:[email protected]/spaces/danielRamon/cluedo main
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *.pyc
generate_players.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from langchain_ollama import OllamaLLM
3
+ from langchain.output_parsers import PydanticOutputParser
4
+ from langchain_core.prompts import PromptTemplate
5
+ from langchain_core.pydantic_v1 import BaseModel, Field
6
+
7
+ llm = OllamaLLM(model="gemma2", temperature=0.8)
8
+
9
+
10
+ class Person(BaseModel):
11
+ name: str = Field(description="Person's name")
12
+ age: int = Field(description="Person's age")
13
+ nationality: str = Field(description="Person's nationality")
14
+ gender: str = Field(description="Person's gender")
15
+ description: str = Field(description="Person's description")
16
+ occupation: str = Field(description="Person's occupation")
17
+ known_languages: str = Field(
18
+ description="Person's known languages it could be one or more")
19
+
20
+
21
+ class Witness(Person):
22
+ testimony: str = Field(description="Witness's testimony")
23
+
24
+
25
+ class Suspect(Person):
26
+ guilty: bool = Field(description="Suspect's guilt")
27
+ background: str = Field(
28
+ description="Suspect's background during the crime")
29
+
30
+
31
+ class Crime(BaseModel):
32
+ suspects: List[Suspect]
33
+ witnesses: List[Witness]
34
+ event: str = Field(
35
+ description="A long description with all the details about the crime with date, aproximetly time and place")
36
+ date: str = Field(description="Crime date")
37
+ location: str = Field(description="Crime location")
38
+ summary: str = Field(description="Brief description of the crime")
39
+ motive: str = Field(description="Motive of the crime")
40
+
41
+
42
+ parser = PydanticOutputParser(pydantic_object=Crime)
43
+
44
+ prompt = PromptTemplate(
45
+ template="Create a crime in {country} in all the text should be in the language of this country, and a list with {suspects} suspects and {witnesses} where only one of the suspects is guilty. \n {format_instructions}",
46
+ input_variables=["suspects", "witnesses"],
47
+ partial_variables={
48
+ "format_instructions": parser.get_format_instructions()},
49
+ )
50
+
51
+
52
+ def generate_crime(suspects, witnesses, country):
53
+ chain = prompt | llm | parser
54
+ output = chain.invoke(
55
+ {"suspects": suspects, "witnesses": witnesses, "country": country})
56
+ return output
readme.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Cluedo
2
+
3
+ ### Detective Game with Streamlit and Ollama: Solve the Crime
4
+
5
+ **Description:**
6
+
7
+ Dive into an exciting world of mystery and crime-solving! This interactive game puts you in the shoes of a clever detective who must unravel a case. Interrogate suspects and witnesses, analyze clues, and use all your intelligence to discover the culprit.
8
+
9
+ **Technologies:**
10
+
11
+ * **Streamlit:** Streamlit's intuitive and user-friendly interface allows for a smooth and engaging gameplay experience.
12
+ * **Ollama:** The large language model service Ollama, using the "Gemma2" model, generates cases and provides intelligent and realistic responses to the player's questions, creating an immersive interrogation experience.
13
+
14
+ **Requirements:**
15
+
16
+ * **Ollama:** The Ollama large language model service must be installed and running on its default port `11434`, and the `Gemma2` model should be downloaded.
17
+
18
+ **How to Play:**
19
+ 1. Clone the repository:
20
+ ```bash
21
+ git clone https://github.com/danielRamon/cluedo.git
22
+ ```
23
+ 2. Install the dependencies:
24
+ ```bash
25
+ cd cluedo
26
+ pip install -r requirements.txt
27
+ ```
28
+ 3. Start the game:
29
+ ```bash
30
+ streamlit run streamlit_app.py
31
+ ```
32
+
33
+ 4. Play
34
+
35
+ ![Intro](./resources/intro_readme.gif)
36
+ ![Game](./resources/game_readme.gif)
37
+
38
+ **Contributions:**
39
+
40
+ Contributions are welcome! You can help improve the game in the following ways:
41
+
42
+ * **AI Enhancement:** Explore ways to improve Ollama's ability to generate more natural and convincing responses, as well as using other LLM services.
43
+ * **New Features:** Implement new features such as a scoring system, multiple difficulty levels, or the ability to save and load games.
44
+ * **Create a Cloud Service:** You can deploy the game on a server so that others without technical knowledge can use it.
45
+
46
+ **Project Structure:**
47
+
48
+ * `streamlit_app.py`: The main file containing the game logic and the Streamlit user interface.
49
+ * `generate_players.py`: Auxiliary code for creating stories and characters at the start of the game.
50
+ * `resources`: Folder containing images for the end of the game.
51
+ * `requirements.txt`: File listing the project's dependencies.
52
+
53
+ **Notes:**
54
+
55
+ This project is intended for self-learning purposes, so any constructive feedback is welcome.
56
+
57
+ **Have fun solving crimes!**
58
+
59
+ **Daniel Ramón Gallardo**
60
+
61
+ **[LinkedIn](https://www.linkedin.com/in/daniel-ramon-gallardo/)**
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ langchain
2
+ langchain-community
3
+ langchain-ollama
4
+ streamlit
5
+
resources/looser.webp ADDED
resources/winner.webp ADDED
streamlit_app.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.chat_message_histories import StreamlitChatMessageHistory
2
+ from langchain_community.callbacks import StreamlitCallbackHandler
3
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
4
+ from langchain_core.runnables.history import RunnableWithMessageHistory
5
+ from langchain_ollama import ChatOllama
6
+
7
+
8
+ import streamlit as st
9
+
10
+ from generate_players import generate_crime
11
+
12
+ # Set up webpage UI
13
+ st.set_page_config(page_title="Detective", page_icon="🤖")
14
+
15
+ # Initialize variables
16
+ num_suspects = 2
17
+ num_witnesses = 0
18
+ country = "Spain"
19
+ winner = False
20
+ crime = None
21
+
22
+
23
+ def start():
24
+ crime = generate_crime(num_suspects, num_witnesses, country)
25
+ st.session_state.crime = crime
26
+ st.session_state.clicked = True
27
+
28
+
29
+ if 'clicked' not in st.session_state or st.session_state.clicked is not True:
30
+ st.session_state.resolved = False
31
+ st.session_state.clicked = False
32
+ st.write("<h1 style='text-align: center;'>🕵️‍♂️ Detective Game</h1>",
33
+ unsafe_allow_html=True)
34
+ num_suspects = st.slider(label="Suspects", min_value=2, max_value=10)
35
+ num_witnesses = st.slider(label="Witnesses", min_value=0, max_value=10)
36
+ country = st.text_input(label="Country", value=country)
37
+ st.button('New game', on_click=start)
38
+
39
+ if st.session_state.clicked and not st.session_state.resolved:
40
+ st.sidebar.title("Notebook")
41
+ person_type = st.sidebar.selectbox(
42
+ "Persons", ["Suspect", "Witness"], placeholder="Choose persons")
43
+ if person_type == "Suspect":
44
+
45
+ all_suspects = [
46
+ suspect.name for suspect in st.session_state.crime.suspects]
47
+ my_person = st.sidebar.selectbox(
48
+ "Suspect", all_suspects, placeholder="Choose your suspect")
49
+ suspect_current = st.session_state.crime.suspects[all_suspects.index(
50
+ my_person)]
51
+ st.write(f"<h1 style='text-align: center;'> 🧐 {person_type}: {suspect_current.name}</h1>",
52
+ unsafe_allow_html=True)
53
+ st.sidebar.markdown(f"""
54
+ Age: {str(suspect_current.age)}\n
55
+ Nationality: {suspect_current.nationality}\n
56
+ Gender: {suspect_current.gender}\n
57
+ Occupation: {suspect_current.occupation}\n
58
+ Description: {suspect_current.description}\n
59
+ """, )
60
+ if st.sidebar.button("Accuse"):
61
+ st.session_state.resolved = True
62
+ if suspect_current.guilty:
63
+ st.balloons()
64
+ winner = True
65
+ st.rerun()
66
+
67
+ elif person_type == "Witness":
68
+ all_witnesses = [
69
+ witness.name for witness in st.session_state.crime.witnesses]
70
+ my_person = st.sidebar.selectbox(
71
+ "Witness", all_witnesses, placeholder="Choose your witness")
72
+ suspect_current = st.session_state.crime.witnesses[all_witnesses.index(
73
+ my_person)]
74
+ st.write(f"<h1 style='text-align: center;'>👀 {person_type}: {suspect_current.name}</h1>",
75
+ unsafe_allow_html=True)
76
+ # Set up memory
77
+ st.session_state.crime.event
78
+ msgs = StreamlitChatMessageHistory(key=my_person)
79
+ if len(msgs.messages) == 0:
80
+ msgs.add_ai_message("...")
81
+
82
+ system = """
83
+ You are a detective game where there are several characters and only one of them is the real culprit.
84
+ The user will have to talk to the different characters in the game to solve the case and find out who the real culprit is.
85
+ At this moment you are:
86
+ {person}
87
+
88
+ You should be very realistic, so if the user talk in any language that you sont' know tell that you don't understand and tell the languages you know but don't answer, in other case talk in the same language than the user.
89
+
90
+ You can only respond as if you were this person and never as an AI.
91
+
92
+ You cannot accuse the other suspect directly.
93
+
94
+ Only the suspects can lie at some point in other.
95
+ """
96
+ # Set up model
97
+ my_chat = ChatOllama(model="gemma2", temperature=0.5)
98
+
99
+ prompt = ChatPromptTemplate.from_messages(
100
+ [
101
+ ("system", system),
102
+ MessagesPlaceholder(variable_name="history"),
103
+ ("human", "{question}"),
104
+ ]
105
+ )
106
+ chain = prompt | my_chat
107
+ chain_with_history = RunnableWithMessageHistory(
108
+ chain,
109
+ lambda session_id: msgs,
110
+ input_messages_key="question",
111
+ history_messages_key="history"
112
+ )
113
+
114
+ # Render current messages from StreamlitChatMessageHistory
115
+ for msg in msgs.messages:
116
+ st.chat_message(msg.type).write(msg.content)
117
+
118
+ # If user inputs a new prompt, generate and draw a new response
119
+ if prompt := st.chat_input():
120
+ st.chat_message("human").write(prompt)
121
+ # Note: new messages are saved to history automatically by Langchain during run
122
+
123
+ # agent response
124
+ st_cb = StreamlitCallbackHandler(st.container())
125
+ # print(suspect_current)
126
+ response = chain_with_history.invoke(
127
+ {"question": prompt, "person": suspect_current}, {"configurable": {"session_id": "any"}, "callbacks": [st_cb]})
128
+ st.chat_message("ai").write(response.content)
129
+ elif st.session_state.resolved:
130
+ culprit = [
131
+ suspect for suspect in st.session_state.crime.suspects if suspect.guilty][0]
132
+ if winner:
133
+ multi = f'''**YOU WIN**
134
+
135
+
136
+ The culprit was: {culprit.name}
137
+
138
+
139
+ Reason: {culprit.background}
140
+ '''
141
+ st.markdown(multi,
142
+ unsafe_allow_html=True)
143
+ st.image("resources/winner.webp")
144
+ else:
145
+ multi = f'''**YOU WIN (refresh to play again)**
146
+ The culprit was: {culprit.name}
147
+ Reason: {culprit.background}
148
+ '''
149
+ st.markdown(
150
+ f"""
151
+ **YOU FAILED (refresh to try again)**
152
+ The culprit: {culprit.name}
153
+ Reason: {culprit.background}
154
+ """,
155
+ unsafe_allow_html=True)
156
+ st.image("resources/looser.webp")