From cb7a0bfe8658b8327f304f7e5a5c5a233bf4452a Mon Sep 17 00:00:00 2001 From: Alex Bozarth Date: Tue, 4 Mar 2025 18:53:00 -0600 Subject: [PATCH 1/2] feat(examples): templates examples ts parity Signed-off-by: Alex Bozarth --- python/beeai_framework/template.py | 2 +- python/docs/agents.md | 37 ++- python/docs/templates.md | 278 +++++++++++++++--- python/examples/README.md | 9 +- python/examples/templates/agent_sys_prompt.py | 12 - python/examples/templates/arrays.py | 31 ++ python/examples/templates/basic_functions.py | 28 -- python/examples/templates/basic_template.py | 34 ++- python/examples/templates/forking.py | 40 +++ python/examples/templates/functions.py | 54 ++++ python/examples/templates/objects.py | 36 +++ python/examples/templates/system_prompt.py | 35 +++ 12 files changed, 485 insertions(+), 111 deletions(-) delete mode 100644 python/examples/templates/agent_sys_prompt.py create mode 100644 python/examples/templates/arrays.py delete mode 100644 python/examples/templates/basic_functions.py create mode 100644 python/examples/templates/forking.py create mode 100644 python/examples/templates/functions.py create mode 100644 python/examples/templates/objects.py create mode 100644 python/examples/templates/system_prompt.py diff --git a/python/beeai_framework/template.py b/python/beeai_framework/template.py index bc04016d2..dbf4ccb8f 100644 --- a/python/beeai_framework/template.py +++ b/python/beeai_framework/template.py @@ -34,7 +34,7 @@ class PromptTemplateInput(BaseModel, Generic[T]): input_schema: type[T] = Field(..., alias="schema") template: str functions: dict[str, Callable[[dict], str]] | None = None - defaults: dict[str, str] | None = {} + defaults: dict[str, Any] = {} class PromptTemplate(Generic[T]): diff --git a/python/docs/agents.md b/python/docs/agents.md index 1eb419c7e..855805272 100644 --- a/python/docs/agents.md +++ b/python/docs/agents.md @@ -106,25 +106,48 @@ _Source: [examples/agents/bee.py](/python/examples/agents/bee.py)_ Customize how the agent formats prompts, including the system prompt that defines its behavior. - + ```py +import sys +import traceback + from beeai_framework.agents.runners.default.prompts import ( SystemPromptTemplate, - SystemPromptTemplateInput, + ToolDefinition, ) +from beeai_framework.errors import FrameworkError from beeai_framework.tools.weather.openmeteo import OpenMeteoTool +from beeai_framework.utils.strings import to_json -tool = OpenMeteoTool() -# Render the granite system prompt -prompt = SystemPromptTemplate.render(SystemPromptTemplateInput(instructions="You are a helpful AI assistant!")) +def main() -> None: + tool = OpenMeteoTool() + + tool_def = ToolDefinition( + name=tool.name, + description=tool.description, + input_schema=to_json(tool.input_schema.model_json_schema()), + ) + + # Render the granite system prompt + prompt = SystemPromptTemplate.render( + instructions="You are a helpful AI assistant!", tools=[tool_def], tools_length=1 + ) -print(prompt) + print(prompt) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) ``` -_Source: [examples/templates/agent_sys_prompt.py](/python/examples/templates/agent_sys_prompt.py)_ +_Source: [examples/templates/system_prompt.py](/python/examples/templates/system_prompt.py)_ The agent uses several templates that you can override: 1. **System Prompt** - Defines the agent's behavior and capabilities diff --git a/python/docs/templates.md b/python/docs/templates.md index 80c84c31b..1f9f6cea6 100644 --- a/python/docs/templates.md +++ b/python/docs/templates.md @@ -44,100 +44,212 @@ At its core, the `PromptTemplate` class: Create templates with basic variable substitution and type validation. + + ```py +import sys +import traceback + from pydantic import BaseModel +from beeai_framework.errors import FrameworkError from beeai_framework.template import PromptTemplate, PromptTemplateInput -class UserMessage(BaseModel): - label: str - input: str +def main() -> None: + class UserMessage(BaseModel): + label: str + input: str - -template = PromptTemplate( - PromptTemplateInput( - schema=UserMessage, - template="""{{label}}: {{input}}""", + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=UserMessage, + template="""{{label}}: {{input}}""", + ) ) -) -prompt = template.render(UserMessage(label="Query", input="What interesting things happened on this day in history?")) + prompt = template.render(label="Query", input="What interesting things happened on this day in history?") -print(prompt) + print(prompt) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) ``` This example creates a simple template that formats a user message with a label and input text. The Pydantic model ensures type safety for the template variables. -_Source: /examples/templates/basic_template.py_ +_Source: [examples/templates/basic_template.py](/python/examples/templates/basic_template.py)_ ### Template functions Add dynamic content to templates using custom functions. + + ```py -import os -from datetime import datetime -from zoneinfo import ZoneInfo +import sys +import traceback +from datetime import UTC, datetime +from typing import Any from pydantic import BaseModel +from beeai_framework.errors import FrameworkError from beeai_framework.template import PromptTemplate, PromptTemplateInput -os.environ["USER"] = "BeeAI" +def main() -> None: + class AuthorMessage(BaseModel): + text: str + author: str | None = None + created_at: str | None = None + + def format_meta(data: dict[str, Any]) -> str: + if data["author"] is None and data["created_at"] is None: + return "" -class UserQuery(BaseModel): - query: str + author = data["author"] or "anonymous" + created_at = data["created_at"] or datetime.now(UTC).strftime("%A, %B %d, %Y at %I:%M:%S %p") + return f"\nThis message was created at {created_at} by {author}." -template = PromptTemplate( - PromptTemplateInput( - schema=UserQuery, - functions={ - "format_date": lambda: datetime.now(ZoneInfo("US/Eastern")).strftime("%A, %B %d, %Y at %I:%M:%S %p"), - "current_user": lambda: os.environ["USER"], - }, - template=""" -{{format_date}} -{{current_user}}: {{query}} -""", + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=AuthorMessage, + functions={ + "format_meta": lambda data: format_meta(data), + }, + template="""Message: {{text}}{{format_meta}}""", + ) ) -) + + # Message: Hello from 2024! + # This message was created at 2024-01-01T00:00:00+00:00 by John. + message = template.render( + text="Hello from 2024!", author="John", created_at=datetime(2024, 1, 1, tzinfo=UTC).isoformat() + ) + print(message) + + # Message: Hello from the present! + message = template.render(text="Hello from the present!") + print(message) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) ``` This example demonstrates how to add custom functions to templates: -* The `format_date` function returns the current date and time in a specific format -* The `current_user` function retrieves the current user from environment variables -* Both functions can be called directly from the template using Mustache-style syntax +* The `format_meta` function returns the date and author in a readable string +* Functions can be called directly from the template using Mustache-style syntax -_Source: [examples/templates/basic_functions.py](/python/examples/templates/basic_functions.py)_ +_Source: [examples/templates/functions.py](/python/examples/templates/functions.py)_ ### Working with objects Handle complex nested data structures in templates with proper type validation. + + ```py -# Coming soon +import sys +import traceback + +from pydantic import BaseModel + +from beeai_framework.errors import FrameworkError +from beeai_framework.template import PromptTemplate, PromptTemplateInput + + +def main() -> None: + class Response(BaseModel): + duration: int + + class ExpectedDuration(BaseModel): + expected: int + responses: list[Response] + + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=ExpectedDuration, + template="""Expected Duration: {{expected}}ms; Retrieved: {{#responses}}{{duration}}ms {{/responses}}""", + defaults={"expected": 5}, + ) + ) + + # Expected Duration: 5ms; Retrieved: 3ms 5ms 6ms + output = template.render(responses=[Response(duration=3), Response(duration=5), Response(duration=6)]) + print(output) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) + ``` This example shows how to work with nested objects in templates. The Mustache syntax allows for iterating through the responses array and accessing properties of each object. -_Source: /examples/templates/objects.py_ +_Source: [examples/templates/objects.py](/python/examples/templates/objects.py)_ ### Working with arrays Process collections of data within templates for dynamic list generation. + + ```py -# Coming soon +import sys +import traceback + +from pydantic import BaseModel, Field + +from beeai_framework.errors import FrameworkError +from beeai_framework.template import PromptTemplate, PromptTemplateInput + + +def main() -> None: + class ColorsObject(BaseModel): + colors: list[str] = Field(..., min_length=1) + + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=ColorsObject, + template="""Colors: {{#colors}}{{.}}, {{/colors}}""", + ) + ) + + # Colors: Green, Yellow, + output = template.render(colors=["Green", "Yellow"]) + print(output) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) + ``` This example demonstrates how to iterate over arrays in templates using Mustache's section syntax. -_Source: /examples/templates/arrays.py_ +_Source: [examples/templates/arrays.py](/python/examples/templates/arrays.py)_ ### Template forking @@ -148,13 +260,55 @@ Template forking is useful for: * Adding new fields or functionality to existing templates * Specializing generic templates for specific use cases + + ```py -# Coming soon +import sys +import traceback + +from pydantic import BaseModel + +from beeai_framework.errors import FrameworkError +from beeai_framework.template import PromptTemplate, PromptTemplateInput + + +def main() -> None: + class OriginalSchema(BaseModel): + name: str + objective: str + + original: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=OriginalSchema, + template="""You are a helpful assistant called {{name}}. Your objective is to {{objective}}.""", + ) + ) + + def customizer(temp_input: PromptTemplateInput) -> PromptTemplateInput: + new_temp = temp_input.model_copy() + new_temp.template = f"""{temp_input.template} Your answers must be concise.""" + new_temp.defaults["name"] = "Bee" + return new_temp + + modified = original.fork(customizer=customizer) + + # You are a helpful assistant called Bee. Your objective is to fulfill the user needs. Your answers must be concise. + prompt = modified.render(objective="fulfill the user needs") + print(prompt) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) + ``` This example shows how to create a new template based on an existing one. -_Source: /examples/templates/forking.py_ +_Source: [examples/templates/forking.py](/python/examples/templates/forking.py)_ ### Default values @@ -166,32 +320,58 @@ Provide default values for template variables that can be overridden at runtime. The framework's agents use specialized templates to structure their behavior. You can customize these templates to alter how agents operate: - + ```py +import sys +import traceback + from beeai_framework.agents.runners.default.prompts import ( SystemPromptTemplate, - SystemPromptTemplateInput, + ToolDefinition, ) +from beeai_framework.errors import FrameworkError from beeai_framework.tools.weather.openmeteo import OpenMeteoTool +from beeai_framework.utils.strings import to_json + + +def main() -> None: + tool = OpenMeteoTool() + + tool_def = ToolDefinition( + name=tool.name, + description=tool.description, + input_schema=to_json(tool.input_schema.model_json_schema()), + ) + + # Render the granite system prompt + prompt = SystemPromptTemplate.render( + instructions="You are a helpful AI assistant!", tools=[tool_def], tools_length=1 + ) -tool = OpenMeteoTool() + print(prompt) -# Render the granite system prompt -prompt = SystemPromptTemplate.render(SystemPromptTemplateInput(instructions="You are a helpful AI assistant!")) -print(prompt) +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) ``` This example demonstrates how to create a system prompt for an agent with tool definitions, which enables the agent to use external tools like weather data retrieval. -_Source: [examples/templates/agent_sys_prompt.py](/examples/templates/agent_sys_prompt.py)_ +_Source: [examples/templates/system_prompt.py](/examples/templates/system_prompt.py)_ --- # Examples - [basic_template.py](/python/examples/templates/basic_template.py) - Simple variable substitution with Pydantic validation -- [basic_functions.py](/python/examples/templates/basic_functions.py) - Adding dynamic content with custom template functions -- [agent_sys_prompt.py](/python/examples/templates/agent_sys_prompt.py) - Creating specialized system prompts for AI assistants with tool definitions \ No newline at end of file +- [functions.py](/python/examples/templates/functions.py) - Adding dynamic content with custom template functions +- [objects.py](/python/examples/templates/objects.py) - Handle complex nested data structures in templates with proper type validation +- [arrays.py](/python/examples/templates/arrays.py) - Process collections of data within templates for dynamic list generation +- [forking.py](/python/examples/templates/forking.py) - Create new templates based on existing ones, with customizations +- [system_prompt.py](/python/examples/templates/system_prompt.py) - Creating specialized system prompts for AI assistants with tool definitions \ No newline at end of file diff --git a/python/examples/README.md b/python/examples/README.md index 5d8236b6e..a000f7dbe 100644 --- a/python/examples/README.md +++ b/python/examples/README.md @@ -48,9 +48,12 @@ This repository contains examples demonstrating the usage of the BeeAI Framework ## Templates -- [`basic_functions.py`](/python/examples/templates/basic_functions.py): Basic functions -- [`basic_template.py`](/python/examples/templates/basic_template.py): Basic template - +- [`basic_template.py`](/python/examples/templates/basic_template.py): Simple template +- [`functions.py`](/python/examples/templates/functions.py): Template functions +- [`objects.py`](/python/examples/templates/objects.py): Working with objects +- [`arrays.py`](/python/examples/templates/arrays.py): Working with arrays +- [`forking.py`](/python/examples/templates/forking.py): Template forking +- [`system_prompt.py`](/python/examples/templates/system_prompt.py): Using templates with agents ## Tools - [`decorator.py`](/python/examples/tools/decorator.py): Tool creation using decorator diff --git a/python/examples/templates/agent_sys_prompt.py b/python/examples/templates/agent_sys_prompt.py deleted file mode 100644 index 296b8c000..000000000 --- a/python/examples/templates/agent_sys_prompt.py +++ /dev/null @@ -1,12 +0,0 @@ -from beeai_framework.agents.runners.default.prompts import ( - SystemPromptTemplate, - SystemPromptTemplateInput, -) -from beeai_framework.tools.weather.openmeteo import OpenMeteoTool - -tool = OpenMeteoTool() - -# Render the granite system prompt -prompt = SystemPromptTemplate.render(SystemPromptTemplateInput(instructions="You are a helpful AI assistant!")) - -print(prompt) diff --git a/python/examples/templates/arrays.py b/python/examples/templates/arrays.py new file mode 100644 index 000000000..e13683126 --- /dev/null +++ b/python/examples/templates/arrays.py @@ -0,0 +1,31 @@ +import sys +import traceback + +from pydantic import BaseModel, Field + +from beeai_framework.errors import FrameworkError +from beeai_framework.template import PromptTemplate, PromptTemplateInput + + +def main() -> None: + class ColorsObject(BaseModel): + colors: list[str] = Field(..., min_length=1) + + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=ColorsObject, + template="""Colors: {{#colors}}{{.}}, {{/colors}}""", + ) + ) + + # Colors: Green, Yellow, + output = template.render(colors=["Green", "Yellow"]) + print(output) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) diff --git a/python/examples/templates/basic_functions.py b/python/examples/templates/basic_functions.py deleted file mode 100644 index da1dfda28..000000000 --- a/python/examples/templates/basic_functions.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -from datetime import datetime -from zoneinfo import ZoneInfo - -from pydantic import BaseModel - -from beeai_framework.template import PromptTemplate, PromptTemplateInput - -os.environ["USER"] = "BeeAI" - - -class UserQuery(BaseModel): - query: str - - -template = PromptTemplate( - PromptTemplateInput( - schema=UserQuery, - functions={ - "format_date": lambda: datetime.now(ZoneInfo("US/Eastern")).strftime("%A, %B %d, %Y at %I:%M:%S %p"), - "current_user": lambda: os.environ["USER"], - }, - template=""" -{{format_date}} -{{current_user}}: {{query}} -""", - ) -) diff --git a/python/examples/templates/basic_template.py b/python/examples/templates/basic_template.py index 47f95d985..801091c1b 100644 --- a/python/examples/templates/basic_template.py +++ b/python/examples/templates/basic_template.py @@ -1,20 +1,32 @@ +import sys +import traceback + from pydantic import BaseModel +from beeai_framework.errors import FrameworkError from beeai_framework.template import PromptTemplate, PromptTemplateInput -class UserMessage(BaseModel): - label: str - input: str - +def main() -> None: + class UserMessage(BaseModel): + label: str + input: str -template = PromptTemplate( - PromptTemplateInput( - schema=UserMessage, - template="""{{label}}: {{input}}""", + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=UserMessage, + template="""{{label}}: {{input}}""", + ) ) -) -prompt = template.render(UserMessage(label="Query", input="What interesting things happened on this day in history?")) + prompt = template.render(label="Query", input="What interesting things happened on this day in history?") + + print(prompt) + -print(prompt) +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) diff --git a/python/examples/templates/forking.py b/python/examples/templates/forking.py new file mode 100644 index 000000000..c32ef70a5 --- /dev/null +++ b/python/examples/templates/forking.py @@ -0,0 +1,40 @@ +import sys +import traceback + +from pydantic import BaseModel + +from beeai_framework.errors import FrameworkError +from beeai_framework.template import PromptTemplate, PromptTemplateInput + + +def main() -> None: + class OriginalSchema(BaseModel): + name: str + objective: str + + original: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=OriginalSchema, + template="""You are a helpful assistant called {{name}}. Your objective is to {{objective}}.""", + ) + ) + + def customizer(temp_input: PromptTemplateInput) -> PromptTemplateInput: + new_temp = temp_input.model_copy() + new_temp.template = f"""{temp_input.template} Your answers must be concise.""" + new_temp.defaults["name"] = "Bee" + return new_temp + + modified = original.fork(customizer=customizer) + + # You are a helpful assistant called Bee. Your objective is to fulfill the user needs. Your answers must be concise. + prompt = modified.render(objective="fulfill the user needs") + print(prompt) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) diff --git a/python/examples/templates/functions.py b/python/examples/templates/functions.py new file mode 100644 index 000000000..1352bac10 --- /dev/null +++ b/python/examples/templates/functions.py @@ -0,0 +1,54 @@ +import sys +import traceback +from datetime import UTC, datetime +from typing import Any + +from pydantic import BaseModel + +from beeai_framework.errors import FrameworkError +from beeai_framework.template import PromptTemplate, PromptTemplateInput + + +def main() -> None: + class AuthorMessage(BaseModel): + text: str + author: str | None = None + created_at: str | None = None + + def format_meta(data: dict[str, Any]) -> str: + if data.get("author") is None and data.get("created_at") is None: + return "" + + author = data.get("author") or "anonymous" + created_at = data.get("created_at") or datetime.now(UTC).strftime("%A, %B %d, %Y at %I:%M:%S %p") + + return f"\nThis message was created at {created_at} by {author}." + + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=AuthorMessage, + functions={ + "format_meta": lambda data: format_meta(data), + }, + template="""Message: {{text}}{{format_meta}}""", + ) + ) + + # Message: Hello from 2024! + # This message was created at 2024-01-01T00:00:00+00:00 by John. + message = template.render( + text="Hello from 2024!", author="John", created_at=datetime(2024, 1, 1, tzinfo=UTC).isoformat() + ) + print(message) + + # Message: Hello from the present! + message = template.render(text="Hello from the present!") + print(message) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) diff --git a/python/examples/templates/objects.py b/python/examples/templates/objects.py new file mode 100644 index 000000000..d0f3ce102 --- /dev/null +++ b/python/examples/templates/objects.py @@ -0,0 +1,36 @@ +import sys +import traceback + +from pydantic import BaseModel + +from beeai_framework.errors import FrameworkError +from beeai_framework.template import PromptTemplate, PromptTemplateInput + + +def main() -> None: + class Response(BaseModel): + duration: int + + class ExpectedDuration(BaseModel): + expected: int + responses: list[Response] + + template: PromptTemplate = PromptTemplate( + PromptTemplateInput( + schema=ExpectedDuration, + template="""Expected Duration: {{expected}}ms; Retrieved: {{#responses}}{{duration}}ms {{/responses}}""", + defaults={"expected": 5}, + ) + ) + + # Expected Duration: 5ms; Retrieved: 3ms 5ms 6ms + output = template.render(responses=[Response(duration=3), Response(duration=5), Response(duration=6)]) + print(output) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) diff --git a/python/examples/templates/system_prompt.py b/python/examples/templates/system_prompt.py new file mode 100644 index 000000000..d17ffc084 --- /dev/null +++ b/python/examples/templates/system_prompt.py @@ -0,0 +1,35 @@ +import sys +import traceback + +from beeai_framework.agents.runners.default.prompts import ( + SystemPromptTemplate, + ToolDefinition, +) +from beeai_framework.errors import FrameworkError +from beeai_framework.tools.weather.openmeteo import OpenMeteoTool +from beeai_framework.utils.strings import to_json + + +def main() -> None: + tool = OpenMeteoTool() + + tool_def = ToolDefinition( + name=tool.name, + description=tool.description, + input_schema=to_json(tool.input_schema.model_json_schema()), + ) + + # Render the granite system prompt + prompt = SystemPromptTemplate.render( + instructions="You are a helpful AI assistant!", tools=[tool_def], tools_length=1 + ) + + print(prompt) + + +if __name__ == "__main__": + try: + main() + except FrameworkError as e: + traceback.print_exc() + sys.exit(e.explain()) From e6d18ade2dd2dd10995480b194dcf004828b1c30 Mon Sep 17 00:00:00 2001 From: Alex Bozarth Date: Tue, 4 Mar 2025 19:54:39 -0600 Subject: [PATCH 2/2] docs: missed docs build Signed-off-by: Alex Bozarth --- python/docs/templates.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/docs/templates.md b/python/docs/templates.md index 1f9f6cea6..037093826 100644 --- a/python/docs/templates.md +++ b/python/docs/templates.md @@ -111,11 +111,11 @@ def main() -> None: created_at: str | None = None def format_meta(data: dict[str, Any]) -> str: - if data["author"] is None and data["created_at"] is None: + if data.get("author") is None and data.get("created_at") is None: return "" - author = data["author"] or "anonymous" - created_at = data["created_at"] or datetime.now(UTC).strftime("%A, %B %d, %Y at %I:%M:%S %p") + author = data.get("author") or "anonymous" + created_at = data.get("created_at") or datetime.now(UTC).strftime("%A, %B %d, %Y at %I:%M:%S %p") return f"\nThis message was created at {created_at} by {author}."