# 6 - Reusable Module ## Recap In the previous parts of this guide, we've built a dynamic story premise generator for our Infinity Quest project: 1. **Part 1**: Created the scene project, imported characters from the original Infinity Quest, and set up basic scene settings. 2. **Part 2**: Set up the Scene Loop by extending the default Talemate loop, created an event module to hook into scene initialization, and implemented logic to generate a dynamic introduction that only happens once when the scene is first loaded. 3. **Part 3**: Improved the introduction generation by switching to contextual generation, added a reset mechanism for testing, and implemented random theme generation to guide the premise creation and reduce AI bias. 4. **Part 4**: Organized our node graph by moving the premise generation logic into a separate submodule, added styling to visually identify agent actions, and improved the module interface. 5. **Part 5**: Implemented a multi-agent workflow with staging where: - Stage 1: Generates a random sci-fi theme - Stage 2: Uses the Summarizer agent to analyze the theme in depth - Stage 3: Uses the analysis to guide the creation of an introduction text At this point, we have a fully functional random premise generator for the Infinity Quest project. However, one of the most powerful features of the Talemate node editor is the ability to create modular, reusable components that can be shared across different scenes and projects. In this part, we'll transform our premise generator from a scene-specific module into a standalone, reusable component that can be plugged into any scene. ## 6.0 Backup We're going to be moving and renaming stuff going forward, so you should backup your project. Either copy the entire `scenes/infinity-quest-dynamic` folder somewhere or at least grab the `nodes` subfolder. ## 6.1 Starting Point We've done quite a bit of work at this point, so lets quickly go over what we have: ### Event Module ![Event Module](./img/load-module-on-scene-init.png) This module is responsible for generating the dynamic introduction that only happens once when the scene is first loaded. ![Event Module](./img/6-0001.png) ### Scene Loop Extension ![Scene Loop Module](./img/load-module-scene-loop.png) An extension of the default Talemate scene loop where we added an instance of the on-scene-init event module. ![Scene Loop Extension](./img/6-0002.png) ### Premise Generation Module ![Premise Generation Module](./img/load-module-generate-premise.png) This module is responsible for generating the a scene introduction based on random sci-fi theme with the help of the Summarizer agent for additional exploration of the concept. --- ![Premise Generation Module](./img/6-0003.png) ## 6.2 Plan In order to make our premise generator a standalone module, there are a few things we need to consider: 1. Rename the event module so its less generic as it will be the entry point for the module. 1. Right now the premise generator has some hardcoded values that make sense for the Infinity Quest project (sci-fi themed, star trek type of story). We need to be able to expose this from the premise module all the way to the scene loop extension as configurable properties. 1. A toggle for turning analyzation on and off. 1. Make the module available as a Talemate module, so it can be plugged into any new scene project. ## 6.1 Renaming the Event Module Currently there is no good way to rename a module through the node editor. (Still early in the development of the node editor, so this will be added in the future.) For now the easiest way to rename it is to simply create a copy. So load the `on-scene-init` module and create a copy of it. --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-on-scene-init" Click the **:material-plus: CREATE MODULE** button and then **Copy current**. ![Copy current module](./img/6-0004.png) In the modal change - **Name**: `Dynamic Premise` - **Registry**: `scene/$N` ![Rename module](./img/6-0005.png) Now load the `scene-loop` module and replace the `On Scene Init` module with the new `Dynamic Premise` module. --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-scene-loop" ![Scene Loop Extension](./img/6-0002.png) Select the `On Scene Init` module and press the `delete` key. Then add the `Dynamic Premise` module to the scene loop and set the `event_name` to `scene_loop_init`. ![Scene Loop Extension](./img/6-0006.png) --8<-- "docs/snippets/common.md:save-graph" **:material-movie-play:** Start the scene loop to test that everything's still working. Then find the old `on-scene-init` module in the :material-group: Modules library and press the :material-close-circle-outline: button to delete it. Confirm the action. ![Delete module](./img/6-0007.png) ## 6.2 Exposing a Reset Toggle --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-dynamic-premise" We have the node chain that lets us reset the premise generation. ![Reset node chain](./img/6-0008.png) It'd be practical for this to be a simple switch on the event node. Create a new group somewhere in the module and call it `Properties`. Color it `cyan`. Add the following nodes to the group: - `Module Property` - `Set State` - `Stage` Connect the nodes as follows: - `.name` :material-transit-connection-horizontal: `.name` - `.value` :material-transit-connection-horizontal: `.value` - `.value` :material-transit-connection-horizontal: `.state` --- !!! payload "Module Property - Properties" - **property_name**: `reset` - **property_type**: `bool` - **default**: `false` `Shift` click the title to auto-title it to `PROP reset`. Leave the `Set State` node as is, but change its title to `SET local.reset`. Make sure the `scope` is set to `local`. !!! note "Can't auto-title the Set State node in this case" Since we are plugging the `name` output of the `Module Property` node into the `name` input of the `Set State` node, auto titling the Set State node currently doesn't work, so do it manually. In the `Stage` node set the following: - **stage**: `-1` `Shift` click the title to auto-title it to `Stage -1`. ![Reset toggle](./img/6-0009.png) Next go back to the `Generate Introduction` group and add the following nodes: - `Get State` Replace the existing `Make Bool` (titled `RESET`) node with the `Get State` node, connecting it's value output to the `Switch.value` input. - `.value` :material-transit-connection-horizontal: `.value` --- !!! payload "Get State - Properties" - **name**: `reset` - **scope**: `local` `Shift` click the title to auto-title it to `GET local.reset`. ![Properties group and updated reset toggle](./img/6-0010.png) --8<-- "docs/snippets/common.md:save-graph" --- --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-scene-loop" Confirm that the `Dynamic Premise` node now has a `reset` property that is a boolean on / off switch. ![Dynamic Premise node with reset toggle](./img/6-0011.png) Turn it on since we will want to test the module still while working on it. --8<-- "docs/snippets/common.md:save-graph" ## 6.3 Fixing the registry path for the Generate Premise Module --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-dynamic-premise" Still in the `Dynamic Premise` module, looking at the `Generate Premise` node, it currently exposes no properties to edit. ![Generate Premise node](./img/6-0012.png) But, hold on, its path still is registered as `infinity-quest-dynamic/generatePremise` - that wont do for a reusable module. So we need to change it. --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-generate-premise" Create a copy of the module. (Same as before) This time in the modal you can leave the **Name** as is, and simply replace the `infinity-quest-dynamic` portion of the **Registry** with `scene` so it reads `scene/generatePremise`. ![Change registry](./img/6-0013.png) Click **Continue** to create the copy. Since we are keeping the name the same, it will simply replace the existing `generate-premise` module in the library. However, the `dynamic-premise` module is still referencing the old `generate-premise` module. !!! warning "Dont restart talemate right now" Currently the old reference is still loaded in talemate, so we can load the `dynamic-premise` module without any issues and remove it. Restarting talemate will cause the old reference to be removed and the dynamic premise module may no longer load. --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-dynamic-premise" Delete the old `Generate Premise` node. --8<-- "docs/snippets/common.md:save-graph" Add the new `Generate Premise` node and connect it like how the old one was connected. ![Scene Loop Extension](./img/6-0015.png) --8<-- "docs/snippets/common.md:save-graph" !!! bug "The node editor can be a bit buggy here (v0.30.0)" It may be that when searching for the new node you will find the old one as well (or even multiples of it). Just make sure you select the one that has the path as `scene/generatePremise`. ![Buggy search results](./img/6-0014.png) The issue will go away the next time talemate is restarted. ## 6.4 Content control inputs --8<-- "docs/user-guide/howto/infinity-quest-dynamic/.snippets.md:load-generate-premise" ### Investigating the current properties Lets look which things we need to expose to allow us to control what kind of themes can be generated. --- `..instructions` This controls the overarching theme topic. (sci-fi currenly) ``` A list of sci-fi topics. Keep each item short (1-3 words). ``` --- `..value` The **jinja2** prompt template for the theme analysis. ``` Analyze the following sci-fi theme: {{theme}} Provide a detailed exploration of this theme, including: 1. Key concepts or technologies involved 2. Potential storylines or conflicts 3. How it might affect the crew of the Starlight Nomad Your analysis will be used to set up the premise for the next storyline in the Infinity Quest series. ``` --- `..value` The prompt template for the introduction generation. ``` Generate the introduction to a random exciting scenario for the crew of the Starlight Nomad. The theme is: "{theme}" Use the following analysis to guide your creation of the scenario premise: {theme_analysis} ``` --- So looking at the above, it seems we will need: - A `topic` input that is a string. - A `analysis_instructions` input that is a string We will also rewrite the templates a bit to make them more flexible. ### Input Nodes Create a new group somewhere in the module and call it `Inputs`. Color it `blue`. Add the following nodes to the group: - `Input Socket` (x2) - `Set State` (x2) - `Stage` --- !!! payload "First Input Socket - Properties" - **input_type**: `str` - **input_name**: `topic` `Shift` click the title to auto-title it to `IN topic`. --- !!! payload "Second Input Socket - Properties" - **input_type**: `str` - **input_name**: `analysis_instructions` `Shift` click the title to auto-title it to `IN analysis_instructions`. --- !!! payload "First Set State - Properties" - **name**: `topic` - **scope**: `local` `Shift` click the title to auto-title it to `SET local.topic`. --- !!! payload "Second Set State - Properties" - **name**: `analysis_instructions` - **scope**: `local` `Shift` click the title to auto-title it to `SET local.analysis_instructions`. --- !!! payload "Stage - Properties" - **stage**: `-1` `Shift` click the title to auto-title it to `Stage -1`. --- Connect the nodes as follows: - `.value` :material-transit-connection-horizontal: `.value` - `.value` :material-transit-connection-horizontal: `.state` - `.value` :material-transit-connection-horizontal: `.value` - `.value` :material-transit-connection-horizontal: `.state_b` ![Module Properties](./img/6-0016.png) --8<-- "docs/snippets/common.md:save-graph" ### Adjusting the Generate Theme stage The topic of the theme will be set in `local.topic`, we need to get this and incorporate it into the `Generate Thematic List` node. We will also change to use the appropriate prompting nodes to build the prompt. So this will now use a jinja2 template syntax. In the `Generate Theme` group add the following nodes: - `Get State` - `Make Text` - `Prompt from Template` - `Render Prompt` - `Dict Set` --- !!! payload "Get State - Properties" - **name**: `topic` - **scope**: `local` `Shift` click the title to auto-title it to `GET local.topic`. --- !!! payload "Make Text - Properties" I am chosing to rewrite the instructions to be a bit more compatible with what a user might type into the `topic` input. Its good to keep in mind that a user doesn't know what the entire prompt looks like, so their input may not always be ideal. Of course if you're just using this for yourself, this matters less. - **value** ``` A list of topics to use for brain storming. The overarching theme is described as "{{ topic }}". Keep each item short (1-3 words). One of these items will be chosen to bootstrap a new story line. ``` Retitle to `Instructions` --- Connect the nodes as follows: - `.value` :material-transit-connection-horizontal: `.value` - `.name` :material-transit-connection-horizontal: `.key` - `.value` :material-transit-connection-horizontal: `.template_text` - `.dict` :material-transit-connection-horizontal: `.variables` - `.prompt` :material-transit-connection-horizontal: `.prompt` - `.rendered` :material-transit-connection-horizontal: `.instructions` ![Generate Theme](./img/6-0017.png) --8<-- "docs/snippets/common.md:save-graph" ### Adjusting the Analyze Theme stage We will do the same for the `Analyze Theme` stage, conveniently this is already using `Prompt from Template` node, but the template itself is about to get a bit of a makeover, so there is a bit of work ahead of us. However here I also want to include some extra context in the instructions to help improve the quality of the analysis. In the `Analyze Theme` group add the following nodes: - `Get State` (x2) - `Dict Set` (x2) - `Template variables` --- !!! payload "First Get State - Properties" - **name**: `analysis_instructions` - **scope**: `local` `Shift` click the title to auto-title it to `GET local.analysis_instructions`. --- !!! payload "Second Get State - Properties" - **name**: `topic` - **scope**: `local` `Shift` click the title to auto-title it to `GET local.topic`. --- !!! payload "Theme Analysis Template" Here I want to add the scene `description` and the characters `description` to the template, thinking it will influence the analysis within the context of the story baseline. Knowing the characters will also help the agent come up with potenial storyline suggestions. Additionally I am deciding to coerce the response to be a bit more concise and to the point. In my testing I found that the agent was getting a bit verbose and even with a 1024 token limit it was getting cut off. - **value** ``` <|SECTION:STORY BASELINE|> {{ scene.description }} <|SECTION:PROTAGONISTS|> {% for character in scene.characters %} ### {{ character.name }} {{ character.description }} {% endfor %} <|SECTION:CONTEXT OF YOUR TASK|> A topic description of "{{ topic }}" was given and a random theme was picked to bootstrap a new storyline: "{{ theme }}". <|SECTION:TASK|> Analyze the theme "{{ theme }}" in the context of the topic "{{ topic }}". Provide a brief exploration of this theme, including: 1. Key concepts or ideas involved and how they fit the context 2. Potential storylines or conflicts 3. How it might affect the protagonists of the story {% if analysis_instructions %} {{ analysis_instructions }} {% endif %} Keep your analysis concise and to the point. Your analysis will be used to set up the premise for the next storyline in the `{{ scene.name }}` series. ``` !!! learn-more "Template variables" Its the `Template variables` node that is responsible for adding variables like `scene` to the template scope. - [Prompt Templating](/talemate/user-guide/node-editor/core-concepts/prompt-templates) --- Connect the nodes as follows: - `.value` :material-transit-connection-horizontal: `.value` - `.name` :material-transit-connection-horizontal: `.key` --- - `.value` :material-transit-connection-horizontal: `.value` - `.name` :material-transit-connection-horizontal: `.key` --- Chain all three `Dict Set` nodes together (the 2 new ones plus the 1 existing one), with each connecting to the `dict` input of the next and the last one connecting to the `merge_with` input of the `Template variables` node. Then connect the remaining nodes as follows: --- - `.agent` :material-transit-connection-horizontal: `