{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"},{"sourceId":3136,"databundleVersionId":26502,"sourceType":"competition"},{"sourceId":5407,"databundleVersionId":868283,"sourceType":"competition"},{"sourceId":61247,"databundleVersionId":8550470,"sourceType":"competition"},{"sourceId":33551,"sourceType":"modelInstanceVersion","modelInstanceId":28083}],"dockerImageVersionId":30733,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# This Introduction is for beginners\n## what is required in this competition?\n\n**TL,DR: create a model able to play 20 questions game**\n\nImagine you have to build a robot that can talk, walk and dance, your job is to make sure that the robot does each role properly, and make it possible to the user to pick any role he wants and run it without causing interference with the others (you can add 3 buttons for example)\n\nthis is exactly what's expected from you in this competition, you have to build an agent/robot(llm model) that can play 3 roles: ask, answer and guess,\n\nif you had read the overview section [here](https://www.kaggle.com/competitions/llm-20-questions/overview), it's mentioned that, the agent you submit will play with another agent submitted by another participant, in this duo, your agent can either active the roles of the asker and guesser, or it can be the answerer, the role and the binomes are chosen by the environment behind the scene based on some conditions. and you don't have to worry about that.\n\nall you have to do is to make sure that, when the environment decides to play the answerer role, your agent should answer with yes or no, any other answer will make you lose the game.\n\n\nfind more details [here](https://www.kaggle.com/competitions/llm-20-questions/overview) \n\n\n## how to submit?\n\nyour code must be in one file named main.py , the file should contain the agent/robot code as well as a mandatory function that takes obs and cfg as parameters, this function is used by the environment behind the scenes to run you agent.\n\nin our code(very simple compared to other notebooks), we will name this function \"agent\", and put the other logic in a class named \"robot\"\n\nsince the environment runs the code in a offline mode and won't have access to /kaggle/input directory, you have to load/copy the packages you need  as well as the main.py file in one tar.gz file.\n\nbehind the scenes, the tar.gz file will be decompressed under the \"/kaggle_simulations/agent/\" folder, this is why we add code that adjusts the paths according to the running environment (see below).\n\nyou can test the code locally, see [local test](#lc) section below","metadata":{}},{"cell_type":"code","source":"%%bash\nmkdir -p /kaggle/working/submission","metadata":{"execution":{"iopub.status.busy":"2024-07-12T08:18:49.607400Z","iopub.execute_input":"2024-07-12T08:18:49.607666Z","iopub.status.idle":"2024-07-12T08:18:49.625303Z","shell.execute_reply.started":"2024-07-12T08:18:49.607640Z","shell.execute_reply":"2024-07-12T08:18:49.624625Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%writefile -a submission/main.py\n\nfrom transformers import AutoTokenizer, AutoModelForCausalLM\nimport torch\nfrom kaggle_secrets import UserSecretsClient\nimport os\nimport sys\nimport shutil\n\ntorch.backends.cuda.enable_mem_efficient_sdp(False)\ntorch.backends.cuda.enable_flash_sdp(False)\n\n\n\nKAGGLE_AGENT_PATH = \"/kaggle_simulations/agent/\"\nif os.path.exists(KAGGLE_AGENT_PATH):\n    model_id = os.path.join(KAGGLE_AGENT_PATH, \"1\")\nelse:\n    model_id = \"/kaggle/input/llama-3/transformers/8b-chat-hf/1\"\n\n\ntokenizer = AutoTokenizer.from_pretrained(model_id)\nmodel = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16, device_map=\"auto\")\nid_eot = tokenizer.convert_tokens_to_ids([\"<|eot_id|>\"])[0]\n\n\ndef generate_answer(template):\n    inp_ids = tokenizer(template, return_tensors=\"pt\").to(\"cuda\")\n    out_ids = model.generate(**inp_ids,max_new_tokens=15).squeeze()\n    start_gen = inp_ids.input_ids.shape[1]\n    out_ids = out_ids[start_gen:]\n    if id_eot in out_ids:\n        stop = out_ids.tolist().index(id_eot)\n        out = tokenizer.decode(out_ids[:stop])\n    else:\n        out = tokenizer.decode(out_ids)\n    return out\n    \n\nclass Robot:\n    def __init__(self):\n        pass\n    \n    def on(self, mode, obs):\n        assert mode in [\"asking\", \"guessing\", \"answering\"], \"mode can only take one of these values: asking, answering, guessing\"\n        if mode == \"asking\":\n            #launch the asker role\n            output = self.asker(obs)\n        if mode == \"answering\":\n            #launch the answerer role\n            output = self.answerer(obs)\n            if \"yes\" in output.lower():\n                output = \"yes\"\n            elif \"no\" in output.lower():\n                output = \"no\"   \n            if (\"yes\" not in output.lower() and \"no\" not in output.lower()):\n                output = \"yes\"\n        if mode == \"guessing\":\n            #launch the guesser role\n            output = self.asker(obs)\n        return output\n    \n    \n    def asker(self, obs):\n        sys_prompt = \"\"\"You are a helpful AI assistant, and your are very smart in playing 20 questions game,\n        the user is going to think of a word, it can be only one of the following 3 categories:\n        1. a place\n        2. a person\n        3. a thing\n        So focus your area of search on these options. and give smart questions that narrows down the search space\\n\"\"\"\n    \n        if obs.turnType ==\"ask\":\n            ask_prompt = sys_prompt + \"\"\"your role is to find the word by asking him up to 20 questions, your questions to be valid must have only a 'yes' or 'no' answer.\n            to help you, here's an example of how it should work assuming that the keyword is Morocco:\n            examle:\n            <you: is it a place?\n            user: yes\n            you: is it in europe?\n            user: no\n            you: is it in africa?\n            user: yes\n            you: do most people living there have dark skin?\n            user: no\n            user: is it a country name starting by m ?\n            you: yes\n            you: is it Morocco?\n            user: yes.>\n\n            the user has chosen the word, ask your first question!\n            please be short and not verbose, give only one question, no extra word!\"\"\"\n            chat_template = f\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\\n\\n{ask_prompt}<|eot_id|>\"\"\"\n            chat_template += \"<|start_header_id|>assistant<|end_header_id|>\\n\\n\"\n            if len(obs.questions)>=1:\n                for q, a in zip(obs.questions, obs.answers):\n                    chat_template += f\"{q}<|eot_id|><|start_header_id|>user<|end_header_id|>\\n\\n\"\n                    chat_template += f\"{a}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\\n\\n\"\n                    \n        elif obs.turnType == \"guess\":\n            conv = \"\"\n            for q, a in zip(obs.questions, obs.answers):\n                conv += f\"\"\"Question: {q}\\nAnswer: {a}\\n\"\"\"\n            guess_prompt =  sys_prompt + f\"\"\"so far, the current state of the game is as following:\\n{conv}\n            based on the conversation, can you guess the word, please give only the word, no verbosity around\"\"\"\n            chat_template = f\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\\n\\n{guess_prompt}<|eot_id|>\"\"\"\n            chat_template += \"<|start_header_id|>assistant<|end_header_id|>\\n\\n\"\n                \n        output = generate_answer(chat_template)        \n        return output\n        \n        \n        \n    def answerer(self, obs):\n        sys_prompt = f\"\"\"you are a helpful AI assistant, and your are very smart in playing 20 questions game,\n        the role of the user is to guess the word by asking you up to 20 questions, your answers to be valid must be a 'yes' or 'no', any other answer is invalid and you lose the game.\n        Know that the user will always guess a word belonging to one of the following 3 categories:\n        1. a place\n        2. a person\n        3. a thing\n        so make sure you understand the user's question and you understand the keyword you're playig on.\n        for now the word that the user should guess is: \"{obs.keyword}\", it is of category \"{obs.category}\",\n        to help you, here's an example of how it should work assuming that the keyword is Morocco in the category \"place\":\n        examle:\n        <user: is it a place?\n        you: yes\n        user: is it in europe?\n        you: no\n        user: is it in africa?\n        you: yes\n        user: do most people living there have dark skin?\n        you: no\n        user: is it a country name starting by m ?\n        you: yes\n        user: is it Morocco?\n        you: yes.>\"\"\"\n        \n        chat_template = f\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\\n\\n{sys_prompt}<|eot_id|>\"\"\"\n        chat_template += \"<|start_header_id|>user<|end_header_id|>\\n\\n\"\n        chat_template += f\"{obs.questions[0]}<|eot_id|>\"\n        chat_template += \"<|start_header_id|>assistant<|end_header_id|>\\n\\n\"\n        if len(obs.answers)>=1:\n            for q, a in zip(obs.questions[1:], obs.answers):\n                chat_template += f\"{a}<|eot_id|><|start_header_id|>user<|end_header_id|>\\n\\n\"\n                chat_template += f\"{q}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\\n\\n\"\n        output = generate_answer(chat_template)\n        return output\n    \n    \nrobot = Robot()\n\n\ndef agent(obs, cfg):\n    \n    if obs.turnType ==\"ask\":\n        response = robot.on(mode = \"asking\", obs = obs)\n        \n    elif obs.turnType ==\"guess\":\n        response = robot.on(mode = \"guessing\", obs = obs)\n        \n    elif obs.turnType ==\"answer\":\n        response = robot.on(mode = \"answering\", obs = obs)\n        \n    if response == None or len(response)<=1:\n        response = \"yes\"\n        \n    return response","metadata":{"execution":{"iopub.status.busy":"2024-07-12T08:20:44.190212Z","iopub.execute_input":"2024-07-12T08:20:44.190542Z","iopub.status.idle":"2024-07-12T08:20:44.199912Z","shell.execute_reply.started":"2024-07-12T08:20:44.190518Z","shell.execute_reply":"2024-07-12T08:20:44.198998Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# local Test <a id=\"lc\"></a>\n\nto test locally, first comment the \"%%writefile -a submission/main.py\" in the cell above and run the cell.\nthen uncomment ad run the following cells\n","metadata":{}},{"cell_type":"code","source":"# %%time\n\n# from kaggle_environments import make\n# env = make(\"llm_20_questions\", debug=True)\n# game_output = env.run(agents=[agent, agent, agent, agent])","metadata":{"execution":{"iopub.status.busy":"2024-06-25T21:31:20.537013Z","iopub.execute_input":"2024-06-25T21:31:20.53805Z","iopub.status.idle":"2024-06-25T21:31:21.897103Z","shell.execute_reply.started":"2024-06-25T21:31:20.538008Z","shell.execute_reply":"2024-06-25T21:31:21.896084Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# env.render(mode=\"ipython\", width=600, height=500)","metadata":{"execution":{"iopub.status.busy":"2024-06-25T21:42:24.805663Z","iopub.execute_input":"2024-06-25T21:42:24.806653Z","iopub.status.idle":"2024-06-25T21:42:24.873565Z","shell.execute_reply.started":"2024-06-25T21:42:24.806602Z","shell.execute_reply":"2024-06-25T21:42:24.872711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# submission file","metadata":{}},{"cell_type":"code","source":"!apt install pigz pv > /dev/null","metadata":{"execution":{"iopub.status.busy":"2024-06-25T16:14:28.204763Z","iopub.execute_input":"2024-06-25T16:14:28.205431Z","iopub.status.idle":"2024-06-25T16:14:31.046479Z","shell.execute_reply.started":"2024-06-25T16:14:28.205393Z","shell.execute_reply":"2024-06-25T16:14:31.045488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!tar --use-compress-program='pigz --fast --recursive | pv' -cf submission.tar.gz -C /kaggle/input/llama-3/transformers/8b-chat-hf . -C /kaggle/working/submission .","metadata":{"execution":{"iopub.status.busy":"2024-06-25T16:14:47.110649Z","iopub.execute_input":"2024-06-25T16:14:47.111039Z","iopub.status.idle":"2024-06-25T16:20:02.504823Z","shell.execute_reply.started":"2024-06-25T16:14:47.110992Z","shell.execute_reply":"2024-06-25T16:20:02.503606Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# to see what's inside tar.gz file\n\n# import tarfile\n# tar = tarfile.open(\"/kaggle/working/submission.tar.gz\")\n# for file in tar.getmembers():\n#     print(file.name)","metadata":{"execution":{"iopub.status.busy":"2024-06-25T16:20:02.506741Z","iopub.execute_input":"2024-06-25T16:20:02.507126Z","iopub.status.idle":"2024-06-25T16:22:51.972507Z","shell.execute_reply.started":"2024-06-25T16:20:02.507093Z","shell.execute_reply":"2024-06-25T16:22:51.971476Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]}]}