初めに
LLM・LLM活用アドカレ 18日目です!
genagentsは、生成的エージェントを作成・操作するためのPythonライブラリです。こちらを使って仮想環境でのNPCの一定時間内でのシミュレーションをおこなっていきます
以下で動く環境を 作成していますので、是非触ってみてください。以下の内容はforkしたリポジトリを前提に進めていきます
開発環境
セットアップ
uv venv -p 3.11 .venv\Scripts\activate uv pip install -r requirements.txt
OpenAIのAPIを使うため、以下の .env
ファイルを ルートに作成します
OPENAI_API_KEY = "api key" KEY_OWNER = "name"
一人のエージェントにユーザー質問をする
まずはシンプルにコードを動かしていきます。エージェントの設定を行って質問に回答してもらいます
import sys import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) from genagents.genagents import GenerativeAgent # エージェントを初期化 agent = GenerativeAgent() # 個人情報を更新 agent.update_scratch({ "first_name": "太郎", "last_name": "山田", "age": 30, "occupation": "ソフトウェアエンジニア", "interests": ["読書", "ハイキング", "プログラミング"] }) # カテゴリカルな質問をする questions = { "あなたはアウトドア活動が好きですか?": ["はい", "いいえ", "時々"] } response = agent.categorical_resp(questions) print(response["responses"]) # 数値的な質問をする questions = { "プログラミングはどのくらい好きですか?(1から10のスケール)": [1, 10] } response = agent.numerical_resp(questions, float_resp=False) print(response["responses"]) # オープンエンドの質問をする dialogue = [ ("インタビュアー", "あなたの好きな趣味について教えてください。"), ] response = agent.utterance(dialogue) print(response)
実行した結果は以下のようになります
['はい'] [9] 私の好きな趣味は読書です。特に小説を読むのが好きで、いろいろなジャンルに挑戦しています。また、ハイキングも好きで、自然の中で過ごすのは心が落ち着きます。プログラミングは仕事の一部ですが、趣味としても 新しい技術を学ぶのが楽しいです。
記憶システムを追加
以下のメソッドを使ってメモリ機能を追加します
- agent.remember(text, time_step) : エージェントの記憶ストリームに新しい記憶を追加
- agent.reflect(anchor, time_step) : エージェントが自身の記憶を振り返り、新たな洞察や結論を得るためのプロセスを実行
- agent.save(path) : エージェントの現在の状態(記憶ストリーム、個人情報、内省の結果など)を保存
- GenerativeAgent(agent_folder="saved_agents/agent1") : 保存したエージェントを再度使用
これらの機能を追加して、エージェントに対して質問を行ってみます。
# run_agent_example.py import os import sys # 現在のディレクトリをモジュール検索パスに追加 sys.path.append(os.path.dirname(os.path.abspath(__file__))) from genagents.genagents import GenerativeAgent # ----- エージェントの作成と初期化 ----- # エージェントを初期化 agent = GenerativeAgent() # 個人情報を更新 agent.update_scratch({ "first_name": "太郎", "last_name": "山田", "age": 30, "occupation": "ソフトウェアエンジニア", "interests": ["読書", "ハイキング", "プログラミング"] }) # ----- メモリの追加 ----- # エージェントにメモリを追加 agent.remember("昨日、友人と一緒に山でハイキングを楽しんだ。", time_step=1) agent.remember("新しいプログラミング言語を学び始めた。", time_step=2) # ----- リフレクションの実行 ----- # アウトドア活動に関するリフレクション agent.reflect(anchor="アウトドア活動", time_step=3) # プログラミングに関するリフレクション agent.reflect(anchor="プログラミング", time_step=4) # ----- エージェントとの対話 ----- # カテゴリカルな質問をする categorical_questions = { "あなたはアウトドア活動が好きですか?": ["はい", "いいえ", "時々"] } categorical_response = agent.categorical_resp(categorical_questions) print("カテゴリカルな質問の回答:", categorical_response["responses"]) # 数値的な質問をする numerical_questions = { "プログラミングはどのくらい好きですか?(1から10のスケール)": [1, 10] } numerical_response = agent.numerical_resp(numerical_questions, float_resp=False) print("数値的な質問の回答:", numerical_response["responses"]) # オープンエンドの質問をする dialogue = [ ("インタビュアー", "あなたの好きな趣味について教えてください。"), ] open_ended_response = agent.utterance(dialogue) print("オープンエンドの質問の回答:", open_ended_response) # ----- エージェントの保存 ----- # エージェントを保存するディレクトリを指定 save_directory = "saved_agents/agent_taro" # ディレクトリが存在しない場合は作成 if not os.path.exists(save_directory): os.makedirs(save_directory) # エージェントを保存 agent.save(save_directory) print(f"エージェントを '{save_directory}' に保存しました。") # ----- エージェントの読み込み ----- # 保存したエージェントを読み込む loaded_agent = GenerativeAgent(agent_folder=save_directory) print("保存したエージェントを読み込みました。") # 読み込んだエージェントとの対話 dialogue = [ ("インタビュアー", "最近学んだことについて教えてください。"), ] loaded_response = loaded_agent.utterance(dialogue) print("読み込んだエージェントからの回答:", loaded_response)
実行結果は以下のようになります
カテゴリカルな質問の回答: ['はい'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 私は読書とハイキングが大好きです。特に、自然の中で過ごす時間が心をリフレッシュさせてくれます。最近、友人と一緒に山でハイキングを楽しんだのですが、その経験を通じて友情も深 まったと感じています。また、プログラミングも趣味の一つで、新しいスキルを学ぶのが楽しいです。 エージェントを 'saved_agents/agent_taro' に保存しました。 保存したエージェントを読み込みました。 読み込んだエージェントからの回答: 最近、新しいプログラミング言語を学び始めました。具体的には、Pythonに挑戦しています。最初は少し難しく感じましたが、実際にコードを書いてみると、だんだん楽しくなってき ました。
複数人のエージェントに対して質問をする
次に複数人に対して質問を投げます。基本的にエージェントの設定を増やして各エージェントに対して質問を投げています
# run_multiple_agents.py import os import sys # 現在のディレクトリをモジュール検索パスに追加 sys.path.append(os.path.dirname(os.path.abspath(__file__))) from genagents.genagents import GenerativeAgent # ----- エージェントのプロフィール作成 ----- agent_profiles = [ { "first_name": "太郎", "last_name": "山田", "age": 30, "occupation": "ソフトウェアエンジニア", "interests": ["読書", "ハイキング", "プログラミング"] }, { "first_name": "花子", "last_name": "佐藤", "age": 28, "occupation": "デザイナー", "interests": ["イラスト", "カフェ巡り", "映画鑑賞"] }, { "first_name": "健一", "last_name": "高橋", "age": 35, "occupation": "教師", "interests": ["歴史", "旅行", "料理"] }, { "first_name": "美咲", "last_name": "鈴木", "age": 26, "occupation": "看護師", "interests": ["音楽", "ランニング", "写真"] }, { "first_name": "一郎", "last_name": "田中", "age": 40, "occupation": "営業", "interests": ["ゴルフ", "ワイン", "ビジネス書"] }, { "first_name": "由美", "last_name": "伊藤", "age": 32, "occupation": "エディター", "interests": ["読書", "ヨガ", "アート"] }, { "first_name": "直樹", "last_name": "渡辺", "age": 29, "occupation": "エンジニア", "interests": ["ゲーム", "アニメ", "プログラミング"] }, { "first_name": "理恵", "last_name": "中村", "age": 31, "occupation": "マーケティング", "interests": ["ファッション", "SNS", "料理"] }, { "first_name": "健二", "last_name": "小林", "age": 27, "occupation": "カメラマン", "interests": ["写真", "登山", "ドキュメンタリー"] }, { "first_name": "美香", "last_name": "加藤", "age": 33, "occupation": "弁護士", "interests": ["映画", "ジョギング", "旅行"] } ] # ----- エージェントの作成と初期化 ----- agents = [] for profile in agent_profiles: agent = GenerativeAgent() agent.update_scratch(profile) agents.append(agent) # ----- メモリの追加とリフレクション ----- for agent in agents: # メモリの追加 memory_text = f"{agent.scratch['first_name']}は最近、{agent.scratch['interests'][0]}に関する新しい経験をした。" agent.remember(memory_text, time_step=1) # リフレクションの実行 agent.reflect(anchor=agent.scratch['interests'][0], time_step=2) # ----- 共通の質問 ----- # カテゴリカルな質問 categorical_questions = { "あなたはアウトドア活動が好きですか?": ["はい", "いいえ", "時々"] } # 数値的な質問 numerical_questions = { "あなたの仕事への満足度はどのくらいですか?(1から10のスケール)": [1, 10] } # オープンエンドの質問 dialogue = [ ("インタビュアー", "最近の出来事について教えてください。"), ] # ----- 各エージェントに質問を行う ----- for idx, agent in enumerate(agents): print(f"\n===== エージェント {idx + 1}: {agent.scratch['first_name']} {agent.scratch['last_name']} =====") # カテゴリカルな質問の回答 categorical_response = agent.categorical_resp(categorical_questions) print("カテゴリカルな質問の回答:", categorical_response["responses"]) # 数値的な質問の回答 numerical_response = agent.numerical_resp(numerical_questions, float_resp=False) print("数値的な質問の回答:", numerical_response["responses"]) # オープンエンドの質問の回答 open_ended_response = agent.utterance(dialogue) print("オープンエンドの質問の回答:", open_ended_response)
実行結果は以下になります
===== エージェント 1: 太郎 山田 ===== カテゴリカルな質問の回答: ['はい'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、新しい本を読んでみたんです。特に心理学に関する本で、他の人の考えや感情を理解する手助けになりました。それによって、周りの人との関係がより深まったと感じています。 ===== エージェント 2: 花子 佐藤 ===== カテゴリカルな質問の回答: ['いいえ'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、イラストを描くことに挑戦してみたんです。自分の表現力が広がったと感じていて、特に新しい技法を使うことで、作品に深みが出てきました。カフェ巡りをしながら、インスピレー ションを得ることも多くて、本当に楽しいです。 ===== エージェント 3: 健一 高橋 ===== カテゴリカルな質問の回答: ['時々'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、地元の歴史博物館を訪れたんです。そこで、私の故郷の文化や歴史についての特別展があって、とても感動しました。自分のルーツを知ることができ、今まで以上に歴史が身近に感じ られるようになりました。 ===== エージェント 4: 美咲 鈴木 ===== カテゴリカルな質問の回答: ['はい'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、友達と一緒に音楽フェスに行ったんです。そこでたくさんのアーティストの演奏を聴いて、音楽が持つ力を改めて感じました。人々が同じメロディに合わせて踊ったり歌ったりする姿 を見て、音楽がどれだけ人を結びつけるかを実感しました。 ===== エージェント 5: 一郎 田中 ===== カテゴリカルな質問の回答: ['時々'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、友人たちと一緒にゴルフをプレイする機会がありました。自分のスキルを見直す良いチャンスになったんです。特に、ショットの精度やコースマネジメントについて考える時間が増え ました。それに、ゴルフを通じて友人たちとの絆も深まりました。 ===== エージェント 6: 由美 伊藤 ===== カテゴリカルな質問の回答: ['時々'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、素晴らしい本に出会いました。それは私にとって新たな視点を提供してくれるもので、内容に深く感動しました。読書を通じて、自分の考えや感情を整理できることがとても大切だと 感じています。あなたは最近、どんな本を読みましたか? ===== エージェント 7: 直樹 渡辺 ===== カテゴリカルな質問の回答: ['いいえ'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、友人たちと一緒に新しいゲームを始めました。最初はちょっと難しかったけれど、みんなで協力しながらプレイすることで、色々な戦略やプレイスタイルを学ぶことができて、とても 楽しかったです。それに、ゲームを通じて友達との絆も深まったと感じています。 ===== エージェント 8: 理恵 中村 ===== カテゴリカルな質問の回答: ['時々'] 数値的な質問の回答: [7] オープンエンドの質問の回答: 最近、ファッションに関する新しい経験をしました。新しいトレンドに触れることで、自分のスタイルを見つめ直す良い機会になったと思います。特に、個性を表現する方法が広がったと感 じています。 ===== エージェント 9: 健二 小林 ===== カテゴリカルな質問の回答: ['はい'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、写真に関する新しい経験をしたんです。特に、日常の中で見過ごしがちな美しさに気づくことができて、自分の視点や感性を再発見できた気がします。そのおかげで、もっと積極的に シャッターを切りたくなりました。 ===== エージェント 10: 美香 加藤 ===== カテゴリカルな質問の回答: ['時々'] 数値的な質問の回答: [8] オープンエンドの質問の回答: 最近、映画に関する新しい経験をしました。それが私の映画の楽しみ方を広げてくれたんです。具体的には、映画の制作過程や監督の意図を理解する機会があったんです。それによって、映 画が私に与える影響や感情について深く考えるようになりました。
数年単位の複数エージェントに対しての文化シミュレーション
架空の村に住むエージェントたちの生活を100年間にわたってシミュレートするものです。エージェントは個々のプロフィール(名前、年齢、職業、興味など)を持ち、毎年発生するイベントや相互作用を通じて変化していきます。
# simulation.py import os import sys import random # 現在のディレクトリをモジュール検索パスに追加 sys.path.append(os.path.dirname(os.path.abspath(__file__))) from genagents.genagents import GenerativeAgent # ----- 1. エージェントの初期化 ----- agent_profiles = [ { "first_name": "たかし", "last_name": "すずき", "age": 25, "occupation": "農民", "interests": ["農業", "漁業", "物語"] }, { "first_name": "あいこ", "last_name": "たなか", "age": 23, "occupation": "織物師", "interests": ["織物", "薬草", "音楽"] }, { "first_name": "ひろし", "last_name": "やまだ", "age": random.randint(20, 40), "occupation": "狩人", "interests": ["狩猟", "追跡", "登山"] }, { "first_name": "ゆみ", "last_name": "さとう", "age": random.randint(20, 40), "occupation": "陶芸家", "interests": ["陶芸", "芸術", "デザイン"] }, { "first_name": "けんた", "last_name": "こばやし", "age": random.randint(20, 40), "occupation": "漁師", "interests": ["漁業", "船作り", "水泳"] }, { "first_name": "さくら", "last_name": "わたなべ", "age": random.randint(20, 40), "occupation": "大工", "interests": ["木工", "建築", "絵画"] }, { "first_name": "だいち", "last_name": "いとう", "age": random.randint(20, 40), "occupation": "薬草師", "interests": ["薬草学", "園芸", "料理"] }, { "first_name": "ゆうこ", "last_name": "なかむら", "age": random.randint(20, 40), "occupation": "語り部", "interests": ["物語", "音楽", "踊り"] }, { "first_name": "さとし", "last_name": "かとう", "age": random.randint(20, 40), "occupation": "交易商", "interests": ["交易", "探索", "交渉"] }, { "first_name": "めぐみ", "last_name": "よしだ", "age": random.randint(20, 40), "occupation": "庭師", "interests": ["農業", "植栽", "自然"] } ] # プログラムで追加のプロフィールを生成 names = [ ("ひろし", "やまだ"), ("ゆみ", "さとう"), ("けんた", "こばやし"), ("さくら", "わたなべ"), ("だいち", "いとう"), ("ゆうこ", "なかむら"), ("さとし", "かとう"), ("めぐみ", "よしだ") ] occupations = ["狩人", "陶芸家", "漁師", "大工", "薬草師", "語り部", "交易商", "庭師"] interests = [ ["狩猟", "追跡", "登山"], ["陶芸", "芸術", "デザイン"], ["漁業", "船作り", "水泳"], ["木工", "建築", "絵画"], ["薬草学", "園芸", "料理"], ["物語", "音楽", "踊り"], ["交易", "探索", "交渉"], ["農業", "植栽", "自然"] ] # エージェントの初期化 agents = [] for profile in agent_profiles: agent = GenerativeAgent() agent.update_scratch(profile) agents.append(agent) # ----- 2. 年次シミュレーション ----- total_years = 10 current_year = 3000 # 西暦3000年からスタート time_step = 0 # タイムステップ for year in range(total_years): current_year += 1 time_step += 1 print(f"\n===== 年 {current_year} =====") # --- 集落イベント --- community_events = [ "豊作に恵まれ、食料が潤沢になった。", "厳しい冬が訪れ、集落を試練が襲った。", "近隣の部族が交易に訪れた。", "大雨による洪水が田畑を襲った。", "新年を祝う祭りが開催された。", "地震が地域を揺るがした。" ] event = random.choice(community_events) print(f"集落イベント: {event}") # エージェントがイベントを記憶 for agent in agents: agent.remember(f"{current_year}年に、{event}", time_step) # --- エージェントのライフイベント --- for agent in agents[:]: # リストをコピーしてイテレート # 年齢を増やす agent_age = agent.scratch.get('age', 25) + 1 agent.scratch['age'] = agent_age # 死亡チェック(簡易的な確率モデル) if agent_age > 60 and random.random() < 0.1: print(f"{agent.scratch['first_name']} {agent.scratch['last_name']} が {agent_age} 歳で亡くなりました。") agents.remove(agent) continue # 出生イベント if 18 <= agent_age <= 45 and random.random() < 0.3: # 子供を持つ可能性 child_first_name = random.choice(["はると", "ゆい", "かいと", "はな", "そら", "まお", "れん", "みこ"]) child_last_name = agent.scratch['last_name'] child_profile = { "first_name": child_first_name, "last_name": child_last_name, "age": 0, "occupation": "子供", "interests": ["遊び", "学び"] } child_agent = GenerativeAgent() child_agent.update_scratch(child_profile) agents.append(child_agent) agent.remember(f"{child_first_name}という子供が生まれた。", time_step) print(f"{agent.scratch['first_name']} に {child_first_name} という子供が生まれました。") # 内省の実行 agent.reflect(anchor="年次イベント", time_step=time_step) # --- エージェント間の相互作用 --- for i in range(len(agents)): for j in range(i + 1, len(agents)): agent_a = agents[i] agent_b = agents[j] # シンプルな対話 if random.random() < 0.2: dialogue = [ (agent_a.scratch['first_name'], "最近どうですか?"), (agent_b.scratch['first_name'], "元気です。最近の出来事は興味深いですね。"), ] agent_a.utterance(dialogue) # 相互に記憶 agent_a.remember(f"{agent_b.scratch['first_name']} と会話した。", time_step) agent_b.remember(f"{agent_a.scratch['first_name']} と会話した。", time_step) # ----- 3. 結果の分析 ----- print("\n===== シミュレーション完了 =====") print(f"シミュレーション終了時のエージェント数: {len(agents)}") for agent in agents: print(f"{agent.scratch['first_name']} {agent.scratch['last_name']}, 年齢: {agent.scratch['age']}, 職業: {agent.scratch['occupation']}") # 最近の記憶を表示 recent_memories = agent.memory_stream[-3:] # 最新の3つの記憶 print("最近の記憶:") for memory in recent_memories: print(f"- {memory}") print()
実行結果は以下のようになります
===== 年 3001 ===== 集落イベント: 地震が地域を揺るがした。 ひろし に ゆい という子供が生まれました。 けんた に まお という子供が生まれました。 さくら に れん という子供が生まれました。 ===== 年 3002 ===== 集落イベント: 新年を祝う祭りが開催された。 ゆみ に はな という子供が生まれました。 さくら に はると という子供が生まれました。 ===== 年 3003 ===== 集落イベント: 厳しい冬が訪れ、集落を試練が襲った。 ===== 年 3004 ===== 集落イベント: 地震が地域を揺るがした。 たかし に ゆい という子供が生まれました。 ゆみ に みこ という子供が生まれました。 ===== 年 3005 ===== 集落イベント: 豊作に恵まれ、食料が潤沢になった。 あいこ に みこ という子供が生まれました。 さくら に みこ という子供が生まれました。 ゆうこ に れん という子供が生まれました。 さとし に ゆい という子供が生まれました。 ===== 年 3006 ===== 集落イベント: 地震が地域を揺るがした。 けんた に はると という子供が生まれました。 ===== 年 3007 ===== 集落イベント: 厳しい冬が訪れ、集落を試練が襲った。 ゆうこ に そら という子供が生まれました。 ===== 年 3008 ===== 集落イベント: 大雨による洪水が田畑を襲った。 ひろし に ゆい という子供が生まれました。 けんた に かいと という子供が生まれました。 ===== 年 3009 ===== 集落イベント: 地震が地域を揺るがした。 たかし に まお という子供が生まれました。 あいこ に かいと という子供が生まれました。 めぐみ に はると という子供が生まれました。 ===== 年 3010 ===== 集落イベント: 厳しい冬が訪れ、集落を試練が襲った。 ゆうこ に そら という子供が生まれました。 ===== シミュレーション完了 ===== シミュレーション終了時のエージェント数: 29 たかし すずき, 年齢: 35, 職業: 農民