Agent dynamic traits

This notebook provides a demonstration of the optional Agent parameter dynamic_traits_function that can be used to generate agent traits dynamically based on the question being asked or the scenario in which the question is asked.

Learn more about this method in the docs: Agent dynamic traits function

How it works

Agents are created by passing a dictionary of traits to an Agent object. For example:

[1]:
from edsl import Agent

my_agent = Agent(
    traits={"persona": "You are a middle-aged mom.", "current_vehicle": "minivan"}
)

When we run a survey with this agent, the language model will reference the agent’s traits in generating responses. We can test this:

[2]:
from edsl.questions import QuestionFreeText
from edsl import Survey

q1 = QuestionFreeText(question_name="age", question_text="How old are you?")
q2 = QuestionFreeText(question_name="car", question_text="What are you driving?")
survey = Survey([q1, q2])

results = survey.by(my_agent).run()

results.select("answer.*").print(format="rich")
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ answer                            answer                                                                       ┃
┃ .car                              .age                                                                         ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ I'm currently driving a minivan.  You know, a lady never tells her age, but let's just say I'm seasoned enough │
│                                   to have mastered the art of juggling family, work, and driving a minivan     │
│                                   with style.                                                                  │
└──────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────┘

Designing question-based traits

For efficiency or other reasons, we may want to minimize the set of traits that we pass to the agent when we create it, and instead only generate certain traits to use with specific questions. To do this, we can create a method for the desired conditional logic and pass it to an agent as the dynamic_traits_function parameter:

[3]:
# Create a method for the desired logic
def favorite_color_response(question):
    if question.question_name == "favorite_color":
        return {
            "favorite_color": "I'd prefer not to say."
        }  # trait to be passed to the agent


# Pass it to the agent
a = Agent(dynamic_traits_function=favorite_color_response)

# Test it by running a relevant question
q = QuestionFreeText(
    question_name="favorite_color", question_text="What is your favorite color?"
)

results = q.by(a).run()

# Inspect the response
results.select("favorite_color").print(format="rich")
┏━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ answer                 ┃
┃ .favorite_color        ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━┩
│ I'd prefer not to say. │
└────────────────────────┘

Context-based traits

The methods used to generate dynamic traits can be complex and provide traits that involve calling other special methods. Here we create a method for accessing the top story on Hacker News and pass it to the agent with a method that will use that result to generate some traits that are not tied to a specific question:

[4]:
# A method for accessing Hacker News stories
import requests


def get_top_hacker_news_story():
    # URL for the top stories endpoint
    top_stories_url = "https://hacker-news.firebaseio.com/v0/topstories.json"

    # Fetch the list of top story IDs
    response = requests.get(top_stories_url)
    if response.status_code != 200:
        return "Error fetching top stories"

    top_stories = response.json()

    # Check if there are any stories
    if not top_stories:
        return "No top stories found"

    # URL for an individual item
    story_url = f"https://hacker-news.firebaseio.com/v0/item/{top_stories[0]}.json"

    # Fetch the top story
    response = requests.get(story_url)
    if response.status_code != 200:
        return "Error fetching the top story"

    top_story = response.json()
    return top_story
[5]:
# Check the output
get_top_hacker_news_story()
[5]:
{'by': 'tolgaarslan',
 'descendants': 16,
 'id': 40168519,
 'kids': [40169818,
  40169746,
  40168998,
  40169299,
  40169616,
  40168935,
  40169010,
  40168566,
  40168520],
 'score': 64,
 'time': 1714134195,
 'title': "I'm creating PBR Textures and 3D models since 2018 and sharing them for free",
 'type': 'story',
 'url': 'https://www.sharetextures.com/'}
[6]:
# Create a method for the trait logic
def reading_habits():
    return {
        "recent_reading": get_top_hacker_news_story()["title"],
        "recent_story_url": get_top_hacker_news_story()["url"],
    }


# Create an agent and pass it the logic
news_hound = Agent(dynamic_traits_function=reading_habits)

# Run some test questions
q1 = QuestionFreeText(
    question_name="reading", question_text="What have you been reading lately?"
)
q2 = QuestionFreeText(
    question_name="url", question_text="Do you know the url for a recent story?"
)

survey = Survey([q1, q2])
results = survey.by(news_hound).run()

# Inspect the results
results.select("answer.*").print(format="rich")
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ answer                          answer                                                                         ┃
┃ .url                            .reading                                                                       ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ https://www.sharetextures.com/  I've been diving into various resources on creating PBR textures and 3D        │
│                                 models, focusing on the latest techniques and tools since 2018. I'm also       │
│                                 exploring ways to share them with a broader audience for free to support the   │
│                                 creative community.                                                            │
└────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘