Concordia

repository·main·Indexed 23 days ago

https://github.com/google-deepmind/concordia

A library for generative agent-based modeling that simulates interactions in physical, social, or digital environments using a Game Master (GM) pattern. It provides core components for agent memory (AssociativeMemory), observation, and identity, as well as advanced reasoning tools like Plan and QuestionOfRecentMemories. The framework includes Game Master components for simulation control, such as EventResolution and SceneTracker, and supports custom agent construction via the EntityAgent class and ConcatActComponent.

Tokens
38.7K
Snippets
63
Records
123
Agent score
80%

What's inside Concordia

  1. Inter-Component Communication Patterns

    main

    Components can communicate directly using a decoupled key-based retrieval pattern.

    Key-based Retrieval

    Components are not aware of each other by default. They use keys (strings) to retrieve other components from their parent entity via: get_entity().get_component(key)

    Example: An EventResolution component can retrieve the MakeObservation component using its key to call add_to_queue() and deliver observations to agents.

    Reasoning with ActionSpecIgnored Components

    Some components inherit from ActionSpecIgnored. These components do not use the action_spec from an act call to compute their state. They are used to provide foundational context (like goals or personality) regardless of the specific action requested. This allows for Dependency Chaining, where one component's output (e.g., a reflection) informs the next component in a reasoning chain.

  2. How MCP integration works in Concordia

    main

    The MCP integration module acts as a bridge between the Model Context Protocol and Concordia. It wraps MCP tools to implement Concordia's concordia.document.tool.Tool interface.

    Core Components:

    • mcp_client.py: Manages the MCP protocol client and connection to servers.
    • mcp_tool.py: Provides the wrapper that implements the concordia.document.tool.Tool interface for each tool exposed by the MCP server.
    • servers/: Contains example implementations of MCP servers.
  3. How to create a custom Game Master (GM) component

    main

    Game Master components control the simulation flow. To implement a custom GM, you must create a class that inherits from entity_component.ContextComponent and entity_component.ComponentWithLogging.

    The core logic resides in the pre_act method, which reacts to different action_spec.output_type values to manage various simulation phases (e.g., generating observations, determining the next actor, or resolving actions).

    To use your custom component, you must register it in the components_of_game_master dictionary using the appropriate component keys (e.g., gm_components.make_observation.DEFAULT_MAKE_OBSERVATION_COMPONENT_KEY).

    import dataclasses
    from concordia.typing import entity as entity_lib
    from concordia.typing import entity_component
    
    class MyGameMasterComponent(
        entity_component.ContextComponent,
        entity_component.ComponentWithLogging,
    ):
        def __init__(
            self,
            acting_player_names: list[str],
            components: list[str] = (),
            pre_act_label: str = "\nMyComponent",
        ):
            super().__init__()
            self._acting_player_names = acting_player_names
            self._components = components
            self._pre_act_label = pre_act_label
            self._state = {"round": 0}
        
        def get_pre_act_label(self) -> str:
            return self._pre_act_label
        
        def get_pre_act_value(self) -> str:
            return f"Current round: {self._state['round']}"
        
        def pre_act(self, action_spec: entity_lib.ActionSpec) -> str:
            """Handle different output types from the simulation engine."""
            output_type = action_spec.output_type
            
            if output_type == entity_lib.OutputType.MAKE_OBSERVATION:
                return self._handle_make_observation(action_spec)
            elif output_type == entity_lib.OutputType.NEXT_ACTION_SPEC:
                return self._handle_next_action_spec(action_spec)
            elif output_type == entity_lib.OutputType.NEXT_ACTING:
                return self._handle_next_acting()
            elif output_type == entity_lib.OutputType.RESOLVE:
                return self._resolve(action_spec)
            elif output_type == entity_lib.OutputType.NEXT_GAME_MASTER:
                return self._handle_next_gm()
            else:
                return ""
  4. Action and Decision-Making Agent Components

    main

    These components translate an agent's internal state into concrete actions:

    • ConcatActComponent (concat_act_component.py): The standard component for generating actions. It combines outputs from other components (memory, observation, instructions) into a single prompt for the language model.
    • ScriptedAct (scripted_act.py): Used for deterministic behavior by providing a predefined sequence of actions for the agent to follow.
  5. Core abstractions in the document module

    main

    The document module manages text, context, and LLM interactions by treating them as a evolving "document" of context. It consists of three primary classes:

    1. Document: A structural container for text content using a list of Content objects (text + tags). It supports branching via copy and filtered viewing via view.
    2. InteractiveDocument: Extends Document to support direct interaction with a LanguageModel. It manages dialogue history and provides methods for asking questions and generating responses.
    3. InteractiveDocumentWithTools: Extends InteractiveDocument to allow the LLM to invoke tools (e.g., web search) during question answering. Tool calls and results are automatically integrated into the document history.
  6. Create an Autorater Rubric

    main

    An autorater rubric is a markdown (.md) file that guides the LLM in its evaluation. It should include:

    • Scoring dimensions: Aspects of quality to evaluate (e.g., Behavioral Realism, Social Emergence, Narrative Coherence).
    • Scoring criteria: Specific descriptions of what constitutes different scores (e.g., what a 1/5 vs. 5/5 looks like).
    • Data extraction recipes: Instructions on how to compute quantitative metrics from the structured log (e.g., connectivity rate, repetition ratio).
    • Known failure modes: Calibration references for the LLM to recognize issues like repetition loops or ghost agents.
    • Report structure: The required output format, such as per-agent narratives, social interaction matrices, and rubric scores.
  7. Core Concepts of Concordia

    main

    Understanding the fundamental abstractions in Concordia:

    • Prefab: A reusable recipe for building an entity (agent or game master).
    • InstanceConfig: Configuration specifying which prefab to use and its specific parameters.
    • Config: The full simulation configuration, containing prefabs, instances, and the premise.
    • Simulation: The main object that orchestrates entities and game masters.
    • Entity: An agent capable of observing the world and taking actions.
    • Game Master: Controls the simulation flow, resolves actions, and generates observations.
    • AssociativeMemoryBank: A component used to store and retrieve memories using embeddings.
  8. Use Entity Components to extend Entity behavior

    main

    Concordia uses a component system to extend the functionality of entities. The BaseComponent serves as the root of this hierarchy.

    If you need to provide context or modify behavior during specific lifecycle stages, implement a ContextComponent. These components provide hooks that are called during different Phases of the simulation lifecycle:

    • pre_act / post_act
    • pre_observe / post_observe
    • update

    Common lifecycle phases are defined in the Phases enum.

  9. Concurrency and Thread Safety in Components

    main

    Concordia supports parallel simulation execution using a specific concurrency model within EntityAgent:

    • Parallel Execution: The internal _parallel_call_ method uses a ThreadPoolExecutor to call component methods in parallel during act and observe phases.
    • Mutual Exclusion: The act and observe methods are wrapped in a _control_lock, ensuring only one of these methods executes at a time for a given agent.

    Implications for Custom Components: If your custom component modifies shared state, you must ensure it is thread-safe. While components modifying only their own internal state are generally safe, any component accessing shared resources must implement its own locking mechanism.

  10. Game Master Prefabs for controlling simulations

    main

    Game Master (GM) prefabs build the directors that control the environment, NPCs, and simulation logic. They are categorized by purpose:

    General Purpose

    • generic.py: Highly configurable via parameters; supports custom thought chains.
    • situated.py: Manages world state, locations, and a "Story so far".
    • situated_in_time_and_place.py: An extension of situated.py using a GenerativeClock to track time passage.

    Scenario Specific

    • dialogic.py: Specialized for conversation; ends repetitive dialogues.
    • dialogic_and_dramaturgic.py: Manages structured "Scenes" (e.g., Prologue, Episode 1).
    • game_theoretic_and_dramaturgic.py: For matrix games/social dilemmas; maps joint actions to scores.
    • marketplace.py: Tuned for economic simulations with market transaction handling.
    • psychology_experiment.py: A harness for injecting custom observation and action components.
    • interviewer.py: Administers fixed multiple-choice questionnaires.
    • open_ended_interviewer.py: Administers free-form text questionnaires.
    • scripted.py: Follows a strict, fixed script; useful for generating data for fine-tuning.

    Utility

    • formative_memories_initializer.py: A special GM used with Role.INITIALIZER to implant memories into agents before the main simulation loop begins.
  11. How LLM autoraters work in Concordia

    main

    An LLM autorater is an automated evaluation system that uses a structured rubric to grade the quality of a Concordia simulation. Instead of manual human review, the autorater processes structured simulation logs to produce a graded report.

    The workflow is:

    1. Manual Review: Perform an initial manual review of a simulation run to identify quality signals and failure modes.
    2. Codify Rubric: Create a markdown document defining scoring dimensions (e.g., Behavioral Realism), scoring criteria (1-5 scales), data extraction recipes, and known failure modes.
    3. Automate: Provide the rubric and the simulation log to an LLM to generate an automated analysis.
    4. Scale: Run the process across multiple experiments, models, or architectures to compare performance.

    Autoraters are particularly useful for identifying failure modes like repetition loops, NPC hallucinations, or temporal incoherence and for comparing different LLM backends (e.g., comparing gemma-27b vs gemini-pro) using a standardized comparison table.

  12. Build a reflective agent with reasoning components

    main

    For complex behaviors, chain components so that one component's output serves as context for another. This allows for 'Chain of Thought' reasoning.

    Key reasoning components include:

    • SituationRepresentation: Summarizes the current situation using recent observations and relevant memories.
    • QuestionOfRecentMemories: Asks the model a specific question (e.g., an 'Internal Monologue') based on the current context.

    When assembling the components dictionary, ensure the component_order in ConcatActComponent matches the logical dependency (e.g., situation_representation must appear before the component that depends on it).

    from concordia.contrib.components.agent import situation_representation_via_narrative
    from concordia.components import agent as agent_components
    
    @dataclasses.dataclass
    class ReflectiveAgent(prefab_lib.Prefab):
        def build(self, model, memory_bank):
            name = self.params.get("name", "Agent")
            
            # 1. Base Components
            instructions = agent_components.instructions.Instructions(
                agent_name=name)
            observation = agent_components.observation.LastNObservations(
                history_length=100)
            memory = agent_components.memory.AssociativeMemory(
                memory_bank=memory_bank)
            
            # 2. Advanced Components (Chain of Thought)
            
            # Step A: Summarize the situation
            situation = situation_representation_via_narrative.SituationRepresentation(
                model=model,
                observation_component_key=agent_components.observation.DEFAULT_OBSERVATION_COMPONENT_KEY,
                declare_entity_as_protagonist=True,
            )
            
            # Step B: Apply a Guiding Principle (uses Step A context)
            principle = agent_components.question_of_recent_memories.QuestionOfRecentMemories(
                model=model,
                pre_act_label=f"{name}'s Internal Monologue",
                question=f"How can {name} best achieve their goals in this situation?",
                answer_prefix=f"{name} thinks: ",
                add_to_memory=False, # Don't clutter memory with every thought
                components=[
                    "Instructions",
                    "situation_representation" # <--- Depends on Step A
                ],
            )
    
            # 3. Assemble Components (Order matters!)
            components = {
                "Instructions": instructions,
                agent_components.memory.DEFAULT_MEMORY_COMPONENT_KEY: memory,
                agent_components.observation.DEFAULT_OBSERVATION_COMPONENT_KEY: observation,
                "situation_representation": situation,
                "guiding_principle": principle,
            }
            
            # The Act Component sees everything in 'components'
            act_component = agent_components.concat_act_component.ConcatActComponent(
                model=model,
                component_order=list(components.keys()),
            )
            
            return entity_agent_with_logging.EntityAgentWithLogging(
                agent_name=name,
                act_component=act_component,
                context_components=components,
            )