Structuring Your Truffle App
Ready to give your Truffle Agent superpowers? The Truffle SDK is your key to transforming simple Python code into powerful, custom tools your Agent can use seamlessly.
In this guide, we'll build a ResearchAssistant app together. This isn't just any assistant; it is a full on AI agent that will leverage the web's knowledge via Perplexity, remember past research, and show you how to make your tools dynamic and informative.
We'll cover everything from defining basic tools to managing state and updating the UI, all within this single, practical example.
What You'll Need
Before we dive in, make sure you've got:
- Python 3.10 or newer installed on your system.
- The latest Truffle SDK installed. If you haven't already:
pip install truffle-sdk
- A Truffle project initialized. If starting fresh, just run:
truffle init
Got everything? Awesome, let's get building!
The Basic Structure: Your App's Skeleton
Every Truffle app starts as a Python class. Think of this class as the container for all your app's logic and tools. Let's create the basic structure for our ResearchAssistant
in your project's main Python file (e.g., app.py
):
import truffle
import requests # We'll use this later for potential error handling
class ResearchAssistant:
def __init__(self):
# The heart of SDK interaction: TruffleClient
self.client = truffle.TruffleClient()
# We'll store research history here later!
self.research_history = []
# ... Our tools will go here soon ...
# This line makes your app runnable
if __name__ == "__main__":
truffle.run(ResearchAssistant())
See that self.client = truffle.TruffleClient()
? That's your main connection point to the Truffle ecosystem. It provides access to built-in capabilities (like web searches!) and lets your tools communicate back to the main Truffle client (like showing progress updates).
The TruffleClient
instance (self.client
) is your gateway to interacting with the Truffle platform, including updating the UI, using built-in tools like Perplexity search, and more. Keep it handy!
We've also added self.research_history = []
. This simple list will act as our app's memory, storing the questions asked and answers found. We'll see how to use this state soon.
Your First Tool: The Research Powerhouse
The core of our app will be a tool that performs web research. In Truffle, you turn a regular Python function into an Agent-usable tool with the @truffle.tool
decorator. It's like giving your function a special badge that the Agent recognizes.
Let's add our research tool:
Don't forget type hints for all tool parameters and the return value. The SDK relies on them for validation and proper function calling.
# Inside the ResearchAssistant class...
@truffle.tool(
description="Performs web research using Perplexity to answer a query.",
icon="magnifyingglass" # SF Symbol name for a magnifying glass
)
@truffle.args(
query="The specific question or topic you want to research."
)
def research(self, query: str) -> str:
"""Researches a topic using Perplexity and records the interaction."""
print(f"Received research query: {query}")
try:
# 1. Update the UI to show we're working
self.client.tool_update(" Accessing Perplexity...")
# 2. Use the built-in Perplexity search (you get free access!)
# Models available: "sonar-small", "sonar-medium", "sonar-pro"
response = self.client.perplexity_search(query=query, model="sonar-pro")
# 3. Update the UI again on success
self.client.tool_update(" Research complete!")
# 4. Store the result in our state
self.research_history.append({"query": query, "answer": response})
print(f"Successfully researched and stored query: {query}")
# 5. Return the answer to the Agent
return response
except Exception as e:
# 6. Handle errors gracefully
print(f"Error during research for '{query}': {e}")
self.client.tool_update(f" Error: {e}")
# Let the Agent know something went wrong
raise ValueError(f"Failed to research '{query}'. Error: {e}")
Let's break down what's happening here:
-
@truffle.tool(...)
: This decorator registers theresearch
function as a tool.description
: Crucial! This tells the LLM what the tool does and when to use it. Be descriptive!icon
: Adds a nice visual touch in the Truffle client. Use any valid SF Symbol name.
-
@truffle.args(...)
: This decorator provides descriptions for your tool's parameters, helping the LLM understand what kind of input is needed. -
self.client.tool_update(...)
: This sends a status message that appears in the Truffle client UI while the tool is running. Great for letting the user know what's happening during longer tasks.Keep the User InformedUse
tool_update
generously during long-running operations to provide feedback. It makes your app feel more responsive. -
self.client.perplexity_search(...)
: A built-in SDK function that gives you direct access to Perplexity's search power. No API key needed initially! -
State Update (
self.research_history.append(...)
): We're saving the question and answer to the list we defined in__init__
. This is how our app remembers things.State PersistenceAny attributes you define on your class instance (like
self.research_history
) act as persistent state for that task session. Truffle handles saving and restoring this state automatically.Errors in the UIAny unhandled exceptions raised by your tool, or messages from
print()
statements, will be clearly displayed in the Truffle client's execution details, aiding debugging.
Adding Conditional Logic: Predicates
What good is storing history if we can't view it? Let's add a tool to show our research history. But we only want this tool to be available if there's actually history to show. This is where the predicate
argument comes in handy!
A predicate is a function that returns True
or False
, determining if a tool should be active and visible to the Agent.
First, let's add the predicate function inside our class:
# Inside the ResearchAssistant class...
def _has_history(self) -> bool:
"""Checks if there is any research history stored."""
has = len(self.research_history) > 0
print(f"Predicate check: Has history? {has}")
return has
Now, let's create the view_history
tool and apply the predicate:
# Inside the ResearchAssistant class...
@truffle.tool(
description="Displays the recorded research history.",
icon="list.bullet.clipboard",
predicate=_has_history # Only enable this tool if _has_history returns True
)
def view_history(self) -> str:
"""Returns the stored research history."""
if not self.research_history:
return "No research history recorded yet."
formatted_history = "\n--- Research History ---\n"
for i, entry in enumerate(self.research_history):
formatted_history += f"{i+1}. Query: {entry['query']}\n Answer: {entry['answer'][:100]}...\n"
formatted_history += "--- End of History ---"
return formatted_history
With predicate=_has_history
, the view_history
tool will only appear as an option for the Agent after the research
tool has been successfully used at least once. This keeps the interface clean and relevant.
Predicates are excellent for decluttering the Agent's tool options. Only show tools when they are actually relevant or usable based on the current state.
Interacting with the Local Filesystem
Tools aren't limited to just processing data and calling APIs; they can also interact with the user's local filesystem, such as reading or writing files. Truffle handles the necessary permissions transparently.
Let's add a tool to save our research summary to a file. This is useful for creating persistent records or outputs.
# Inside the ResearchAssistant class...
@truffle.tool(
description="Saves the current research history summary to a local file.",
icon="doc.text.below.ecg",
predicate=_has_history # Only allow saving if there's history
)
@truffle.args(
filename="The name of the file to save the summary to (e.g., 'research_notes.txt')."
)
def save_research_summary(self, filename: str) -> str:
"""Saves the research history to a specified local file."""
summary = self.view_history() # Reuse the history formatting logic
if summary == "No research history recorded yet.":
return "Nothing to save."
try:
# Write the summary to the specified filename
# The user will be prompted to confirm the save location
with open(filename, 'w', encoding='utf-8') as f:
f.write(summary)
print(f"Research summary saved to {filename}")
self.client.tool_update(f"✅ Summary saved to {filename}")
return f"Successfully saved research summary to '{filename}'."
except Exception as e:
print(f"Error saving summary to '{filename}': {e}")
self.client.tool_update(f"⚠️ Error saving file: {e}")
raise ValueError(f"Failed to save summary to '{filename}'. Error: {e}")
Organizing Tools: Groups (Optional)
As your app grows, you might have several related tools. You can organize them using @truffle.group
. While our ResearchAssistant
is simple, imagine adding tools for different types of research (e.g., research_academic_papers
, research_news_articles
). You could group them:
# Example of grouping (not added to our main code)
@truffle.group("Advanced Research")
@truffle.tool(...)
def research_academic_papers(self, query: str) -> str:
# ... implementation ...
pass
@truffle.group("Advanced Research") # Same group name
@truffle.tool(...)
def research_news_articles(self, query: str) -> str:
# ... implementation ...
pass
This helps structure your tools logically within the Truffle client.
Preparing for Launch: Build & Upload
You've built your awesome ResearchAssistant
! Now, how do you get it into the Truffle client for your Agent to use?
-
Icon (
icon.png
): Place anicon.png
file in the root directory of your project (where yourapp.py
andmanifest.json
are). A transparent background usually looks best. 512x512 pixels is a good size. -
Manifest (
manifest.json
): This file was created bytruffle init
. You can edit it to change your app's display name, description, and add example prompts that showcase its capabilities.Example Prompts: Guiding the Agent
A key part of the
manifest.json
is theexample_prompts
list. These aren't just for show; they directly help the Truffle client's classification system understand when your app is the right tool for the job. By providing relevant examples of user queries your app can handle, you significantly increase the chance that your app will be automatically selected when the user types a similar prompt.// Example snippet from manifest.json
{
// ...
"example_prompts": [
"Research the impact of AI on software development.",
"Find recent news about renewable energy breakthroughs.",
"Can you look up the history of the Truffle framework?",
"What are the latest developments in quantum computing?"
]
}Write Good Example Prompts!Think about the core tasks your app performs. Add 3-5 diverse example prompts to
manifest.json
that accurately reflect these tasks. This is one of the best ways to improve your app's automatic activation! -
Dependencies (
requirements.txt
): Make sure any external Python libraries your app uses (besides thetruffle-sdk
itself, likerequests
if you were using it directly) are listed inrequirements.txt
. The build process uses this to package everything correctly.// Example requirements.txt
truffle-sdk>=0.1.0 # Or your specific version
// Add other dependencies here if needed -
Build: Open your terminal in the project's root directory and run:
truffle build
This command validates your code, checks types, and packages everything into a bundle (
app.truffle
). Crucially,truffle build
includes all files present in your project directory (the directory containing yourapp.py
andmanifest.json
) within the bundle. This means if you have data files, templates, or other assets, you can access them from your tool code using relative paths from your main app file (e.g.,open('my_data.csv', 'r')
).Bundled FilesNeed to include data files (like CSVs or JSON) or templates with your app? Just place them in the same directory as your
app.py
before runningtruffle build
, and access them using relative paths in your code. -
Upload: Once the build is successful, upload your app:
truffle upload