diff --git a/docs/examples/features/private-flows.mdx b/docs/examples/features/private-flows.mdx index e0c0db4d..fdbe4594 100644 --- a/docs/examples/features/private-flows.mdx +++ b/docs/examples/features/private-flows.mdx @@ -1,5 +1,5 @@ --- -title: Private flows +title: Private Flows description: Create isolated execution environments within your workflows. icon: lock --- diff --git a/docs/examples/features/tools.mdx b/docs/examples/features/tools.mdx index f60dc1c7..d095feb2 100644 --- a/docs/examples/features/tools.mdx +++ b/docs/examples/features/tools.mdx @@ -1,5 +1,5 @@ --- -title: Custom tools +title: Custom Tools description: Provide tools to expand agent capabilities. icon: wrench --- diff --git a/docs/examples/library.mdx b/docs/examples/library.mdx deleted file mode 100644 index 7940293a..00000000 --- a/docs/examples/library.mdx +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Library ---- - - - - Play a game of rock, paper, scissors against an AI - without letting it cheat. - - - Two agents cooperate to route customer calls to the correct department. - - - An autonomous software engineer that creates applications based on your input. - - - More examples are on the way! - - diff --git a/docs/examples/seinfeld-conversation.mdx b/docs/examples/seinfeld-conversation.mdx new file mode 100644 index 00000000..1a837194 --- /dev/null +++ b/docs/examples/seinfeld-conversation.mdx @@ -0,0 +1,127 @@ +--- +title: Seinfeld Conversation +description: Simulate a conversation between Seinfeld characters using multiple AI agents. +icon: comments +--- + +This example demonstrates how to use ControlFlow to create a multi-agent conversation simulating the characters from the TV show Seinfeld. It showcases the use of multiple agents with distinct personalities, a task-based conversation flow, and command-line interaction. + +## Code + +The following code creates a conversation between Jerry, George, Elaine, Kramer, and Newman, discussing a given topic: + +```python +import sys +from controlflow import Agent, Task, flow + +jerry = Agent( + name="Jerry", + description="The observational comedian and natural leader.", + instructions=""" + You are Jerry from the show Seinfeld. You excel at observing the quirks of + everyday life and making them amusing. You are rational, often serving as + the voice of reason among your friends. Your objective is to moderate the + conversation, ensuring it stays light and humorous while guiding it toward + constructive ends. + """, +) + +george = Agent( + name="George", + description="The neurotic and insecure planner.", + instructions=""" + You are George from the show Seinfeld. You are known for your neurotic + tendencies, pessimism, and often self-sabotaging behavior. Despite these + traits, you occasionally offer surprising wisdom. Your objective is to + express doubts and concerns about the conversation topics, often envisioning + the worst-case scenarios, adding a layer of humor through your exaggerated + anxieties. + """, +) + +elaine = Agent( + name="Elaine", + description="The confident and independent thinker.", + instructions=""" + You are Elaine from the show Seinfeld. You are bold, witty, and unafraid to + challenge social norms. You often take a no-nonsense approach to issues but + always with a comedic twist. Your objective is to question assumptions, push + back against ideas you find absurd, and inject sharp humor into the + conversation. + """, +) + +kramer = Agent( + name="Kramer", + description="The quirky and unpredictable idea generator.", + instructions=""" + You are Kramer from the show Seinfeld. Known for your eccentricity and + spontaneity, you often come up with bizarre yet creative ideas. Your + unpredictable nature keeps everyone guessing what you'll do or say next. + Your objective is to introduce unusual and imaginative ideas into the + conversation, providing comic relief and unexpected insights. + """, +) + +newman = Agent( + name="Newman", + description="The antagonist and foil to Jerry.", + instructions=""" + You are Newman from the show Seinfeld. You are Jerry's nemesis, often + serving as a source of conflict and comic relief. Your objective is to + challenge Jerry's ideas, disrupt the conversation, and introduce chaos and + absurdity into the group dynamic. + """, +) + +@flow +def demo(topic: str): + task = Task( + "Discuss a topic", + agents=[jerry, george, elaine, kramer, newman], + completion_agents=[jerry], + result_type=None, + context=dict(topic=topic), + instructions="Every agent should speak at least once. only one agent per turn. Keep responses 1-2 paragraphs max.", + ) + task.run() + +if __name__ == "__main__": + if len(sys.argv) > 1: + topic = sys.argv[1] + else: + topic = "sandwiches" + + print(f"Topic: {topic}") + demo(topic=topic) +``` + +## Key concepts + +This implementation showcases several important ControlFlow features: + +1. **Multiple agents**: We create five distinct agents, each with their own personality and objectives, mirroring the characters from Seinfeld. + +2. **Agent instructions**: Each agent has detailed instructions that guide their behavior and responses, ensuring they stay in character. + +3. **Task-based conversation**: The conversation is structured as a task, with specific instructions for how the agents should interact. + +4. **Completion agent**: Jerry is designated as the completion agent, giving him the role of moderating and concluding the conversation. + +5. **Command-line interaction**: The script accepts a topic as a command-line argument, allowing for easy customization of the conversation subject. + +## Running the example + +You can run this example with a custom topic: + +```bash +python examples/seinfeld.py "coffee shops" +``` + +Or use the default topic ("sandwiches") by running it without arguments: + +```bash +python examples/seinfeld.py +``` + +This example demonstrates how ControlFlow can be used to create complex, multi-agent interactions that simulate realistic conversations between distinct personalities. It's a fun and engaging way to showcase the capabilities of AI in generating dynamic, character-driven dialogues. \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index 21451762..748e80e0 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -101,8 +101,9 @@ "pages": [ "examples/language-tutor", "examples/rock-paper-scissors", - "examples/agent-engineer", - "examples/call-routing" + "examples/seinfeld-conversation", + "examples/call-routing", + "examples/agent-engineer" ] }, { diff --git a/examples/anonymization.py b/examples/anonymization.py new file mode 100644 index 00000000..c06a759c --- /dev/null +++ b/examples/anonymization.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel, Field + +import controlflow as cf + + +class AnonymizationResult(BaseModel): + original: str + anonymized: str + replacements: dict[str, str] = Field( + description=r"The replacements made during anonymization, {original} -> {placeholder}" + ) + + +def anonymize_text(text: str) -> AnonymizationResult: + return cf.run( + "Anonymize the given text by replacing personal information with generic placeholders", + result_type=AnonymizationResult, + context={"text": text}, + ) + + +if __name__ == "__main__": + original_text = "John Doe, born on 05/15/1980, lives at 123 Main St, New York. His email is john.doe@example.com." + + result = anonymize_text(original_text) + print(f"Original: {result.original}") + print(f"Anonymized: {result.anonymized}") + print("Replacements:") + for original, placeholder in result.replacements.items(): + print(f" {original} -> {placeholder}") diff --git a/examples/business_headline_sentiment.py b/examples/business_headline_sentiment.py deleted file mode 100644 index 995cb8fb..00000000 --- a/examples/business_headline_sentiment.py +++ /dev/null @@ -1,38 +0,0 @@ -# uv pip install langchain-community, duckduckgo-search - -from langchain_community.tools import DuckDuckGoSearchRun - -import controlflow as cf - -summarizer = cf.Agent( - name="Headline Summarizer", - description="An AI agent that fetches and summarizes current events", - tools=[DuckDuckGoSearchRun()], -) - -extractor = cf.Agent( - name="Entity Extractor", - description="An AI agent that does named entity recognition", -) - - -@cf.flow -def get_headlines(): - summarizer_task = cf.Task( - "Retrieve and summarize today's two top business headlines", - agent=summarizer, - result_type=list[str], - ) - - extractor_task = cf.Task( - "Extract any fortune 500 companies mentioned in the headlines and whether the sentiment is positive, neutral, or negative", - agent=extractor, - depends_on=[summarizer_task], - ) - - return summarizer_task, extractor_task - - -if __name__ == "__main__": - headlines, entity_sentiment = get_headlines() - print(headlines, entity_sentiment) diff --git a/examples/call_routing.py b/examples/call_routing.py new file mode 100644 index 00000000..965bad95 --- /dev/null +++ b/examples/call_routing.py @@ -0,0 +1,78 @@ +import random + +import controlflow as cf + +DEPARTMENTS = [ + "Sales", + "Support", + "Billing", + "Returns", +] + + +@cf.flow +def routing_flow(): + target_department = random.choice(DEPARTMENTS) + + print(f"\n---\nThe target department is: {target_department}\n---\n") + + customer = cf.Agent( + name="Customer", + instructions=f""" + You are training customer reps by pretending to be a customer + calling into a call center. You need to be routed to the + {target_department} department. Come up with a good backstory. + """, + ) + + trainee = cf.Agent( + name="Trainee", + instructions=""", + You are a trainee customer service representative. You need to + listen to the customer's story and route them to the correct + department. Note that the customer is another agent training you. + """, + ) + + with cf.Task( + "Route the customer to the correct department.", + agents=[trainee], + result_type=DEPARTMENTS, + ) as main_task: + while main_task.is_incomplete(): + cf.run( + "Talk to the trainee.", + instructions=( + "Post a message to talk. In order to help the trainee " + "learn, don't be direct about the department you want. " + "Instead, share a story that will let them practice. " + "After you speak, mark this task as complete." + ), + agents=[customer], + result_type=None, + ) + + cf.run( + "Talk to the customer.", + instructions=( + "Post a message to talk. Ask questions to learn more " + "about the customer. After you speak, mark this task as " + "complete. When you have enough information, use the main " + "task tool to route the customer to the correct department." + ), + agents=[trainee], + result_type=None, + tools=[main_task.get_success_tool()], + ) + + if main_task.result == target_department: + print("Success! The customer was routed to the correct department.") + else: + print( + f"Failed. The customer was routed to the wrong department. " + f"The correct department was {target_department}." + ) + + +if __name__ == "__main__": + routing_flow() diff --git a/examples/choose_a_number.py b/examples/choose_a_number.py deleted file mode 100644 index fe8f5e82..00000000 --- a/examples/choose_a_number.py +++ /dev/null @@ -1,16 +0,0 @@ -from controlflow import Agent, Task, flow - -a1 = Agent(name="A1", instructions="You struggle to make decisions.") -a2 = Agent( - name="A2", - instructions="You like to make decisions.", -) - - -@flow -def demo(): - task = Task("choose a number between 1 and 100", agents=[a1, a2], result_type=int) - return task.run() - - -demo() diff --git a/examples/code_explanation.py b/examples/code_explanation.py new file mode 100644 index 00000000..0d47c260 --- /dev/null +++ b/examples/code_explanation.py @@ -0,0 +1,31 @@ +from pydantic import BaseModel + +import controlflow as cf + + +class CodeExplanation(BaseModel): + code: str + explanation: str + language: str + + +def explain_code(code: str, language: str = None) -> CodeExplanation: + return cf.run( + f"Explain the following code snippet", + result_type=CodeExplanation, + context={"code": code, "language": language or "auto-detect"}, + ) + + +if __name__ == "__main__": + code_snippet = """ + def fibonacci(n): + if n <= 1: + return n + else: + return fibonacci(n-1) + fibonacci(n-2) + """ + + result = explain_code(code_snippet, "Python") + print(f"Code:\n{result.code}\n") + print(f"Explanation:\n{result.explanation}") diff --git a/examples/controlflow_docs.py b/examples/controlflow_docs.py deleted file mode 100644 index cedbf95c..00000000 --- a/examples/controlflow_docs.py +++ /dev/null @@ -1,96 +0,0 @@ -from pathlib import Path - -from langchain_openai import OpenAIEmbeddings - -import controlflow as cf -from controlflow.tools import tool - -try: - from langchain_community.document_loaders import DirectoryLoader - from langchain_community.vectorstores import LanceDB - from langchain_text_splitters import ( - MarkdownTextSplitter, - PythonCodeTextSplitter, - ) -except ImportError: - raise ImportError( - "Missing requirements: `pip install lancedb langchain-community langchain-text-splitters unstructured`" - ) - - -def create_code_db(): - # .py files - py_loader = DirectoryLoader( - Path(cf.__file__).parents[2] / "src/controlflow/", glob="**/*.py" - ) - py_raw_documents = py_loader.load() - py_splitter = PythonCodeTextSplitter(chunk_size=1400, chunk_overlap=200) - documents = py_splitter.split_documents(py_raw_documents) - return LanceDB.from_documents(documents, OpenAIEmbeddings()) - - -def create_docs_db(): - # .mdx files - mdx_loader = DirectoryLoader(Path(cf.__file__).parents[2] / "docs", glob="**/*.mdx") - mdx_raw_documents = mdx_loader.load() - mdx_splitter = MarkdownTextSplitter(chunk_size=1400, chunk_overlap=200) - documents = mdx_splitter.split_documents(mdx_raw_documents) - return LanceDB.from_documents(documents, OpenAIEmbeddings()) - - -code_db = create_code_db() -docs_db = create_docs_db() - - -@tool -def search_code(query: str, n=50) -> list[dict]: - """ - Semantic search over the current ControlFlow documentation - - Returns the top `n` results. - """ - results = docs_db.similarity_search(query, k=n) - return [ - dict(content=r.page_content, metadata=r.metadata["metadata"]) for r in results - ] - - -@tool -def search_docs(query: str, n=50) -> list[dict]: - """ - Semantic search over the current ControlFlow documentation - - Returns the top `n` results. - """ - results = docs_db.similarity_search(query, k=n) - return [ - dict(content=r.page_content, metadata=r.metadata["metadata"]) for r in results - ] - - -@tool -def read_file(path: str) -> str: - """ - Read a file from a path. - """ - with open(path) as f: - return f.read() - - -agent = cf.Agent( - "DocsAgent", - description="The agent for the ControlFlow documentation", - instructions="Use your tools to explore the ControlFlow code and documentation. If you find something interesting but only see a snippet with the search tools, use the read_file tool to get the full text.", - tools=[search_code, search_docs, read_file], -) - - -@cf.flow -def write_docs(topic: str): - task = cf.Task( - "Research the provided topic, then produce world-class documentation in the style of the existing docs.", - context=dict(topic=topic), - agents=[agent], - ) - task.generate_subtasks() - return task diff --git a/examples/engineer/engineer.py b/examples/engineer/engineer.py deleted file mode 100644 index 3dadfeb9..00000000 --- a/examples/engineer/engineer.py +++ /dev/null @@ -1,76 +0,0 @@ -from pathlib import Path - -from pydantic import BaseModel - -import controlflow as cf -import controlflow.tools.code -import controlflow.tools.filesystem - -# load the instructions -instructions = open(Path(__file__).parent / "instructions.md").read() - -# create the agent -agent = cf.Agent( - "Engineer", - instructions=instructions, - tools=[ - *controlflow.tools.filesystem.ALL_TOOLS, - controlflow.tools.code.python, - controlflow.tools.code.shell, - ], -) - - -class DesignDoc(BaseModel): - goals: str - design: str - implementation_details: str - criteria: str - - -@cf.flow -def run_engineer(): - # the first task is to work with the user to create a design doc - design_doc = cf.Task( - "Learn about the software the user wants to build", - instructions=""" - Interact with the user to understand the software they want to - build. What is its purpose? What language should you use? What does - it need to do? Engage in a natural conversation to collect as much - or as little information as the user wants to share. Once you have - enough, write out a design document to complete the task. - """, - interactive=True, - result_type=DesignDoc, - ) - - # next we create a directory for any files - mkdir = cf.Task( - "Create a directory for the software", - instructions=""" - Create a directory to store the software and any related files. The - directory should be named after the software. Return the path. - """, - result_type=str, - tools=[controlflow.tools.filesystem.mkdir], - agents=[agent], - ) - - # the final task is to write the software - software = cf.Task( - "Finish the software", - instructions=""" - Mark this task complete when the software runs as expected and the - user can invoke it. Until then, continue to build the software. - - All files must be written to the provided root directory. - """, - result_type=None, - context=dict(design_doc=design_doc, root_dir=mkdir), - agents=[agent], - ) - return software - - -if __name__ == "__main__": - run_engineer() diff --git a/examples/engineer/instructions.md b/examples/engineer/instructions.md deleted file mode 100644 index 92384940..00000000 --- a/examples/engineer/instructions.md +++ /dev/null @@ -1,37 +0,0 @@ -# Software Engineer Agent - -## Role and Purpose -You are a software engineer specialized in leveraging large language models (LLMs) to transform user ideas into fully functional software projects. Your primary role involves understanding user requirements, setting up project environments, writing necessary files, executing code, and iteratively refining the software to meet user expectations. - -## Process Overview -1. **Understanding the User's Idea**: - - **Engage in Clarification**: Ask targeted questions to grasp the core functionality, expected outcomes, and specific requirements of the user's idea. - - **Requirement Documentation**: Summarize the user’s concept into detailed requirements, including features, constraints, and any preferred technologies or frameworks. - -2. **Setting Up the Project**: - - **Initialize Project Structure**: Create a logical directory structure for the project, ensuring separation of concerns (e.g., `src/` for source code, `docs/` for documentation). - - **Environment Configuration**: Set up the development environment, including the creation of virtual environments, installation of necessary dependencies, and configuration of development tools (e.g., linters, formatters). - -3. **Writing Code and Files**: - - **Code Generation**: Write clean, efficient, and modular code based on the documented requirements. Ensure that code adheres to best practices and coding standards. - - **Documentation**: Create comprehensive documentation for the code, including docstrings, README files, and usage guides to facilitate understanding and future maintenance. - -4. **Executing and Testing**: - - **Initial Execution**: Run the code in the development environment to ensure it executes correctly and meets the primary requirements. - - **Debugging**: Identify and resolve any bugs or issues that arise during execution. Ensure the code runs smoothly and performs as expected. - -5. **Editing and Improving**: - - **Iterative Refinement**: Based on user feedback and testing outcomes, iteratively improve the software. This may involve refactoring code, optimizing performance, and adding new features. - - **Code Reviews**: Conduct thorough code reviews to maintain code quality and consistency. Incorporate feedback from peers to enhance the overall robustness of the software. - - **User Feedback Integration**: Actively seek and integrate feedback from the user to ensure the software evolves in alignment with their vision. - -## Best Practices -- **Clear Communication**: Maintain clear and continuous communication with the user to ensure alignment on goals and expectations. -- **Modular Design**: Write modular and reusable code to facilitate future enhancements and maintenance. - -## Tools and Technologies -- **Programming Languages**: Use appropriate programming languages based on project requirements (e.g., Python, JavaScript). -- **Frameworks and Libraries**: Leverage relevant frameworks and libraries to accelerate development (e.g., Django, React, TensorFlow). -- **Development Tools**: Utilize integrated development environments (IDEs) and project management tools to streamline the development process. - -By adhering to this structured approach and best practices, you will efficiently transform user ideas into high-quality, functional software solutions, ensuring user satisfaction and project success. \ No newline at end of file diff --git a/examples/generate_people.py b/examples/generate_people.py new file mode 100644 index 00000000..a136ea3a --- /dev/null +++ b/examples/generate_people.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel, Field + +import controlflow as cf + + +class UserProfile(BaseModel): + name: str = Field(description="The full name of the user") + age: int = Field(description="The age of the user, 20-60") + occupation: str = Field(description="The occupation of the user") + hobby: str + + +def generate_profiles(count: int) -> list[UserProfile]: + return cf.run( + f"Generate {count} user profiles", + result_type=list[UserProfile], + context={"count": count}, + ) + + +if __name__ == "__main__": + test_data = generate_profiles(count=5) + + from rich import print + + print(test_data) diff --git a/examples/headline_categorization.py b/examples/headline_categorization.py new file mode 100644 index 00000000..5693256c --- /dev/null +++ b/examples/headline_categorization.py @@ -0,0 +1,24 @@ +import controlflow as cf + +classifier = cf.Agent(model="openai/gpt-4o-mini") + + +def classify_news(headline: str) -> str: + return cf.run( + "Classify the news headline into the most appropriate category", + agents=[classifier], + result_type=["Politics", "Technology", "Sports", "Entertainment", "Science"], + context={"headline": headline}, + ) + + +if __name__ == "__main__": + headline = "New AI Model Breaks Records in Language Understanding" + category = classify_news(headline) + print(f"Headline: {headline}") + print(f"Category: {category}") + + headline = "Scientists Discover Potentially Habitable Exoplanet" + category = classify_news(headline) + print(f"\nHeadline: {headline}") + print(f"Category: {category}") diff --git a/examples/language_tutor.py b/examples/language_tutor.py new file mode 100644 index 00000000..d9b4905b --- /dev/null +++ b/examples/language_tutor.py @@ -0,0 +1,75 @@ +from pydantic import BaseModel + +import controlflow as cf + + +class Lesson(BaseModel): + topic: str + content: str + exercises: list[str] + + +def language_learning_session(language: str) -> None: + tutor = cf.Agent( + name="Tutor", + instructions=""" + You are a friendly and encouraging language tutor. Your goal is to create an + engaging and supportive learning environment. Always maintain a warm tone, + offer praise for efforts, and provide gentle corrections. Adapt your teaching + style to the user's needs and pace. Use casual language to keep the + conversation light and fun. When working through exercises: + - Present one exercise at a time. + - Provide hints if the user is struggling. + - Offer the correct answer if the user can't solve it after a few attempts. + - Use encouraging language throughout the process. + """, + ) + + @cf.flow(default_agent=tutor) + def learning_flow(): + user_name = cf.run( + f"Greet the user, learn their name, and introduce the {language} learning session", + interactive=True, + result_type=str, + ) + + print(f"\nWelcome, {user_name}! Let's start your {language} lesson.\n") + + while True: + lesson = cf.run( + "Create a fun and engaging language lesson", result_type=Lesson + ) + + print(f"\nToday's topic: {lesson.topic}") + print(f"Lesson content: {lesson.content}\n") + + for exercise in lesson.exercises: + print(f"Exercise: {exercise}") + cf.run( + "Work through the exercise with the user", + interactive=True, + context={"exercise": exercise}, + ) + + continue_learning = cf.run( + "Check if the user wants to continue learning", + result_type=bool, + interactive=True, + ) + + if not continue_learning: + break + + summary = cf.run( + "Summarize the learning session and provide encouragement", + context={"user_name": user_name}, + result_type=str, + ) + print(f"\nSession summary: {summary}") + + learning_flow() + + +if __name__ == "__main__": + language = input("Which language would you like to learn? ") + language_learning_session(language) diff --git a/examples/memory.py b/examples/memory.py new file mode 100644 index 00000000..53d810a6 --- /dev/null +++ b/examples/memory.py @@ -0,0 +1,37 @@ +import controlflow as cf + +# Create a memory module for user preferences +user_preferences = cf.Memory( + key="user_preferences", instructions="Store and retrieve user preferences." +) + +# Create an agent with access to the memory +agent = cf.Agent(memories=[user_preferences]) + + +# Create a flow to ask for the user's favorite color +@cf.flow +def remember_color(): + return cf.run( + "Ask the user for their favorite color and store it in memory", + agents=[agent], + interactive=True, + ) + + +# Create a flow to recall the user's favorite color +@cf.flow +def recall_color(): + return cf.run( + "What is the user's favorite color?", + agents=[agent], + ) + + +if __name__ == "__main__": + print("First flow:") + remember_color() + + print("\nSecond flow:") + result = recall_color() + print(result) diff --git a/examples/memory_between_flows.py b/examples/memory_between_flows.py deleted file mode 100644 index 75067273..00000000 --- a/examples/memory_between_flows.py +++ /dev/null @@ -1,20 +0,0 @@ -import controlflow as cf - -thread_id = "test-thread" - - -@cf.flow(thread=thread_id) -def flow_1(): - task = cf.Task("get the user's name", result_type=str, interactive=True) - return task - - -@cf.flow(thread=thread_id) -def flow_2(): - task = cf.Task("write the user's name backwards, if you don't know it, say so") - return task - - -if __name__ == "__main__": - flow_1() - flow_2() diff --git a/examples/named_entity_recognition.py b/examples/named_entity_recognition.py new file mode 100644 index 00000000..91d9c828 --- /dev/null +++ b/examples/named_entity_recognition.py @@ -0,0 +1,47 @@ +from typing import Dict, List + +import controlflow as cf + +extractor = cf.Agent( + name="Named Entity Recognizer", + model="openai/gpt-4o-mini", +) + + +def extract_entities(text: str) -> List[str]: + return cf.run( + "Extract all named entities from the text", + agents=[extractor], + result_type=List[str], + context={"text": text}, + ) + + +def extract_categorized_entities(text: str) -> Dict[str, List[str]]: + return cf.run( + "Extract named entities from the text and categorize them", + instructions=""" + Return a dictionary with the following keys: + - 'persons': List of person names + - 'organizations': List of organization names + - 'locations': List of location names + - 'dates': List of date references + - 'events': List of event names + Only include keys if entities of that type are found in the text. + """, + agents=[extractor], + result_type=Dict[str, List[str]], + context={"text": text}, + ) + + +if __name__ == "__main__": + text = "Apple Inc. is planning to open a new store in New York City next month." + entities = extract_entities(text) + print("Simple extraction:") + print(entities) + + text = "In 1969, Neil Armstrong became the first person to walk on the Moon during the Apollo 11 mission." + categorized_entities = extract_categorized_entities(text) + print("\nCategorized extraction:") + print(categorized_entities) diff --git a/examples/pineapple_pizza.py b/examples/pineapple_pizza.py new file mode 100644 index 00000000..fc019944 --- /dev/null +++ b/examples/pineapple_pizza.py @@ -0,0 +1,33 @@ +import controlflow as cf + +optimist = cf.Agent( + name="Half-full", + instructions="You are an eternal optimist.", +) +pessimist = cf.Agent( + name="Half-empty", + instructions="You are an eternal pessimist.", +) +moderator = cf.Agent(name="Moderator") + + +@cf.flow +def demo(topic: str): + cf.run( + "Have a debate about the topic.", + instructions="Each agent should take at least two turns.", + agents=[optimist, pessimist], + context={"topic": topic}, + ) + + winner: cf.Agent = cf.run( + "Whose argument do you find more compelling?", + agents=[moderator], + result_type=[optimist, pessimist], + ) + + print(f"{winner.name} wins the debate!") + + +if __name__ == "__main__": + demo("pineapple on pizza") diff --git a/examples/poem.py b/examples/poem.py deleted file mode 100644 index 60329d8f..00000000 --- a/examples/poem.py +++ /dev/null @@ -1,43 +0,0 @@ -from pydantic import BaseModel - -from controlflow import Agent, Task, flow, instructions, task - - -class Name(BaseModel): - first_name: str - last_name: str - - -@task(interactive=True) -def get_user_name() -> Name: - pass - - -@task(agents=[Agent(name="poetry-bot", instructions="loves limericks")]) -def write_poem_about_user(name: Name, interests: list[str]) -> str: - """write a poem based on the provided `name` and `interests`""" - pass - - -@flow() -def demo(): - # set instructions that will be used for multiple tasks - with instructions("talk like a pirate"): - # define an AI task as a function - name = get_user_name() - - # define an AI task imperatively - interests = Task( - "ask user for three interests", result_type=list[str], interactive=True - ) - interests.run() - - # set instructions for just the next task - with instructions("no more than 8 lines"): - poem = write_poem_about_user(name, interests.result) - - return poem - - -if __name__ == "__main__": - demo() diff --git a/examples/private_flows.py b/examples/private_flows.py new file mode 100644 index 00000000..4a768e02 --- /dev/null +++ b/examples/private_flows.py @@ -0,0 +1,30 @@ +import controlflow as cf + + +@cf.flow(args_as_context=False) +def process_user_data(user_name: str, sensitive_info: str): + # Main flow context + print(f"Processing data for user: {user_name}") + + # Create a private flow to handle sensitive information + with cf.Flow() as private_flow: + # This task runs in an isolated context + masked_info = cf.run( + "Mask the sensitive information", + context={"sensitive_info": sensitive_info}, + result_type=str, + ) + + # Task in the main flow can be provided the masked_info as context + summary = cf.run( + "Summarize the data processing result", + context={"user_name": user_name, "masked_info": masked_info}, + result_type=str, + ) + + return summary + + +if __name__ == "__main__": + result = process_user_data("Alice", "SSN: 123-45-6789") + print(result) diff --git a/examples/restaurant_recs.py b/examples/restaurant_recs.py deleted file mode 100644 index e8258a20..00000000 --- a/examples/restaurant_recs.py +++ /dev/null @@ -1,34 +0,0 @@ -from pydantic import BaseModel - -from controlflow import Task, flow - - -class Restaurant(BaseModel): - name: str - description: str - - -@flow -def restaurant_recs(n: int) -> list[Restaurant]: - """ - An agentic workflow that asks the user for their location and - cuisine preference, then recommends n restaurants based on their input. - """ - - # get the user's location - location = Task("Get a location", interactive=True) - - # get the user's preferred cuisine - cuisine = Task("Get a preferred cuisine", interactive=True) - - # generate the recommendations from the user's input - recs = Task( - f"Recommend {n} restaurants to the user", - context=dict(location=location, cuisine=cuisine), - result_type=list[Restaurant], - ) - return recs - - -if __name__ == "__main__": - restaurant_recs(5) diff --git a/examples/rock_paper_scissors.py b/examples/rock_paper_scissors.py new file mode 100644 index 00000000..72078642 --- /dev/null +++ b/examples/rock_paper_scissors.py @@ -0,0 +1,35 @@ +import controlflow as cf + + +@cf.flow +def rock_paper_scissors(): + """Play rock, paper, scissors against an AI.""" + play_again = True + + while play_again: + # Get the user's choice on a private thread + with cf.Flow(): + user_choice = cf.run( + "Get the user's choice", + result_type=["rock", "paper", "scissors"], + interactive=True, + ) + + # Get the AI's choice on a private thread + with cf.Flow(): + ai_choice = cf.run( + "Choose rock, paper, or scissors", + result_type=["rock", "paper", "scissors"], + ) + + # Report the score and ask if the user wants to play again + play_again = cf.run( + "Report the score to the user and see if they want to play again.", + interactive=True, + context={"user_choice": user_choice, "ai_choice": ai_choice}, + result_type=bool, + ) + + +if __name__ == "__main__": + rock_paper_scissors() diff --git a/examples/multi_agent_conversation.py b/examples/seinfeld.py similarity index 91% rename from examples/multi_agent_conversation.py rename to examples/seinfeld.py index 15d4669e..1bad21cd 100644 --- a/examples/multi_agent_conversation.py +++ b/examples/seinfeld.py @@ -1,3 +1,5 @@ +import sys + from controlflow import Agent, Task, flow jerry = Agent( @@ -66,12 +68,19 @@ def demo(topic: str): task = Task( "Discuss a topic", agents=[jerry, george, elaine, kramer, newman], + completion_agents=[jerry], result_type=None, context=dict(topic=topic), - instructions="every agent should speak at least once. only one agent per turn. Keep responses 1-2 paragraphs max.", + instructions="Every agent should speak at least once. only one agent per turn. Keep responses 1-2 paragraphs max.", ) task.run() if __name__ == "__main__": - demo(topic="sandwiches") + if len(sys.argv) > 1: + topic = sys.argv[1] + else: + topic = "sandwiches" + + print(f"Topic: {topic}") + demo(topic=topic) diff --git a/examples/sentiment_classifier.py b/examples/sentiment_classifier.py new file mode 100644 index 00000000..b65a6dcc --- /dev/null +++ b/examples/sentiment_classifier.py @@ -0,0 +1,29 @@ +import controlflow as cf +from controlflow.tasks.validators import between + +optimist = cf.Agent(model="openai/gpt-4o-mini") + + +def sentiment(text: str) -> float: + return cf.run( + "Classify the sentiment of the text as a value between 0 and 1", + agents=[optimist], + result_type=float, + result_validator=between(0, 1), + context={"text": text}, + ) + + +if __name__ == "__main__": + print(sentiment("I love ControlFlow!")) + + long_text = """ + Far out in the uncharted backwaters of the unfashionable end of + the western spiral arm of the Galaxy lies a small unregarded yellow sun. + Orbiting this at a distance of roughly ninety-two million miles is an utterly + insignificant little blue-green planet whose ape-descended life forms are so + amazingly primitive that they still think digital watches are a pretty neat + idea. This planet has – or rather had – a problem, which was this: most of + the people living on it were unhappy for pretty much of the time. + """ + print(sentiment(long_text)) diff --git a/examples/standardize_addresses.py b/examples/standardize_addresses.py new file mode 100644 index 00000000..b8626ef9 --- /dev/null +++ b/examples/standardize_addresses.py @@ -0,0 +1,38 @@ +from typing import List + +from pydantic import BaseModel + +import controlflow as cf + + +class StandardAddress(BaseModel): + city: str + state: str + country: str = "USA" + + +def standardize_addresses(place_names: List[str]) -> List[StandardAddress]: + return cf.run( + "Standardize the given place names into consistent postal addresses", + result_type=List[StandardAddress], + context={"place_names": place_names}, + ) + + +if __name__ == "__main__": + place_names = [ + "NYC", + "New York, NY", + "Big Apple", + "Los Angeles, California", + "LA", + "San Fran", + "The Windy City", + ] + + standardized_addresses = standardize_addresses(place_names) + + for original, standard in zip(place_names, standardized_addresses): + print(f"Original: {original}") + print(f"Standardized: {standard}") + print() diff --git a/examples/summarization.py b/examples/summarization.py new file mode 100644 index 00000000..9f9fa639 --- /dev/null +++ b/examples/summarization.py @@ -0,0 +1,40 @@ +from pydantic import BaseModel + +import controlflow as cf + + +class Summary(BaseModel): + summary: str + key_points: list[str] + + +def summarize_text(text: str, max_words: int = 100) -> Summary: + return cf.run( + f"Summarize the given text in no more than {max_words} words and list key points", + result_type=Summary, + context={"text": text}, + ) + + +if __name__ == "__main__": + long_text = """ + The Internet of Things (IoT) is transforming the way we interact with our + environment. It refers to the vast network of connected devices that collect + and share data in real-time. These devices range from simple sensors to + sophisticated wearables and smart home systems. The IoT has applications in + various fields, including healthcare, agriculture, and urban planning. In + healthcare, IoT devices can monitor patients remotely, improving care and + reducing hospital visits. In agriculture, sensors can track soil moisture and + crop health, enabling more efficient farming practices. Smart cities use IoT to + manage traffic, reduce energy consumption, and enhance public safety. However, + the IoT also raises concerns about data privacy and security, as these + interconnected devices can be vulnerable to cyber attacks. As the technology + continues to evolve, addressing these challenges will be crucial for the + widespread adoption and success of IoT. + """ + + result = summarize_text(long_text) + print(f"Summary:\n{result.summary}\n") + print("Key Points:") + for point in result.key_points: + print(f"- {point}") diff --git a/examples/task_dag.py b/examples/task_dag.py deleted file mode 100644 index 25b71994..00000000 --- a/examples/task_dag.py +++ /dev/null @@ -1,34 +0,0 @@ -import controlflow -from controlflow import Task, flow - -controlflow.settings.enable_experimental_tui = True - - -@flow -def book_ideas(): - genre = Task("pick a genre") - - ideas = Task( - "generate three short ideas for a book", - list[str], - context=dict(genre=genre), - ) - - abstract = Task( - "pick one idea and write a short abstract", - result_type=str, - context=dict(ideas=ideas, genre=genre), - ) - - title = Task( - "pick a title", - result_type=str, - context=dict(abstract=abstract), - ) - - return dict(genre=genre, ideas=ideas, abstract=abstract, title=title) - - -if __name__ == "__main__": - result = book_ideas() - print(result) diff --git a/examples/teacher_student.py b/examples/teacher_student.py deleted file mode 100644 index 549c195a..00000000 --- a/examples/teacher_student.py +++ /dev/null @@ -1,34 +0,0 @@ -from controlflow import Agent, Task, flow -from controlflow.instructions import instructions - -teacher = Agent(name="Teacher") -student = Agent(name="Student") - - -@flow -def demo(): - with Task("Teach a class by asking and answering 3 questions", agents=[teacher]): - for _ in range(3): - question = Task( - "Ask the student a question.", result_type=str, agents=[teacher] - ) - - with instructions("One sentence max"): - answer = Task( - "Answer the question.", - agents=[student], - context=dict(question=question), - ) - - grade = Task( - "Assess the answer.", - result_type=["pass", "fail"], - agents=[teacher], - context=dict(answer=answer), - ) - - # run each qa session, one at a time - grade.run() - - -t = demo() diff --git a/examples/translation.py b/examples/translation.py new file mode 100644 index 00000000..f4b1d312 --- /dev/null +++ b/examples/translation.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel + +import controlflow as cf + + +class TranslationResult(BaseModel): + translated: str + target_language: str + + +def translate_text(text: str, target_language: str) -> TranslationResult: + return cf.run( + f"Translate the given text to {target_language}", + result_type=TranslationResult, + context={"text": text, "target_language": target_language}, + ) + + +if __name__ == "__main__": + original_text = "Hello, how are you?" + target_language = "French" + + result = translate_text(original_text, target_language) + print(f"Original: {original_text}") + print(f"Translated ({result.target_language}): {result.translated}") diff --git a/examples/write_and_critique_paper.py b/examples/write_and_critique_paper.py deleted file mode 100644 index 522341be..00000000 --- a/examples/write_and_critique_paper.py +++ /dev/null @@ -1,30 +0,0 @@ -from controlflow import Agent, Task - -writer = Agent(name="writer") -editor = Agent(name="editor", instructions="you always find at least one problem") -critic = Agent(name="critic") - - -# ai tasks: -# - automatically supply context from kwargs -# - automatically wrap sub tasks in parent -# - automatically iterate over sub tasks if they are all completed but the parent isn't? - - -def write_paper(topic: str) -> str: - """ - Write a paragraph on the topic - """ - draft = Task( - "produce a 3-sentence draft on the topic", - str, - # agents=[writer], - context=dict(topic=topic), - ) - edits = Task("edit the draft", str, agents=[editor], depends_on=[draft]) - critique = Task("is it good enough?", bool, agents=[critic], depends_on=[edits]) - return critique - - -task = write_paper("AI and the future of work") -task.run()