async def _chat_with_ai(
            self, prompt, user_input, token_limit
    ):
        """Interact with the OpenAI API, sending the prompt, user input, message history,
        and permanent memory."""
        while True:
            try:
                model = CFG.llm_model
                # Reserve 1000 tokens for the response

                send_token_limit = token_limit - 1000

                (
                    next_message_to_add_index,
                    current_tokens_used,
                    insertion_index,
                    current_context,
                ) = await self._generate_context(prompt, model)

                current_tokens_used += await token_counter.count_message_tokens(
                    [{"role": "user", "content": user_input}], model
                )  # Account for user input (appended later)

                # TODO: OpenAI does not say how to count function tokens, we use this method to roughly get the tokens count
                #   It's result looks much larger than OpenAI's result
                current_tokens_used += await token_counter.count_message_tokens(
                    [{"role": "user", "content": json.dumps(moduleRegistry.to_json_schema())}], model
                )

                while next_message_to_add_index >= 0:
                    # print (f"CURRENT TOKENS USED: {current_tokens_used}")
                    tokens_to_add = await self._get_history_message_tokens(next_message_to_add_index, model)
                    if current_tokens_used + tokens_to_add > send_token_limit:
                        break

                    message_to_add = self._full_message_history[next_message_to_add_index]
                    # Add the most recent message to the start of the current context,
                    #  after the two system prompts.
                    current_context.insert(insertion_index, message_to_add)

                    # Count the currently used tokens
                    current_tokens_used += tokens_to_add

                    # Move to the next most recent message in the full message history
                    next_message_to_add_index -= 1

                # Append user input, the length of this is accounted for above
                current_context.extend([{"role": "user", "content": user_input}])

                # Calculate remaining tokens
                tokens_remaining = token_limit - current_tokens_used

                assert tokens_remaining >= 0

                async def on_single_chat_timeout(will_retry):
                    await self._caller_context.push_notification(
                        f'Thinking timeout{", retry" if will_retry else ", give up"}.')


                reply_type, assistant_reply = await gpt.acreate_chat_completion(
                    model=model,
                    messages=current_context,
                    temperature=CFG.temperature,
                    max_tokens=tokens_remaining,
                    on_single_request_timeout=on_single_chat_timeout,
                    functions=moduleRegistry.to_json_schema()
                )

                # Update full message history
                if reply_type == "content":
                    self.append_history_message("user", user_input)
                    self.append_history_message("assistant", assistant_reply)
                    pass
                elif reply_type == "function_call":
                    self.append_history_message("user", user_input)
                    self.append_history_message_raw({"role": "assistant", "function_call": assistant_reply, "content": None})
                    pass
                else:
                    assert False, "Unexpected reply type"

                return reply_type, assistant_reply
            except RateLimitError:
                # TODO: When we switch to langchain, or something else this is built in
                print("Error: ", "API Rate Limit Reached. Waiting 10 seconds...")
                await asyncio.sleep(10)
Interact with the OpenAI API, sending the prompt, user input, message history,
        and permanent memory.
PoC/agent_jarvis/jarvis/ai_agent/gpt_agent.py