Merge branch 'main' into code_interpreter

This commit is contained in:
yzlin 2024-04-11 18:08:30 +08:00
commit f87387d22a
377 changed files with 15703 additions and 893 deletions

View file

@ -0,0 +1,2 @@
pyshine==0.0.9
opencv-python==4.6.0.66

View file

@ -0,0 +1,71 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : the entry of android assistant including learning and acting stage
# See the usage README inside `metagpt/ext/android_assistant`
# README see `metagpt/ext/android_assistant/README.md`
import asyncio
from pathlib import Path
import typer
from metagpt.config2 import config
from metagpt.environment.android.android_env import AndroidEnv
from metagpt.ext.android_assistant.roles.android_assistant import AndroidAssistant
from metagpt.team import Team
app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
@app.command("", help="Run a Android Assistant")
def startup(
task_desc: str = typer.Argument(help="the task description you want the android assistant to learn or act"),
n_round: int = typer.Option(default=20, help="The max round to do an app operation task."),
stage: str = typer.Option(default="learn", help="stage: learn / act"),
mode: str = typer.Option(default="auto", help="mode: auto / manual , when state=learn"),
app_name: str = typer.Option(default="demo", help="the name of app you want to run"),
investment: float = typer.Option(default=5.0, help="Dollar amount to invest in the AI company."),
refine_doc: bool = typer.Option(
default=False, help="Refine existing operation docs based on the latest observation if True."
),
min_dist: int = typer.Option(
default=30, help="The minimum distance between elements to prevent overlapping during the labeling process."
),
android_screenshot_dir: str = typer.Option(
default="/sdcard/Pictures/Screenshots",
help="The path to store screenshots on android device. Make sure it exists.",
),
android_xml_dir: str = typer.Option(
default="/sdcard",
help="The path to store xml files for determining UI elements localtion. Make sure it exists.",
),
device_id: str = typer.Option(default="emulator-5554", help="The Android device_id"),
):
config.extra = {
"stage": stage,
"mode": mode,
"app_name": app_name,
"task_desc": task_desc,
"refine_doc": refine_doc,
"min_dist": min_dist,
"android_screenshot_dir": android_screenshot_dir,
"android_xml_dir": android_xml_dir,
"device_id": device_id,
}
team = Team(
env=AndroidEnv(
device_id=device_id,
xml_dir=Path(android_xml_dir),
screenshot_dir=Path(android_screenshot_dir),
)
)
team.hire([AndroidAssistant(output_root_dir=Path(__file__).parent)])
team.invest(investment)
team.run_project(idea=task_desc)
asyncio.run(team.run(n_round=n_round))
if __name__ == "__main__":
app()

View file

@ -17,7 +17,7 @@ from metagpt.schema import Message
class SimpleWriteCode(Action):
PROMPT_TEMPLATE: str = """
Write a python function that can {instruction} and provide two runnnable test cases.
Write a python function that can {instruction} and provide two runnable test cases.
Return ```python your_code_here ``` with NO other texts,
your code:
"""

View file

@ -0,0 +1 @@
Bob likes traveling.

View file

@ -0,0 +1,109 @@
Productivity
I think I am at least somewhat more productive than average, and people sometimes ask me for productivity tips. So I decided to just write them all down in one place.
Compound growth gets discussed as a financial concept, but it works in careers as well, and it is magic. A small productivity gain, compounded over 50 years, is worth a lot. So its worth figuring out how to optimize productivity. If you get 10% more done and 1% better every day compared to someone else, the compounded difference is massive.
What you work on
Famous writers have some essential qualities, creativity and discipline
It doesnt matter how fast you move if its in a worthless direction. Picking the right thing to work on is the most important element of productivity and usually almost ignored. So think about it more! Independent thought is hard but its something you can get better at with practice.
The most impressive people I know have strong beliefs about the world, which is rare in the general population. If you find yourself always agreeing with whomever you last spoke with, thats bad. You will of course be wrong sometimes, but develop the confidence to stick with your convictions. It will let you be courageous when youre right about something important that most people dont see.
I make sure to leave enough time in my schedule to think about what to work on. The best ways for me to do this are reading books, hanging out with interesting people, and spending time in nature.
Ive learned that I cant be very productive working on things I dont care about or dont like. So I just try not to put myself in a position where I have to do them (by delegating, avoiding, or something else). Stuff that you dont like is a painful drag on morale and momentum.
By the way, here is an important lesson about delegation: remember that everyone else is also most productive when theyre doing what they like, and do what youd want other people to do for you—try to figure out who likes (and is good at) doing what, and delegate that way.
If you find yourself not liking what youre doing for a long period of time, seriously consider a major job change. Short-term burnout happens, but if it isnt resolved with some time off, maybe its time to do something youre more interested in.
Ive been very fortunate to find work I like so much Id do it for free, which makes it easy to be really productive.
Its important to learn that you can learn anything you want, and that you can get better quickly. This feels like an unlikely miracle the first few times it happens, but eventually you learn to trust that you can do it.
Doing great work usually requires colleagues of some sort. Try to be around smart, productive, happy, and positive people that dont belittle your ambitions. I love being around people who push me and inspire me to be better. To the degree you able to, avoid the opposite kind of people—the cost of letting them take up your mental cycles is horrific.
You have to both pick the right problem and do the work. There arent many shortcuts. If youre going to do something really important, you are very likely going to work both smart and hard. The biggest prizes are heavily competed for. This isnt true in every field (there are great mathematicians who never spend that many hours a week working) but it is in most.
Prioritization
Writers have to work hard to be successful
My system has three key pillars: “Make sure to get the important shit done”, “Dont waste time on stupid shit”, and “make a lot of lists”.
I highly recommend using lists. I make lists of what I want to accomplish each year, each month, and each day. Lists are very focusing, and they help me with multitasking because I dont have to keep as much in my head. If Im not in the mood for some particular task, I can always find something else Im excited to do.
I prefer lists written down on paper. Its easy to add and remove tasks. I can access them during meetings without feeling rude. I re-transcribe lists frequently, which forces me to think about everything on the list and gives me an opportunity to add and remove items.
I dont bother with categorization or trying to size tasks or anything like that (the most I do is put a star next to really important items).
I try to prioritize in a way that generates momentum. The more I get done, the better I feel, and then the more I get done. I like to start and end each day with something I can really make progress on.
I am relentless about getting my most important projects done—Ive found that if I really want something to happen and I push hard enough, it usually happens.
I try to be ruthless about saying no to stuff, and doing non-critical things in the quickest way possible. I probably take this too far—for example, I am almost sure I am terse to the point of rudeness when replying to emails.
Passion and adaptability are key qualities to writers
I generally try to avoid meetings and conferences as I find the time cost to be huge—I get the most value out of time in my office. However, it is critical that you keep enough space in your schedule to allow for chance encounters and exposure to new people and ideas. Having an open network is valuable; though probably 90% of the random meetings I take are a waste of time, the other 10% really make up for it.
I find most meetings are best scheduled for 15-20 minutes, or 2 hours. The default of 1 hour is usually wrong, and leads to a lot of wasted time.
I have different times of day I try to use for different kinds of work. The first few hours of the morning are definitely my most productive time of the day, so I dont let anyone schedule anything then. I try to do meetings in the afternoon. I take a break, or switch tasks, whenever I feel my attention starting to fade.
I dont think most people value their time enough—I am surprised by the number of people I know who make $100 an hour and yet will spend a couple of hours doing something they dont want to do to save $20.
Also, dont fall into the trap of productivity porn—chasing productivity for its own sake isnt helpful. Many people spend too much time thinking about how to perfectly optimize their system, and not nearly enough asking if theyre working on the right problems. It doesnt matter what system you use or if you squeeze out every second if youre working on the wrong thing.
The right goal is to allocate your year optimally, not your day.
Physical factors
Very likely what is optimal for me wont be optimal for you. Youll have to experiment to find out what works best for your body. Its definitely worth doing—it helps in all aspects of life, and youll feel a lot better and happier overall.
It probably took a little bit of my time every week for a few years to arrive at what works best for me, but my sense is if I do a good job at all the below Im at least 1.5x more productive than if not.
Sleep seems to be the most important physical factor in productivity for me. Some sort of sleep tracker to figure out how to sleep best is helpful. Ive found the only thing Im consistent with are in the set-it-and-forget-it category, and I really like the Emfit QS+Active.
I like a cold, dark, quiet room, and a great mattress (I resisted spending a bunch of money on a great mattress for years, which was stupid—it makes a huge difference to my sleep quality. I love this one). Not eating a lot in the few hours before sleep helps. Not drinking alcohol helps a lot, though Im not willing to do that all the time.
I use a Chili Pad to be cold while I sleep if I cant get the room cold enough, which is great but loud (I set it up to have the cooler unit outside my room).
When traveling, I use an eye mask and ear plugs.
Writers usually have empathy to write good books.
This is likely to be controversial, but I take a low dose of sleeping pills (like a third of a normal dose) or a very low dose of cannabis whenever I cant sleep. I am a bad sleeper in general, and a particularly bad sleeper when I travel. It likely has tradeoffs, but so does not sleeping well. If you can already sleep well, I wouldnt recommend this.
I use a full spectrum LED light most mornings for about 10-15 minutes while I catch up on email. Its great—if you try nothing else in here, this is the thing Id try. Its a ridiculous gain for me. I like this one, and its easy to travel with.
Exercise is probably the second most important physical factor. I tried a number of different exercise programs for a few months each and the one that seemed best was lifting heavy weights 3x a week for an hour, and high intensity interval training occasionally. In addition to productivity gains, this is also the exercise program that makes me feel the best overall.
The third area is nutrition. I very rarely eat breakfast, so I get about 15 hours of fasting most days (except an espresso when I wake up). I know this is contrary to most advice, and I suspect its not optimal for most people, but it definitely works well for me.
Eating lots of sugar is the thing that makes me feel the worst and that I try hardest to avoid. I also try to avoid foods that aggravate my digestion or spike up inflammation (for example, very spicy foods). I dont have much willpower when it comes to sweet things, so I mostly just try to keep junk food out of the house.
I have one big shot of espresso immediately when I wake up and one after lunch. I assume this is about 200mg total of caffeine per day. I tried a few other configurations; this was the one that worked by far the best. I otherwise aggressively avoid stimulants, but I will have more coffee if Im super tired and really need to get something done.
If a writer want to be super, then should include innovative thinking.
Im vegetarian and have been since I was a kid, and I supplement methyl B-12, Omega-3, Iron, and Vitamin D-3. I got to this list with a year or so of quarterly blood tests; its worked for me ever since (I re-test maybe every year and a half or so). There are many doctors who will happily work with you on a super comprehensive blood test (and services like WellnessFX). I also go out of my way to drink a lot of protein shakes, which I hate and I wouldnt do if I werent vegetarian.
Other stuff
Heres what I like in a workspace: natural light, quiet, knowing that I wont be interrupted if I dont want to be, long blocks of time, and being comfortable and relaxed (Ive got a beautiful desk with a couple of 4k monitors on it in my office, but I spend almost all my time on my couch with my laptop).
I wrote custom software for the annoying things I have to do frequently, which is great. I also made an effort to learn to type really fast and the keyboard shortcuts that help with my workflow.
Like most people, I sometimes go through periods of a week or two where I just have no motivation to do anything (I suspect it may have something to do with nutrition). This sucks and always seems to happen at inconvenient times. I have not figured out what to do about it besides wait for the fog to lift, and to trust that eventually it always does. And I generally try to avoid people and situations that put me in bad moods, which is good advice whether you care about productivity or not.
In general, I think its good to overcommit a little bit. I find that I generally get done what I take on, and if I have a little bit too much to do it makes me more efficient at everything, which is a way to train to avoid distractions (a great habit to build!). However, overcommitting a lot is disastrous.
Dont neglect your family and friends for the sake of productivity—thats a very stupid tradeoff (and very likely a net productivity loss, because youll be less happy). Dont neglect doing things you love or that clear your head either.
Finally, to repeat one more time: productivity in the wrong direction isnt worth anything at all. Think more about what to work on.
Open-Mindedness and curiosity are essential to writers

View file

@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from metagpt.roles.di.data_interpreter import DataInterpreter
async def main():
template = "https://arxiv.org/list/{tag}/pastweek?skip=0&show=300"
tags = ["cs.ai", "cs.cl", "cs.lg", "cs.se"]
urls = [template.format(tag=tag) for tag in tags]
prompt = f"""This is a collection of arxiv urls: '{urls}' .
Record each article, remove duplicates by title (they may have multiple tags), filter out papers related to
large language model / agent / llm, print top 100 and visualize the word count of the titles"""
di = DataInterpreter(react_mode="react", tools=["scrape_web_playwright"])
await di.run(prompt)
if __name__ == "__main__":
import asyncio
asyncio.run(main())

View file

@ -0,0 +1,36 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2024/3/22 10:54
@Author : alexanderwu
@File : custom_tool.py
"""
from metagpt.roles.di.data_interpreter import DataInterpreter
from metagpt.tools.tool_registry import register_tool
@register_tool()
def magic_function(arg1: str, arg2: int) -> dict:
"""
The magic function that does something.
Args:
arg1 (str): ...
arg2 (int): ...
Returns:
dict: ...
"""
return {"arg1": arg1 * 3, "arg2": arg2 * 5}
async def main():
di = DataInterpreter(tools=["magic_function"])
await di.run("Just call the magic function with arg1 'A' and arg2 2. Tell me the result.")
if __name__ == "__main__":
import asyncio
asyncio.run(main())

View file

@ -1,14 +1,17 @@
import asyncio
from metagpt.logs import logger
from metagpt.roles.di.data_interpreter import DataInterpreter
from metagpt.utils.recovery_util import save_history
async def main(requirement: str = ""):
di = DataInterpreter()
await di.run(requirement)
rsp = await di.run(requirement)
logger.info(rsp)
save_history(role=di)
if __name__ == "__main__":
requirement = "Run data analysis on sklearn Iris dataset, include a plot"
asyncio.run(main(requirement))

247
examples/rag_pipeline.py Normal file
View file

@ -0,0 +1,247 @@
"""RAG pipeline"""
import asyncio
from pydantic import BaseModel
from metagpt.const import DATA_PATH, EXAMPLE_DATA_PATH
from metagpt.logs import logger
from metagpt.rag.engines import SimpleEngine
from metagpt.rag.schema import (
ChromaIndexConfig,
ChromaRetrieverConfig,
ElasticsearchIndexConfig,
ElasticsearchRetrieverConfig,
ElasticsearchStoreConfig,
FAISSRetrieverConfig,
LLMRankerConfig,
)
from metagpt.utils.exceptions import handle_exception
DOC_PATH = EXAMPLE_DATA_PATH / "rag/writer.txt"
QUESTION = "What are key qualities to be a good writer?"
TRAVEL_DOC_PATH = EXAMPLE_DATA_PATH / "rag/travel.txt"
TRAVEL_QUESTION = "What does Bob like?"
LLM_TIP = "If you not sure, just answer I don't know."
class Player(BaseModel):
"""To demonstrate rag add objs."""
name: str = ""
goal: str = "Win The 100-meter Sprint."
tool: str = "Red Bull Energy Drink."
def rag_key(self) -> str:
"""For search"""
return self.goal
class RAGExample:
"""Show how to use RAG."""
def __init__(self, engine: SimpleEngine = None):
self._engine = engine
@property
def engine(self):
if not self._engine:
self._engine = SimpleEngine.from_docs(
input_files=[DOC_PATH],
retriever_configs=[FAISSRetrieverConfig()],
ranker_configs=[LLMRankerConfig()],
)
return self._engine
@engine.setter
def engine(self, value: SimpleEngine):
self._engine = value
async def run_pipeline(self, question=QUESTION, print_title=True):
"""This example run rag pipeline, use faiss retriever and llm ranker, will print something like:
Retrieve Result:
0. Productivi..., 10.0
1. I wrote cu..., 7.0
2. I highly r..., 5.0
Query Result:
Passion, adaptability, open-mindedness, creativity, discipline, and empathy are key qualities to be a good writer.
"""
if print_title:
self._print_title("Run Pipeline")
nodes = await self.engine.aretrieve(question)
self._print_retrieve_result(nodes)
answer = await self.engine.aquery(question)
self._print_query_result(answer)
async def add_docs(self):
"""This example show how to add docs.
Before add docs llm anwser I don't know.
After add docs llm give the correct answer, will print something like:
[Before add docs]
Retrieve Result:
Query Result:
Empty Response
[After add docs]
Retrieve Result:
0. Bob like..., 10.0
Query Result:
Bob likes traveling.
"""
self._print_title("Add Docs")
travel_question = f"{TRAVEL_QUESTION}{LLM_TIP}"
travel_filepath = TRAVEL_DOC_PATH
logger.info("[Before add docs]")
await self.run_pipeline(question=travel_question, print_title=False)
logger.info("[After add docs]")
self.engine.add_docs([travel_filepath])
await self.run_pipeline(question=travel_question, print_title=False)
@handle_exception
async def add_objects(self, print_title=True):
"""This example show how to add objects.
Before add docs, engine retrieve nothing.
After add objects, engine give the correct answer, will print something like:
[Before add objs]
Retrieve Result:
[After add objs]
Retrieve Result:
0. 100m Sprin..., 10.0
[Object Detail]
{'name': 'Mike', 'goal': 'Win The 100-meter Sprint', 'tool': 'Red Bull Energy Drink'}
"""
if print_title:
self._print_title("Add Objects")
player = Player(name="Mike")
question = f"{player.rag_key()}"
logger.info("[Before add objs]")
await self._retrieve_and_print(question)
logger.info("[After add objs]")
self.engine.add_objs([player])
try:
nodes = await self._retrieve_and_print(question)
logger.info("[Object Detail]")
player: Player = nodes[0].metadata["obj"]
logger.info(player.name)
except Exception as e:
logger.error(f"nodes is empty, llm don't answer correctly, exception: {e}")
async def init_objects(self):
"""This example show how to from objs, will print something like:
Same as add_objects.
"""
self._print_title("Init Objects")
pre_engine = self.engine
self.engine = SimpleEngine.from_objs(retriever_configs=[FAISSRetrieverConfig()])
await self.add_objects(print_title=False)
self.engine = pre_engine
async def init_and_query_chromadb(self):
"""This example show how to use chromadb. how to save and load index. will print something like:
Query Result:
Bob likes traveling.
"""
self._print_title("Init And Query ChromaDB")
# 1. save index
output_dir = DATA_PATH / "rag"
SimpleEngine.from_docs(
input_files=[TRAVEL_DOC_PATH],
retriever_configs=[ChromaRetrieverConfig(persist_path=output_dir)],
)
# 2. load index
engine = SimpleEngine.from_index(index_config=ChromaIndexConfig(persist_path=output_dir))
# 3. query
answer = await engine.aquery(TRAVEL_QUESTION)
self._print_query_result(answer)
@handle_exception
async def init_and_query_es(self):
"""This example show how to use es. how to save and load index. will print something like:
Query Result:
Bob likes traveling.
"""
self._print_title("Init And Query Elasticsearch")
# 1. create es index and save docs
store_config = ElasticsearchStoreConfig(index_name="travel", es_url="http://127.0.0.1:9200")
engine = SimpleEngine.from_docs(
input_files=[TRAVEL_DOC_PATH],
retriever_configs=[ElasticsearchRetrieverConfig(store_config=store_config)],
)
# 2. load index
engine = SimpleEngine.from_index(index_config=ElasticsearchIndexConfig(store_config=store_config))
# 3. query
answer = await engine.aquery(TRAVEL_QUESTION)
self._print_query_result(answer)
@staticmethod
def _print_title(title):
logger.info(f"{'#'*30} {title} {'#'*30}")
@staticmethod
def _print_retrieve_result(result):
"""Print retrieve result."""
logger.info("Retrieve Result:")
for i, node in enumerate(result):
logger.info(f"{i}. {node.text[:10]}..., {node.score}")
logger.info("")
@staticmethod
def _print_query_result(result):
"""Print query result."""
logger.info("Query Result:")
logger.info(f"{result}\n")
async def _retrieve_and_print(self, question):
nodes = await self.engine.aretrieve(question)
self._print_retrieve_result(nodes)
return nodes
async def main():
"""RAG pipeline"""
e = RAGExample()
await e.run_pipeline()
await e.add_docs()
await e.add_objects()
await e.init_objects()
await e.init_and_query_chromadb()
await e.init_and_query_es()
if __name__ == "__main__":
asyncio.run(main())

21
examples/rag_search.py Normal file
View file

@ -0,0 +1,21 @@
"""Agent with RAG search."""
import asyncio
from examples.rag_pipeline import DOC_PATH, QUESTION
from metagpt.logs import logger
from metagpt.rag.engines import SimpleEngine
from metagpt.roles import Sales
async def search():
"""Agent with RAG search."""
store = SimpleEngine.from_docs(input_files=[DOC_PATH])
role = Sales(profile="Sales", store=store)
result = await role.run(QUESTION)
logger.info(result)
if __name__ == "__main__":
asyncio.run(search())

View file

@ -1,33 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@File : search_kb.py
@Modified By: mashenquan, 2023-12-22. Delete useless codes.
"""
import asyncio
from langchain.embeddings import OpenAIEmbeddings
from metagpt.config2 import config
from metagpt.const import DATA_PATH, EXAMPLE_PATH
from metagpt.document_store import FaissStore
from metagpt.logs import logger
from metagpt.roles import Sales
def get_store():
llm = config.get_openai_llm()
embedding = OpenAIEmbeddings(openai_api_key=llm.api_key, openai_api_base=llm.base_url)
return FaissStore(DATA_PATH / "example.json", embedding=embedding)
async def search():
store = FaissStore(EXAMPLE_PATH / "example.json")
role = Sales(profile="Sales", store=store)
query = "Which facial cleanser is good for oily skin?"
result = await role.run(query)
logger.info(result)
if __name__ == "__main__":
asyncio.run(search())

View file

@ -13,7 +13,7 @@ async def main():
question = "What are the most interesting human facts?"
search = Config.default().search
kwargs = {"api_key": search.api_key, "cse_id": search.cse_id, "proxy": None}
kwargs = search.model_dump()
await Searcher(search_engine=SearchEngine(engine=search.api_type, **kwargs)).run(question)

View file

@ -0,0 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc :

View file

View file

@ -0,0 +1,94 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : entry of Stanford Town(ST/st) game
# README see `metagpt/ext/stanford_town/README.md`
import asyncio
from typing import Optional
import fire
from metagpt.ext.stanford_town.roles.st_role import STRole
from metagpt.ext.stanford_town.stanford_town import StanfordTown
from metagpt.ext.stanford_town.utils.const import STORAGE_PATH
from metagpt.ext.stanford_town.utils.mg_ga_transform import (
get_reverie_meta,
write_curr_sim_code,
write_curr_step,
)
from metagpt.ext.stanford_town.utils.utils import copy_folder
from metagpt.logs import logger
async def startup(
idea: str, fork_sim_code: str, sim_code: str, temp_storage_path: str, investment: float = 30.0, n_round: int = 500
):
town = StanfordTown()
logger.info("StanfordTown init environment")
# copy `storage/{fork_sim_code}` to `storage/{sim_code}`
copy_folder(str(STORAGE_PATH.joinpath(fork_sim_code)), str(STORAGE_PATH.joinpath(sim_code)))
# get role names from `storage/{simulation_name}/reverie/meta.json` and then init roles
reverie_meta = get_reverie_meta(fork_sim_code)
roles = []
sim_path = STORAGE_PATH.joinpath(sim_code)
sim_path.mkdir(exist_ok=True)
for idx, role_name in enumerate(reverie_meta["persona_names"]):
has_inner_voice = True if idx == 0 else False
role = STRole(
name=role_name,
profile=role_name,
sim_code=sim_code,
step=reverie_meta.get("step", 0),
start_time=reverie_meta.get("start_date"),
curr_time=reverie_meta.get("curr_time"),
sec_per_step=reverie_meta.get("sec_per_step"),
has_inner_voice=has_inner_voice,
)
roles.append(role)
# init temp_storage
write_curr_sim_code({"sim_code": sim_code}, temp_storage_path)
write_curr_step({"step": reverie_meta.get("step", 0)}, temp_storage_path)
await town.hire(roles)
town.invest(investment)
town.run_project(idea)
await town.run(n_round)
def main(
idea: str,
fork_sim_code: str,
sim_code: str,
temp_storage_path: Optional[str] = None,
investment: float = 30.0,
n_round: int = 500,
):
"""
Args:
idea: idea works as an `inner voice` to the first agent.
fork_sim_code: old simulation name to start with, choose one inside `generative_agents/environment/frontend_server/storage/`
sim_code: new simulation name to save simulation result
temp_storage_path: generative_agents temp_storage path inside `environment/frontend_server` to interact.
investment: the investment of running agents
n_round: rounds to run agents
"""
asyncio.run(
startup(
idea=idea,
fork_sim_code=fork_sim_code,
sim_code=sim_code,
temp_storage_path=temp_storage_path,
investment=investment,
n_round=n_round,
)
)
if __name__ == "__main__":
fire.Fire(main)

View file

@ -0,0 +1,4 @@
# path to store simulation data
test_*
unittest*
July*

View file

@ -0,0 +1,26 @@
{
"Isabella Rodriguez": {
"maze": "the_ville",
"x": 72,
"y": 14
},
"Klaus Mueller": {
"maze": "the_ville",
"x": 126,
"y": 46
},
"Maria Lopez": {
"maze": "the_ville",
"x": 123,
"y": 57
}
}

View file

@ -0,0 +1,51 @@
{
"vision_r": 8,
"att_bandwidth": 8,
"retention": 8,
"curr_time": null,
"curr_tile": null,
"daily_plan_req": "Isabella Rodriguez opens Hobbs Cafe at 8am everyday, and works at the counter until 8pm, at which point she closes the cafe.",
"name": "Isabella Rodriguez",
"first_name": "Isabella",
"last_name": "Rodriguez",
"age": 34,
"innate": "friendly, outgoing, hospitable",
"learned": "Isabella Rodriguez is a cafe owner of Hobbs Cafe who loves to make people feel welcome. She is always looking for ways to make the cafe a place where people can come to relax and enjoy themselves.",
"currently": "Isabella Rodriguez is planning on having a Valentine's Day party at Hobbs Cafe with her customers on February 14th, 2023 at 5pm. She is gathering party material, and is telling everyone to join the party at Hobbs Cafe on February 14th, 2023, from 5pm to 7pm.",
"lifestyle": "Isabella Rodriguez goes to bed around 11pm, awakes up around 6am.",
"living_area": "the Ville:Isabella Rodriguez's apartment:main room",
"concept_forget": 100,
"daily_reflection_time": 180,
"daily_reflection_size": 5,
"overlap_reflect_th": 4,
"kw_strg_event_reflect_th": 10,
"kw_strg_thought_reflect_th": 9,
"recency_w": 1,
"relevance_w": 1,
"importance_w": 1,
"recency_decay": 0.995,
"importance_trigger_max": 150,
"importance_trigger_curr": 150,
"importance_ele_n": 0,
"thought_count": 5,
"daily_req": [],
"f_daily_schedule": [],
"f_daily_schedule_hourly_org": [],
"act_address": null,
"act_start_time": null,
"act_duration": null,
"act_description": null,
"act_pronunciatio": null,
"act_event": ["Isabella Rodriguez", null, null],
"act_obj_description": null,
"act_obj_pronunciatio": null,
"act_obj_event": [null, null, null],
"chatting_with": null,
"chat": null,
"chatting_with_buffer": {},
"chatting_end_time": null,
"act_path_set": false,
"planned_path": []
}

View file

@ -0,0 +1,66 @@
{
"the Ville": {
"Hobbs Cafe": {
"cafe": [
"refrigerator",
"cafe customer seating",
"cooking area",
"kitchen sink",
"behind the cafe counter",
"piano"
]
},
"Isabella Rodriguez's apartment": {
"main room": [
"bed",
"desk",
"refrigerator",
"closet",
"shelf"
]
},
"The Rose and Crown Pub": {
"pub": [
"shelf",
"refrigerator",
"bar customer seating",
"behind the bar counter",
"kitchen sink",
"cooking area",
"microphone"
]
},
"Harvey Oak Supply Store": {
"supply store": [
"supply store product shelf",
"behind the supply store counter",
"supply store counter"
]
},
"The Willows Market and Pharmacy": {
"store": [
"behind the pharmacy counter",
"pharmacy store shelf",
"pharmacy store counter",
"grocery store shelf",
"behind the grocery counter",
"grocery store counter"
]
},
"Dorm for Oak Hill College": {
"garden": [
"dorm garden"
],
"common room": [
"common room sofa",
"pool table",
"common room table"
]
},
"Johnson Park": {
"park": [
"park garden"
]
}
}
}

View file

@ -0,0 +1,2 @@
{"kw_strength_event": {},
"kw_strength_thought": {}}

View file

@ -0,0 +1,51 @@
{
"vision_r": 8,
"att_bandwidth": 8,
"retention": 8,
"curr_time": null,
"curr_tile": null,
"daily_plan_req": "Klaus Mueller goes to the library at Oak Hill College early in the morning, spends his days writing, and eats at Hobbs Cafe.",
"name": "Klaus Mueller",
"first_name": "Klaus",
"last_name": "Mueller",
"age": 20,
"innate": "kind, inquisitive, passionate",
"learned": "Klaus Mueller is a student at Oak Hill College studying sociology. He is passionate about social justice and loves to explore different perspectives.",
"currently": "Klaus Mueller is writing a research paper on the effects of gentrification in low-income communities.",
"lifestyle": "Klaus Mueller goes to bed around 11pm, awakes up around 7am, eats dinner around 5pm.",
"living_area": "the Ville:Dorm for Oak Hill College:Klaus Mueller's room",
"concept_forget": 100,
"daily_reflection_time": 180,
"daily_reflection_size": 5,
"overlap_reflect_th": 4,
"kw_strg_event_reflect_th": 10,
"kw_strg_thought_reflect_th": 9,
"recency_w": 1,
"relevance_w": 1,
"importance_w": 1,
"recency_decay": 0.99,
"importance_trigger_max": 150,
"importance_trigger_curr": 150,
"importance_ele_n": 0,
"thought_count": 5,
"daily_req": [],
"f_daily_schedule": [],
"f_daily_schedule_hourly_org": [],
"act_address": null,
"act_start_time": null,
"act_duration": null,
"act_description": null,
"act_pronunciatio": null,
"act_event": ["Klaus Mueller", null, null],
"act_obj_description": null,
"act_obj_pronunciatio": null,
"act_obj_event": [null, null, null],
"chatting_with": null,
"chat": null,
"chatting_with_buffer": {},
"chatting_end_time": null,
"act_path_set": false,
"planned_path": []
}

View file

@ -0,0 +1,86 @@
{
"the Ville": {
"Oak Hill College": {
"hallway": [],
"library": [
"library sofa",
"library table",
"bookshelf"
],
"classroom": [
"blackboard",
"classroom podium",
"classroom student seating"
]
},
"Dorm for Oak Hill College": {
"garden": [
"dorm garden"
],
"Klaus Mueller's room": [
"bed",
"game console",
"closet",
"desk"
],
"woman's bathroom": [
"toilet",
"shower",
"bathroom sink"
],
"common room": [
"common room sofa",
"pool table",
"common room table"
],
"man's bathroom": [
"shower",
"bathroom sink",
"toilet"
]
},
"The Willows Market and Pharmacy": {
"store": [
"grocery store shelf",
"behind the grocery counter",
"grocery store counter",
"pharmacy store shelf",
"pharmacy store counter",
"behind the pharmacy counter"
]
},
"Harvey Oak Supply Store": {
"supply store": [
"supply store product shelf",
"behind the supply store counter",
"supply store counter"
]
},
"Johnson Park": {
"park": [
"park garden"
]
},
"The Rose and Crown Pub": {
"pub": [
"shelf",
"refrigerator",
"bar customer seating",
"behind the bar counter",
"kitchen sink",
"cooking area",
"microphone"
]
},
"Hobbs Cafe": {
"cafe": [
"refrigerator",
"cafe customer seating",
"cooking area",
"kitchen sink",
"behind the cafe counter",
"piano"
]
}
}
}

View file

@ -0,0 +1,2 @@
{"kw_strength_event": {},
"kw_strength_thought": {}}

View file

@ -0,0 +1,51 @@
{
"vision_r": 8,
"att_bandwidth": 8,
"retention": 8,
"curr_time": null,
"curr_tile": null,
"daily_plan_req": "Maria Lopez spends at least 3 hours a day Twitch streaming or gaming.",
"name": "Maria Lopez",
"first_name": "Maria",
"last_name": "Lopez",
"age": 21,
"innate": "energetic, enthusiastic, inquisitive",
"learned": "Maria Lopez is a student at Oak Hill College studying physics and a part time Twitch game streamer who loves to connect with people and explore new ideas.",
"currently": "Maria Lopez is working on her physics degree and streaming games on Twitch to make some extra money. She visits Hobbs Cafe for studying and eating just about everyday.",
"lifestyle": "Maria Lopez goes to bed around 2am, awakes up around 9am, eats dinner around 6pm. She likes to hang out at Hobbs Cafe if it's before 6pm.",
"living_area": "the Ville:Dorm for Oak Hill College:Maria Lopez's room",
"concept_forget": 100,
"daily_reflection_time": 180,
"daily_reflection_size": 5,
"overlap_reflect_th": 4,
"kw_strg_event_reflect_th": 10,
"kw_strg_thought_reflect_th": 9,
"recency_w": 1,
"relevance_w": 1,
"importance_w": 1,
"recency_decay": 0.99,
"importance_trigger_max": 150,
"importance_trigger_curr": 150,
"importance_ele_n": 0,
"thought_count": 5,
"daily_req": [],
"f_daily_schedule": [],
"f_daily_schedule_hourly_org": [],
"act_address": null,
"act_start_time": null,
"act_duration": null,
"act_description": null,
"act_pronunciatio": null,
"act_event": ["Maria Lopez", null, null],
"act_obj_description": null,
"act_obj_pronunciatio": null,
"act_obj_event": [null, null, null],
"chatting_with": null,
"chat": null,
"chatting_with_buffer": {},
"chatting_end_time": null,
"act_path_set": false,
"planned_path": []
}

View file

@ -0,0 +1,87 @@
{
"the Ville": {
"Oak Hill College": {
"hallway": [],
"library": [
"library sofa",
"library table",
"bookshelf"
],
"classroom": [
"blackboard",
"classroom podium",
"classroom student seating"
]
},
"Dorm for Oak Hill College": {
"garden": [
"dorm garden"
],
"Maria Lopez's room": [
"closet",
"desk",
"bed",
"computer",
"blackboard"
],
"woman's bathroom": [
"toilet",
"shower",
"bathroom sink"
],
"common room": [
"common room sofa",
"pool table",
"common room table"
],
"man's bathroom": [
"shower",
"bathroom sink",
"toilet"
]
},
"The Willows Market and Pharmacy": {
"store": [
"grocery store shelf",
"behind the grocery counter",
"grocery store counter",
"pharmacy store shelf",
"pharmacy store counter",
"behind the pharmacy counter"
]
},
"Harvey Oak Supply Store": {
"supply store": [
"supply store product shelf",
"behind the supply store counter",
"supply store counter"
]
},
"Johnson Park": {
"park": [
"park garden"
]
},
"The Rose and Crown Pub": {
"pub": [
"shelf",
"refrigerator",
"bar customer seating",
"behind the bar counter",
"kitchen sink",
"cooking area",
"microphone"
]
},
"Hobbs Cafe": {
"cafe": [
"refrigerator",
"cafe customer seating",
"cooking area",
"kitchen sink",
"behind the cafe counter",
"piano"
]
}
}
}

View file

@ -0,0 +1,13 @@
{
"fork_sim_code": "base_the_ville_isabella_maria_klaus",
"start_date": "February 13, 2023",
"curr_time": "February 13, 2023, 00:00:00",
"sec_per_step": 10,
"maze_name": "the_ville",
"persona_names": [
"Isabella Rodriguez",
"Maria Lopez",
"Klaus Mueller"
],
"step": 0
}

View file

@ -0,0 +1,117 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2024/3/27 9:44
@Author : leiwu30
@File : stream_output_via_api.py
@Description : Stream log information and communicate over the network via web api.
"""
import asyncio
import json
import socket
import threading
from contextvars import ContextVar
from flask import Flask, Response, jsonify, request, send_from_directory
from metagpt.const import TUTORIAL_PATH
from metagpt.logs import logger, set_llm_stream_logfunc
from metagpt.roles.tutorial_assistant import TutorialAssistant
from metagpt.utils.stream_pipe import StreamPipe
app = Flask(__name__)
def stream_pipe_log(content):
print(content, end="")
stream_pipe = stream_pipe_var.get(None)
if stream_pipe:
stream_pipe.set_message(content)
def write_tutorial(message):
async def main(idea, stream_pipe):
stream_pipe_var.set(stream_pipe)
role = TutorialAssistant()
await role.run(idea)
def thread_run(idea: str, stream_pipe: StreamPipe = None):
"""
Convert asynchronous function to thread function
"""
asyncio.run(main(idea, stream_pipe))
stream_pipe = StreamPipe()
thread = threading.Thread(
target=thread_run,
args=(
message["content"],
stream_pipe,
),
)
thread.start()
while thread.is_alive():
msg = stream_pipe.get_message()
yield stream_pipe.msg2stream(msg)
@app.route("/v1/chat/completions", methods=["POST"])
def completions():
"""
data: {
"model": "write_tutorial",
"stream": true,
"messages": [
{
"role": "user",
"content": "Write a tutorial about MySQL"
}
]
}
"""
data = json.loads(request.data)
logger.info(json.dumps(data, indent=4, ensure_ascii=False))
# Non-streaming interfaces are not supported yet
stream_type = True if data.get("stream") else False
if not stream_type:
return jsonify({"status": 400, "msg": "Non-streaming requests are not supported, please use `stream=True`."})
# Only accept the last user information
# openai['model'] ~ MetaGPT['agent']
last_message = data["messages"][-1]
model = data["model"]
# write_tutorial
if model == "write_tutorial":
return Response(write_tutorial(last_message), mimetype="text/plain")
else:
return jsonify({"status": 400, "msg": "No suitable agent found."})
@app.route("/download/<path:filename>")
def download_file(filename):
return send_from_directory(TUTORIAL_PATH, filename, as_attachment=True)
if __name__ == "__main__":
"""
curl https://$server_address:$server_port/v1/chat/completions -X POST -d '{
"model": "write_tutorial",
"stream": true,
"messages": [
{
"role": "user",
"content": "Write a tutorial about MySQL"
}
]
}'
"""
server_port = 7860
server_address = socket.gethostbyname(socket.gethostname())
set_llm_stream_logfunc(stream_pipe_log)
stream_pipe_var: ContextVar[StreamPipe] = ContextVar("stream_pipe")
app.run(port=server_port, host=server_address)

View file

@ -0,0 +1,218 @@
"""
Filename: MetaGPT/examples/werewolf_game/evals/eval.py
Created Date: Oct 18, 2023
Updated Date: Oct 24, 2023
Author: [Aria](https://github.com/ariafyy)
Info: eval the Voting Accuracy Rate of non_werewolves and Vote Difficulity
"""
import glob
import os
import re
from pathlib import Path
import numpy as np
import pandas as pd
from tqdm import tqdm
from utils import Utils
from metagpt.const import DEFAULT_WORKSPACE_ROOT, METAGPT_ROOT
from metagpt.environment.werewolf.const import RoleType
class Vote:
"""Vote Evaluation"""
def __init__(self):
self.OUT_PATH = DEFAULT_WORKSPACE_ROOT / "outputs"
os.makedirs(self.OUT_PATH, exist_ok=True)
self.SUB_FOLDER_LIST = ["01-10", "11-20", "21-30"]
def _get_log_fileslist(self, IN_PATH) -> list[str]:
files_list = []
for SUB_FOLDER in self.SUB_FOLDER_LIST:
files_list.extend(glob.glob(str(IN_PATH / SUB_FOLDER / "*.txt")))
return files_list
def extract_votes_from_logs(self, files_list: list):
for in_logfile in tqdm(files_list):
SUB_FOLDER = (Path(in_logfile).parent).stem
out_txtfile = self.OUT_PATH / "# {0}_{1}.txt".format(SUB_FOLDER, Path(in_logfile).stem)
Utils().pick_vote_log(in_logfile, out_txtfile)
votefiles_list = Utils().get_file_list(self.OUT_PATH)
return votefiles_list
@staticmethod
def parse_vote_text2chunks(text: str):
"""
parse each game vote log into text chunks
one chunk example:
['Player1', 'Player2', 'Player3', 'Player5', 'Player6']. Say ONLY: I vote to eliminate ...
Player1(Witch): 49 | I vote to eliminate Player5
Player2(Villager): 49 | I vote to eliminate Player5
Player3(Villager): 49 | I vote to eliminate Player5
Player5(Werewolf): 49 | I vote to eliminate Player6
Player6(Seer): 49 | I vote to eliminate Player5
"""
pattern = re.compile(r"""\[([^\]]+)\]. Say ONLY: I vote to eliminate ...""")
chunks = {}
chunk_id = 0
last_end = 0
for match in pattern.finditer(text):
start = match.start()
chunk = text[last_end:start]
chunks[f"vote_{chunk_id}"] = chunk.strip()
last_end = match.end()
chunk_id += 1
final_chunk = text[last_end:].strip()
if final_chunk:
chunks[f"vote_{chunk_id}"] = final_chunk
return chunks
def _vote_rate_players(self, text: str):
"""
# calculate the rate of goodteam vote werewolves
:example:
input:
['Player1', 'Player2', 'Player3', 'Player5', 'Player6']. Say ONLY: I vote to eliminate ...
Player1(Witch): 49 | I vote to eliminate Player5
Player2(Villager): 49 | I vote to eliminate Player5
Player3(Villager): 49 | I vote to eliminate Player5
Player5(Werewolf): 49 | I vote to eliminate Player6
Player6(Seer): 49 | I vote to eliminate Player5
output:
werewolves: ['Player5']
non_werewolves: ['Player1', 'Player2', 'Player3', 'Player6']
as you can see :Player2(Villager) and Player3(Villager) vote to eliminate Player5(Werewolf)
:return goodteam vote rateability: 100.00%
"""
pattern = re.compile(r"(\w+)\(([^\)]+)\): \d+ \| I vote to eliminate (\w+)")
# find all werewolves
werewolves = []
for match in pattern.finditer(text):
if match.group(2) == RoleType.WEREWOLF.value:
werewolves.append(match.group(1))
# find all non_werewolves
non_werewolves = []
for match in pattern.finditer(text):
if match.group(2) != RoleType.WEREWOLF.value:
non_werewolves.append(match.group(1))
num_non_werewolves = len(non_werewolves)
# count players other than werewolves made the correct votes
correct_votes = 0
for match in pattern.finditer(text):
if match.group(2) != RoleType.WEREWOLF.value and match.group(3) in werewolves:
correct_votes += 1
# cal the rateability of non_werewolves
rate = correct_votes / num_non_werewolves
good_vote_rate = round(rate, 2)
return {"good_vote_rate": good_vote_rate, "werewolves": werewolves, "non_werewolves": non_werewolves}
def get_goodteam_vote_rate(self, text: str) -> float:
goodteam_vote_rate = self._vote_rate_players(text)["good_vote_rate"]
return goodteam_vote_rate
def get_werewolves(self, text: str) -> list:
werewolves_list = self._vote_rate_players(text)["werewolves"]
return werewolves_list
def get_non_werewolves(self, text: str) -> list:
non_werewolves_list = self._vote_rate_players(text)["non_werewolves"]
return non_werewolves_list
def get_votewolf_difficulty(self, werewolves: list, non_werewolves: list) -> str:
num_living_wolfs = len(werewolves)
num_living_players = len(werewolves) + len(non_werewolves)
votewolf_difficulty = "_{0} / {1}".format(num_living_wolfs, num_living_players)
return votewolf_difficulty
def get_result_df(self, out_txtfile: str) -> pd.DataFrame:
"""
folder: sub folders for evals
file: evaluation file, each file represents one game
votes: the number of votes, eg. vote_1 represents the first vote of this game,
good_vote_rate:the rateability of a good person voting against a werewolf,
correct_votes / the total number of players other than werewolves
total_votes:the total number of votes cast
"""
with open(out_txtfile, "r") as out_file:
text = out_file.read()
chunks = self.parse_vote_text2chunks(text)
res = []
for k, v in chunks.items():
if v != "":
chunks_list = list(chunks.keys())
total_votes = len(chunks_list) - 1
werewolves = self.get_werewolves(v)
non_werewolves = self.get_non_werewolves(v)
good_vote_rate = self.get_goodteam_vote_rate(v)
votewolf_difficulty = self.get_votewolf_difficulty(werewolves, non_werewolves)
folder = Utils().filename_to_foldername(out_txtfile)
result = {
"folder": folder,
"file": Path(out_txtfile).stem + ".txt",
"vote_round": k,
"good_vote_rate": good_vote_rate,
"total_votes": total_votes,
"votewolf_difficulty": votewolf_difficulty,
}
res.append(result)
df = pd.DataFrame(res)
return df
def calc_avg_rate(self, IN_PATH) -> pd.DataFrame:
"""
get avg_rate for each game
avg_rate : the good_rate/total number of votes in the game
vote1_rate: First Round Voting Accuracy Rate
"""
infiles_list = self._get_log_fileslist(IN_PATH)
votefiles_list = self.extract_votes_from_logs(infiles_list)
df_list = [self._load_df_from_file(file) for file in votefiles_list]
combined_df = pd.concat(df_list, ignore_index=True)
# calculate the average good_vote_rate for each file
mean_rates = self._calculate_mean_rates(combined_df)
combined_df["avg_rate"] = combined_df["file"].map(mean_rates)
# calculate vote1 rate
vote1_rates = self._calc_vote1_rates(combined_df)
combined_df["vote1_rate"] = combined_df["folder"].map(vote1_rates.set_index("folder")["good_vote_rate"])
combined_df.loc[combined_df["vote_round"] != "vote_1", "vote1_rate"] = np.nan
combined_df["vote1_rate"] = combined_df["vote1_rate"].apply(self._format_rates)
combined_df["good_vote_rate"] = combined_df["good_vote_rate"].apply(self._format_rates)
combined_df["avg_rate"] = combined_df["avg_rate"].apply(self._format_rates)
combined_df.sort_values(["file"], ascending=True, inplace=True)
return combined_df
def _calc_vote1_rates(self, df):
df_vote1 = df[df["vote_round"] == "vote_1"]
vote1_rates = df_vote1.groupby("folder")["good_vote_rate"].mean().reset_index()
return vote1_rates
def _load_df_from_file(self, file):
return self.get_result_df(file)
def _calculate_mean_rates(self, df):
return df.groupby("file")["good_vote_rate"].mean()
def _format_rates(self, s):
return Utils().float_to_percent(s)
def get_eval_csv(self, IN_PATH, EVAL_RESULT):
"""
IN_PATH : parent folder of ["01-10", "11-20", "21-30"]
EVAL_RESULT : output csv file path
"""
combined_df = self.calc_avg_rate(IN_PATH)
combined_df.to_csv(EVAL_RESULT, index=False)
if __name__ == "__main__":
IN_PATH = METAGPT_ROOT / "examples/werewolf_game/evals"
EVAL_RESULT = DEFAULT_WORKSPACE_ROOT / "outputs" / "goodteam_vote_rate.csv"
Vote().get_eval_csv(IN_PATH, EVAL_RESULT)

View file

@ -0,0 +1,134 @@
"""
Filename: MetaGPT/examples/werewolf_game/evals/utils.py
Created Date: Oct 11, 2023
Revised Date: Oct 20, 2023
Author: [Aria](https://github.com/ariafyy)
"""
import glob
import os
import re
from pathlib import Path
from metagpt.const import METAGPT_ROOT
class Utils:
"""Utils: utils of logs"""
@staticmethod
def polish_log(in_logfile, out_txtfile):
"""polish logs for evaluation"""
pattern_text = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| (\w+) +\| ([\w\.]+:\w+:\d+) - (.*\S)"
pattern_player = r"(Player(\d{1}): \w+)"
pattern_start = False
json_start = False
with open(in_logfile, "r") as f, open(out_txtfile, "w") as out:
for line in f.readlines():
matches = re.match(pattern_text, line)
if matches:
message = matches.group(4).strip()
pattern_start = True
json_start = False
if (
"Moderator(Moderator) ready to InstructSpeak" not in message
and "Moderator(Moderator) ready to ParseSpeak" not in message
and "Total running cost:" not in message
):
out.write("- " + message + "\n")
else:
out.write("\n")
elif pattern_start and not matches:
if "gpt-4 may update over time" in line:
line = ""
out.write(line)
elif line.strip().startswith("{"):
out.write(line.strip())
json_start = True
elif json_start and not line.strip().endswith("}"):
out.write(line.strip())
elif json_start and line.strip().endswith("}"):
out.write(line.strip())
json_start = False
elif (
line.startswith("(User):") or line.startswith("********** STEP:") or re.search(pattern_player, line)
):
out.write(line)
else:
out.write("\n")
@staticmethod
def pick_vote_log(in_logfile, out_txtfile):
"""
pick the vote log from the log file.
ready to AnnounceGameResult serves as the 'HINT_TEXT ' which indicates the end of the game.
based on bservation and reflection, then discuss is not in vote session.
"""
pattern_vote = r"(Player\d+)\(([A-Za-z]+)\): (\d+) \| (I vote to eliminate Player\d+)"
ignore_text = """reflection"""
HINT_TEXT = r"ready to AnnounceGameResult"
pattern_moderator = r"\[([^\]]+)\]\. Say ONLY: I vote to eliminate ..."
in_valid_block = False
with open(in_logfile, "r") as f:
lines = f.read()
split_lines = lines.split(HINT_TEXT)
if len(split_lines) < 2:
print(f"Key text :{HINT_TEXT} not found in {in_logfile}")
return
relevant_lines = split_lines[1].split("\n")
with open(out_txtfile, "w") as out:
for line in relevant_lines:
if re.search(pattern_moderator, line):
in_valid_block = True
out.write(line.lstrip() + "\n")
elif in_valid_block and re.search(pattern_vote, line):
out.write(line + "\n")
elif ignore_text in line:
in_valid_block = False
@staticmethod
def get_file_list(path: str) -> list:
file_pattern = os.path.join(path, "*.txt")
files_list = glob.glob(file_pattern)
return files_list
@staticmethod
def filename_to_foldername(out_txtfile: str):
"""
convert filename into its parent folder name
input:"....../# 01-10_10132100.txt"
output:# 01-10
"""
s = Path(out_txtfile).stem
pattern_folder = r"([^_]*)_"
match = re.match(pattern_folder, s)
if match:
folder = match.group(1)
return folder
@staticmethod
def float_to_percent(decimal: float) -> str:
"""
input: 1.00
output: 100.00%
"""
percent = decimal * 100
return f"{percent:.2f}%"
if __name__ == "__main__":
in_logfile = METAGPT_ROOT / "logs/log.txt"
out_txtfile = "input your wish path"
# Utils().polish_log(in_logfile, out_txtfile)
Utils().pick_vote_log(in_logfile, out_txtfile)

View file

@ -0,0 +1,68 @@
import asyncio
import fire
from metagpt.ext.werewolf.roles import Guard, Moderator, Seer, Villager, Werewolf, Witch
from metagpt.ext.werewolf.roles.human_player import prepare_human_player
from metagpt.ext.werewolf.werewolf_game import WerewolfGame
from metagpt.logs import logger
async def start_game(
investment: float = 3.0,
n_round: int = 5,
shuffle: bool = True,
add_human: bool = False,
use_reflection: bool = True,
use_experience: bool = False,
use_memory_selection: bool = False,
new_experience_version: str = "",
):
game = WerewolfGame()
game_setup, players = game.env.init_game_setup(
role_uniq_objs=[Villager, Werewolf, Guard, Seer, Witch],
num_werewolf=2,
num_villager=2,
shuffle=shuffle,
add_human=add_human,
use_reflection=use_reflection,
use_experience=use_experience,
use_memory_selection=use_memory_selection,
new_experience_version=new_experience_version,
prepare_human_player=prepare_human_player,
)
logger.info(f"{game_setup}")
players = [Moderator()] + players
game.hire(players)
game.invest(investment)
game.run_project(game_setup)
await game.run(n_round=n_round)
def main(
investment: float = 20.0,
n_round: int = 100,
shuffle: bool = True,
add_human: bool = False,
use_reflection: bool = True,
use_experience: bool = False,
use_memory_selection: bool = False,
new_experience_version: str = "",
):
asyncio.run(
start_game(
investment,
n_round,
shuffle,
add_human,
use_reflection,
use_experience,
use_memory_selection,
new_experience_version,
)
)
if __name__ == "__main__":
fire.Fire(main)