Class Client

java.lang.Object
edu.uky.cs.nil.tt.Client
All Implemented Interfaces:
AutoCloseable, Callable<String>
Direct Known Subclasses:
Test.RandomClient

public abstract class Client extends Object implements Callable<String>, AutoCloseable
A client connects to a server to find a partner and play a role in a storytelling session.

This abstract class implements the necessary communication protocol to connect to the server, find a partner, and start a session. Each time a status update arrives from the server that requires this client to make a choice, the onChoice(Status) method is called to determine what choice the agent wants to make.

This class provides several other methods that are all called from the same thread at important moments in the client's lifecycle. These methods can be overridden to, for example, log important information or update the client's world model. See the call() method for a full description of these methods and when they are called.

If the session begins and ends normally the client's call() method returns the session ID of the completed session. If this client disconnects early, or if a problem occurs that does not cause an uncaught exception, that method will return null.

Author:
Stephen G. Ware
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final int
    The default network port the client will attempt to connect to if one is not explicitly provided in the constructor
    static final String
    The default URL the client will attempt to connect to if one is not explicitly provided in the constructor
    static final String
    The name of the environment variable where the client expects to find the API key used to authenticate with the service that provides functions which require special external resources or computation.
    static final String
    The name of the environment variable where the client expects to find the password this agent will use, unless the password is explicitly set by the agent's constructor.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Client(String name)
    Constructs a client with a given name, which has no preference for its role, world, or partner, reading the password and API key from the environment, and using the default network settings.
    Client(String name, String world, Role role)
    Constructs a client with a name, role, and world name, which has no preference for a partner, reading the password and API key from the environment, and using the default network settings.
    Client(String name, String world, Role role, String partner)
    Constructs a client with the given session preferences, reading the password and API key from the environment, and using the default network settings.
    Client(String name, String world, Role role, String partner, String url, int port)
    Constructs a client with the given session preferences and network details, reading the password and API key from the environment.
    Client(String name, String password, String world, Role role, String partner, String key, String url, int port)
    Constructs a client with the given session preferences, password, API key, and network details.
  • Method Summary

    Modifier and Type
    Method
    Description
    final String
    void
    protected String
    complete(String system, String prompt, float temperature)
    Makes an external call to a large language model API to complete a text prompt.
    protected SSLSocket
    connect(String url, int port)
    Establishes a secure socket to the server based on this client's network configuration.
    protected float[]
    embed(String string)
    Makes an external call to a large language model API to embed a text string in the model's latent space.
    If it is currently this client's turn to act, this method returns the list of turns they can take next.
    Returns the list of all turns that have been taken so far in this client's session that this client has observed.
    Returns the client's name.
    Returns the name of the partner this client wants to play with.
    Returns the role this client wants to play or is currently playing in its session.
    Returns the ID of this client's session.
    Returns the current state of the story world.
    Returns the story world in which this client's session is taking place.
    Returns the name of the story world this client will play in or is currently playing in.
    protected abstract int
    onChoice(Status status)
    This method is called each time it is this client's turn to make a choice in the story.
    protected void
    This method is called once if the client was closed, if it was interrupted, or if the socket was disconnected.
    protected void
    onConnect(Connect connect)
    This method is called after the client connects to the server and the server sends the list of available worlds and agents.
    protected void
    If the client ever established its connection to the server, this method is called once right before the call() method returns or throws an exception.
    protected void
    onEnd(Ending ending)
    This method is called once if the story reaches one of its pre-defined endings.
    protected void
    onError(String message)
    This method is called if the server reports an error to this client.
    protected void
    onStart(World world, Role role, State initial)
    This method is called once when the client's session starts.
    protected void
    onStop(String message)
    This method is called once when the client's session ends.
    protected void
    onUpdate(Status status)
    This method is called each time the story world changes as a result of a turn the client observes.
    protected void
    onWarning(String message)
    This method is called if this client encounters a problem which does not immediately require it to close but which may cause problems.
    protected Message
    This method blocks until the client receives a message from the server and then returns that message.
    protected <M extends Message>
    M
    receive(Class<M> type)
    This method blocks until the client receives a message of a given type from the server.
    protected void
    send(Message message)
    This method sends a message to the server.
    protected void
    Sets the name this client will use.
    protected void
    setPartner(String partner)
    Sets the name of the partner this client wants to play in its session.
    protected void
    setPassword(String password)
    Sets the password this client will use.
    protected void
    setRole(Role role)
    Sets the role this client wants to play in its session.
    protected void
    Sets the name of the story world this client wants to play in.
     

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Field Details

    • ENVIRONMENT_VARIABLE_PASSWORD

      public static final String ENVIRONMENT_VARIABLE_PASSWORD
      The name of the environment variable where the client expects to find the password this agent will use, unless the password is explicitly set by the agent's constructor. If this environment variable is not set and no password is provided in the constructor, this agent will not use a password.
      See Also:
    • ENVIRONMENT_VARIABLE_API_KEY

      public static final String ENVIRONMENT_VARIABLE_API_KEY
      The name of the environment variable where the client expects to find the API key used to authenticate with the service that provides functions which require special external resources or computation. If this environment variable is not set and no API key is provided in the constructor, this agent will not be able to use the external API.
      See Also:
    • DEFAULT_URL

      public static final String DEFAULT_URL
      The default URL the client will attempt to connect to if one is not explicitly provided in the constructor
      See Also:
    • DEFAULT_PORT

      public static final int DEFAULT_PORT
      The default network port the client will attempt to connect to if one is not explicitly provided in the constructor
      See Also:
  • Constructor Details

    • Client

      public Client(String name, String password, String world, Role role, String partner, String key, String url, int port)
      Constructs a client with the given session preferences, password, API key, and network details.
      Parameters:
      name - the name the client will use
      password - the password the client will provide to the server, or null if the client will not use a password
      world - the name of the world the client wants their session to take place in, or null if the client has no preference for a story world
      role - the role the client wants to play in the session, or null if the client is willing to play either role
      partner - the name of the client's desired partner, or null if the client is willing to play with any partner
      key - the API key the client will use to access external resources and computation, or null if the client will not use the external API
      url - the URL of the server to which this client will connect
      port - the network port on which this client will connect
    • Client

      public Client(String name, String world, Role role, String partner, String url, int port)
      Constructs a client with the given session preferences and network details, reading the password and API key from the environment. The client's password will be read from ENVIRONMENT_VARIABLE_PASSWORD. The client's API key will be read from ENVIRONMENT_VARIABLE_API_KEY.
      Parameters:
      name - the name the client will use
      world - the name of the world the client wants their session to take place in, or null if the client has no preference for a story world
      role - the role the client wants to play in the session, or null if the client is willing to play either role
      partner - the name of the client's desired partner, or null if the client is willing to play with any partner
      url - the URL of the server to which this client will connect
      port - the network port on which this client will connect
    • Client

      public Client(String name, String world, Role role, String partner)
      Constructs a client with the given session preferences, reading the password and API key from the environment, and using the default network settings.
      Parameters:
      name - the name the client will use
      world - the name of the world the client wants their session to take place in, or null if the client has no preference for a story world
      role - the role the client wants to play in the session, or null if the client is willing to play either role
      partner - the name of the client's desired partner, or null if the client is willing to play with any partner
    • Client

      public Client(String name, String world, Role role)
      Constructs a client with a name, role, and world name, which has no preference for a partner, reading the password and API key from the environment, and using the default network settings.
      Parameters:
      name - the name the client will use
      world - the name of the world the client wants their session to take place in, or null if the client has no preference for a story world
      role - the role the client wants to play in the session, or null if the client is willing to play either role
    • Client

      public Client(String name)
      Constructs a client with a given name, which has no preference for its role, world, or partner, reading the password and API key from the environment, and using the default network settings.
      Parameters:
      name - the name the client will use
  • Method Details

    • toString

      public String toString()
      Overrides:
      toString in class Object
    • getName

      public String getName()
      Returns the client's name.
      Returns:
      the client's name
    • setName

      protected void setName(String name)
      Sets the name this client will use.
      Parameters:
      name - the new name this client will use
      Throws:
      IllegalStateException - if the client has already joined the server
    • setPassword

      protected void setPassword(String password)
      Sets the password this client will use.
      Parameters:
      password - the new password this client will use
      Throws:
      IllegalStateException - if the client has already joined the server
    • getWorldName

      public String getWorldName()
      Returns the name of the story world this client will play in or is currently playing in. A null value means this client's session has not yet started this client is willing to play in any world. Once the session starts, this method will return the name of the world the client is playing in.
      Returns:
      the name of the client's story world
    • setWorldName

      protected void setWorldName(String world)
      Sets the name of the story world this client wants to play in.
      Parameters:
      world - the name of the world this client wants to play in
      Throws:
      IllegalStateException - if the client has already joined the server
    • getRole

      public Role getRole()
      Returns the role this client wants to play or is currently playing in its session. A null value means this client's session has not yet started and this client is willing to play either role. Once the session starts, this method will return the role assigned to the client.
      Returns:
      the client's role
    • setRole

      protected void setRole(Role role)
      Sets the role this client wants to play in its session.
      Parameters:
      role - the role this client wants to play in its session
      Throws:
      IllegalStateException - if the client has already joined the server
    • getPartner

      public String getPartner()
      Returns the name of the partner this client wants to play with. A null value means this client is willing to play with any partner. This value does not change when the session starts. In other words, if the client did not request a specific partner, they client has no way to know the name of the partner they were assigned.
      Returns:
      the name of the partner this client wants to play with
    • setPartner

      protected void setPartner(String partner)
      Sets the name of the partner this client wants to play in its session.
      Parameters:
      partner - the name of the partner this client wants to play with
      Throws:
      IllegalStateException - if the client has already joined the server
    • getWorld

      public World getWorld()
      Returns the story world in which this client's session is taking place.
      Returns:
      the story world for this client's session
      Throws:
      IllegalStateException - if the client's session has not yet started
    • getHistory

      public List<Turn> getHistory()
      Returns the list of all turns that have been taken so far in this client's session that this client has observed. If the client is the game master, they always observe all turns. If this client is the player, they may not observe all turns.
      Returns:
      the list of all turns that have been taken so far in the session
      Throws:
      IllegalStateException - if the client's session has not yet started
    • getState

      public State getState()
      Returns the current state of the story world.
      Returns:
      the current state of the story world
      Throws:
      IllegalStateException - if the client's session has not yet started
    • getChoices

      public List<Turn> getChoices()
      If it is currently this client's turn to act, this method returns the list of turns they can take next. If it is not thie client's turn, the list will be empty.
      Returns:
      the list of turns the client can take next
      Throws:
      IllegalStateException - if the client's session has not yet started
    • getSession

      public String getSession()
      Returns the ID of this client's session. A session is only assigned after the session has ended, so the value returned by this method will always be null until the very end of this client's lifecycle.
      Returns:
      the session ID of this client's session
    • call

      public final String call() throws Exception

      This method connects to the server, joins, waits for a session to start, sends the choices made by this client, and eventually returns the session ID of the completed session.

      As this method runs, it calls other methods to notify the client of important events. Some of these methods are guaranteed to be called even if an exception is thrown. The list of those methods and when they happen is as follows:

      • The connect(String, int) method is called to establish a secure socket to the server. If an exception is thrown by this method, it will be thrown immediately, and the rest of the methods below will not be called.
      • After the client receives the connect message from the server, onConnect(Connect) is called. This is the client's last chance to make changes to its identity or session details.
      • After the client receives the start message and the first update message, the session begins and the onStart(World, Role, State) method is called. Immediately before that method is called, method like getWorld() will be able to return their values.
      • Each time the client receives an update message, the onUpdate(Status) method is called to notify the client about changes to the session history and world state. This method is called whether or not the client need to make a choice.
      • Each time the client receives an update message and it is the client's turn to act, after calling onUpdate(Status) the onChoice(Status) method will be called to solicit which turn the client wants to take. After calling that method, the getChoices() method will return an empty list.
      • If the session's story reaches one of its pre-defined endings, the onEnd(Ending) method will be called.
      • If the session ended because this client was closed, because the client thread was interrupted, or because the socket was disconnected, the onClose() method will be called. If an exception was thrown earlier in this method, onClose() will not be called.
      • If the session started, the onStop(String) method will always be called before this method returns or throw an exception.
      • If the client successfully connected to the server, the onDisconnect() method will always be called before this method returns or throws an exception.
      Specified by:
      call in interface Callable<String>
      Throws:
      Exception
    • close

      public void close()

      Disconnects this client from the server, causing it to stop. This method can be called safely from any thread. If this method is called before the client connects (e.g. before call()), it does nothing.

      Specified by:
      close in interface AutoCloseable
    • connect

      protected SSLSocket connect(String url, int port) throws Exception
      Establishes a secure socket to the server based on this client's network configuration.

      By default, this method uses Java's Secure Socket Layer to create and return a new socket.

      Parameters:
      url - the URL of the server
      port - the network port on which to open the socket
      Returns:
      a secure socket connection to the server
      Throws:
      IOException - if a problem occurred establishing the socket
      SecurityException - if a security manager exists and it does not allow the socket to be established
      UnknownHostException - if the URL of server is not known
      IllegalArgumentException - if the network port is outside the specified range of valid port values
      Exception
    • receive

      protected <M extends Message> M receive(Class<M> type)
      This method blocks until the client receives a message of a given type from the server. If the next message received is not of the correct type, this method throws an exception. If the client's connection to the server is closed before it receives a message, this method returns null.

      It is usually unsafe for implementations of this abstract class to call this method. The call() method has specific expectations about when and what type of messages will be received. Calling this method at an unexpected time is likely to cause this client to get out of sync with the server.

      Type Parameters:
      M - the type of message the client expects
      Parameters:
      type - the message class this client expects to receive
      Returns:
      the message received from the server, or null if the connection was closed
      Throws:
      IllegalStateException - if the client has not yet connected to the server
    • receive

      protected Message receive()
      This method blocks until the client receives a message from the server and then returns that message. If the client's connection to the server is closed before it receives a message, this method returns null.
      Returns:
      the message received from the server, or null if the connection was closed
      Throws:
      IllegalStateException - if the client has not yet connected to the server
      See Also:
    • send

      protected void send(Message message)
      This method sends a message to the server.

      It is usually unsafe for implementations of this abstract class to call this method. The call() method has specific expectations about when and what type of messages should be sent. Calling this method at an unexpected time is likely to cause this client to get out of sync with the server.

      Parameters:
      message - the message to send to the server
      Throws:
      IllegalStateException - if the client has not yet connected to the server
    • onConnect

      protected void onConnect(Connect connect) throws Exception
      This method is called after the client connects to the server and the server sends the list of available worlds and agents. This method is typically the last chance the client has to change its identity, such as its name or requested role, before the client's join request is sent.

      By default, this method does nothing. It can be overridden if the client wants to react to connecting to the server.

      Parameters:
      connect - the connect message sent from the server
      Throws:
      Exception - if a problem occurs during this method
    • onStart

      protected void onStart(World world, Role role, State initial) throws Exception
      This method is called once when the client's session starts.

      By default, this method does nothing. It can be overridden if the client wants to react to its session starting.

      Parameters:
      world - the story world in which the session will take place
      role - the role this client will play in the story
      initial - the initial state of the story world before the story begins
      Throws:
      Exception - if a problem occurs during this method
    • onUpdate

      protected void onUpdate(Status status) throws Exception
      This method is called each time the story world changes as a result of a turn the client observes. If this client is the game master, it will observe all turns. If this client is the player, it may not observe all turns.

      By default, this method does nothing. It can be overridden if the client wants to react to a change in the world state.

      Parameters:
      status - the current status of the story world, including the history of all turns and the current world state
      Throws:
      Exception - if a problem occurs during this method
    • onChoice

      protected abstract int onChoice(Status status) throws Exception
      This method is called each time it is this client's turn to make a choice in the story. Calls to this method will always be preceded by a call to onStart(World, Role, State) if this is the start of the session or onUpdate(Status) if this is not the start of the session.
      Parameters:
      status - the current status of the story world, including the history of all turns so far, the current state of the world, and the list of choices available for the client to choose from
      Returns:
      the index (starting at 0) of the turn this client wants to take from the list of choices given
      Throws:
      Exception - if a problem occurs during this method
    • onEnd

      protected void onEnd(Ending ending) throws Exception
      This method is called once if the story reaches one of its pre-defined endings. If the session never starts or of the story does not reach an ending, this method will not be called.

      By default, this method does nothing. It can be overridden if the client wants to react to the end of the story.

      Parameters:
      ending - the ending of the story
      Throws:
      Exception - if a problem occurs during this method
    • onStop

      protected void onStop(String message) throws Exception
      This method is called once when the client's session ends. If the session started, this method will always be called, even if an uncaught exception was thrown at an earlier stage in the client's lifecycle.

      By default, this method does nothing. It can be overridden if the client wants to react to the end of the session.

      Parameters:
      message - the message explaining why the session ended, or null if no explanation was received
      Throws:
      Exception - if a problem occurs during this method
    • onClose

      protected void onClose() throws Exception
      This method is called once if the client was closed, if it was interrupted, or if the socket was disconnected. This method will not be called if the client is stopping because an uncaught exception was thrown at an earlier stage of the client's lifecycle. This method is always called from the call() method, meaning it will always run on the thread which called that method, even if a different thread called the close() method.

      By default, this method does nothing. It can be overridden if the client wants to react to be closed.

      Throws:
      Exception - if a problem occurs during this method
    • onDisconnect

      protected void onDisconnect() throws Exception
      If the client ever established its connection to the server, this method is called once right before the call() method returns or throws an exception.

      By default, this method does nothing. It can be overridden if the client wants to react to the end of its lifecycle. This is a good method to clean up resources.

      Throws:
      Exception - if a problem occurs during this method
    • onWarning

      protected void onWarning(String message) throws Exception
      This method is called if this client encounters a problem which does not immediately require it to close but which may cause problems.

      By default, this method prints the warning to standard error.

      Parameters:
      message - a description of the problem
      Throws:
      Exception - if this method throws an exception
    • onError

      protected void onError(String message) throws Exception
      This method is called if the server reports an error to this client.

      By default, if the session has not yet started, this method throws a RuntimeException whose message is the message passed to this method. If the session has started, the error will be printed to standard error and no exception will be thrown.

      Parameters:
      message - an explanation of what caused the error
      Throws:
      Exception - if this method throws an exception
    • complete

      protected String complete(String system, String prompt, float temperature)
      Makes an external call to a large language model API to complete a text prompt.

      This method requires the agent to have an API key; without one, this method will throw an exception.

      Parameters:
      system - the system prompt which instructs the language model how to respond to the prompt
      prompt - the prompt which the large language model will respond to
      temperature - a parameter influencing the predictability of the language model's output, where 0 means completely predictable and higher values mean less predictable (more "creative") output
      Returns:
      the response from the large language model to the prompt
      Throws:
      IllegalStateException - if the client does not have an API key
    • embed

      protected float[] embed(String string)
      Makes an external call to a large language model API to embed a text string in the model's latent space.

      This method requires the agent to have an API key; without one, this method will throw an exception.

      Parameters:
      string - the string to embed
      Returns:
      a vector representing the string's embedding
      Throws:
      IllegalStateException - if the client does not have an API key