input: Write a Python function to implement a reinforcement learning agent that utilizes Q-learning for solving an environment with sparse rewards. The algorithm should keep the learning agent in a continuous loop of taking actions, observing rewards and updating the Q-value function to reach the outcome desired.
  assert isinstance(rl_agent, QLearningAgent)
  assert rl_agent.update_q_value((0, 1), 1, -1, (0, 2)) == expected_q_value
  assert rl_agent.select_action((0, 1)) in rl_agent.get_possible_actions()
output: import numpy as np
  import random
  
  class QLearningAgent:
      def __init__(self, actions, alpha=0.1, gamma=0.9, epsilon=0.1):
          self.q_table = {}
          self.actions = actions
          self.alpha = alpha
          self.gamma = gamma
          self.epsilon = epsilon
  
      def get_q_value(self, state, action):
          return self.q_table.get((state, action), 0.0)
  
      def update_q_value(self, state, action, reward, next_state):
          future_rewards = [self.get_q_value(next_state, a) for a in self.actions]
          best_future_q = max(future_rewards) if future_rewards else 0.0
          current_q = self.get_q_value(state, action)
          new_q = current_q + self.alpha * (reward + self.gamma * best_future_q - current_q)
          self.q_table[(state, action)] = new_q
          return new_q
  
      def select_action(self, state):
          if random.uniform(0, 1) < self.epsilon:
              return random.choice(self.actions)
          q_values = {action: self.get_q_value(state, action) for action in self.actions}
          max_q = max(q_values.values())
          best_actions = [action for action, q in q_values.items() if q == max_q]
          return random.choice(best_actions)
  
      def get_possible_actions(self):
          return self.actions
  
  def rl_agent():
      actions = ['up', 'down', 'left', 'right']
      agent = QLearningAgent(actions)
      return agent