Overview

The Upsonic framework employs the Task class as its fundamental building block for defining and executing tasks, whether through agents or direct LLM calls. Tasks can be configured with various parameters, tools, and contextual elements, enabling the construction of comprehensive systems. Through context management, you can establish connections between tasks, link them to knowledge bases, or associate them with string values.

You don’t have to create tasks like steps. The agent will automatically generate these steps for you.

Unlike other systems that employ non-task structures, Upsonic addresses two critical limitations commonly found in alternative solutions. First, it eliminates the restriction of binding one agent to a single task, allowing agents to handle multiple tasks efficiently. Second, it significantly improves programmability. When creating tasks that involve dependencies—such as referenced websites, company information, or competitor data—Upsonic enables you to define these elements programmatically rather than embedding them within individual agents. This approach prevents the need to create separate agents for repetitive operations like competitor analysis, resulting in a more scalable and maintainable system architecture.

Creating a Task

The Task class is an integral component of Upsonic and can be easily imported into your project. Once imported, you can define multiple tasks by assigning them identifiers such as “task1,” “task2,” “task3,” or any other descriptive names of your choice.

from upsonic import Task

task1 = Task("Do an in-depth analysis of US history")

Task Attributest

Tasks within the framework can range from basic to complex, depending on your specific requirements. The framework is designed with simplicity in mind, requiring only one mandatory parameter: the description. All other parameters are optional, providing flexibility in task configuration.

AttributeParametersTypeDescription
DescriptiondescriptionstrA clear and concise statement of what the task.
Response Format (Optional)response_formatOptional[List[Union(BaseModal, ObjectResponse)]]Describe the response you expect.
Tools (Optional)toolsOptional[List[Union(MCP, Function)]]The tools needed to complete the task.
Context (Optional)contextOptional[List[Union(Task, KnowledgeBase, str)]]Context that helps accomplish the task

Adding Tools to a Task

Tools play a crucial role in agent functionality by bridging the gap between LLMs and real-world applications such as APIs, services, and search engines.

The framework supports two distinct types of tool implementation. The first option allows you to utilize Python functions directly as tools within Upsonic agents. The second approach leverages the Model Context Protocol (MCP), a standardized protocol for LLM tools that supports multiple platforms including Python, Node.js, and Docker. MCP tools are continuously developed and maintained by both companies and the community, with detailed information available in the “Tools” section.

Integrating a tool into your task is straightforward: simply create a list containing the desired tool’s class name and assign it to the “tools” parameter in the Task object.

1

Function Tools

Let’s define a class called MyTools that includes a function named is_page_available. This function will perform a simple URL validation check, returning True if the specified URL is accessible, making it useful for verifying web resources.

import requests

class MyTools:
  def is_page_available(url: str) -> bool:
      return requests.get(url).status_code == 200

2

MCP Tools

This example demonstrates integration with the HackerNews MCP Server, which provides several functions including get_stories, get_story_info, search_stories, and get_user_info. The MCP framework simplifies the process of connecting our agents to external services, as illustrated by this HackerNews integration.

class HackerNewsMCP:
    command = "uvx"
    args = ["mcp-hn"]
3

Put to Task

Once you’ve configured your custom tools, they can be directly incorporated into the Task object. The agent will then automatically utilize these tools as needed during task execution.

task = Task(
  "Summarize the latest hackernews stories of today", 
  tools=[Search, MyTools] # Specify the tools list
)

Putting Task to Another Task as Context

The framework supports the combination of multiple tasks to handle complex operations, particularly useful in scenarios requiring deep analysis followed by report generation. While individual tasks may be complex, the true power lies in their interconnection. By creating task chains and linking them through shared context, you can build sophisticated workflows that seamlessly pass information between tasks.

task = Task("Do an in-depth analysis of the history of chips")

task2 = Task(
  "Prepare a draft report on Europe's position", 
  context=[task1] # Add task1 in a list as task context
)}

The Upsonic framework explains the context to the LLM for your purposes. You don’t have to worry about sharing multiple contexts.

Giving Knowledge Base as Context

The framework incorporates a robust KnowledgeBase feature designed to extend LLM models beyond their inherent knowledge limitations. While LLM models are constrained by their training data boundaries, real-world applications frequently demand access to external information sources such as PDFs, documents, and spreadsheets. Through seamless integration with the Context system, the KnowledgeBase feature enables you to efficiently incorporate these external data sources into your tasks, enhancing the model’s capability to process and utilize supplementary information.

You can see the supported files and options of the Knowledge Base System from here.

1

Define a KnowledgeBase

In this example, we’ll use a PDF and a web link to make a knowledge base.

from upsonic import KnowledgeBase

firm_knowledge_base = KnowledgeBase(
  files=["february_technical_tasks.pdf", "https://upsonic.ai"]
)
2

Add as Context to Task

task = Task(
  "Create this month's overall technical report.",
  context=[firm_knowledge_base] # Adding firm_knowledge_base to task
)

Giving string as Context

Tasks represent specific objectives that need to be accomplished within the system. These tasks often contain variable elements such as URLs, names, individuals, or topics. Traditionally, all these components would need to be incorporated directly into the prompt, resulting in a non-programmatic approach. This conventional method relies heavily on f-strings or format operations, which significantly limits prompt management capabilities and constrains it within the main execution flow.

In the context system we also support str as context. This provide an capability to put your variables to outside of the prompt.

Use this when you need to separate your prompt and variable, like in a city list. You don’t need to use it if your prompt doesn’t have variables.

city = "New Yorg"

task = Task(
  "Find resources in the city",
  context=[city] # Adding city string as context
)

When you need to create task for different cities, you can use this method:

1

City List and Base Description

cities = ["New Yorg", "San Fransisco", "San Jose"]
base_description = "Find resources in the city"
2

Creating a for loop

tasks = []

for city in cities:
  city_task = Task(
    base_description, # Setting description from base_description
    context=[city] # Setting city string as context
  )
  tasks.append(city_task)

Response Format

The Upsonic framework leverages Pydantic BaseModel compatibility for defining expected results, enabling programmatic handling of agent responses. By specifying a response format, the system returns structured objects rather than raw text, allowing for seamless integration and logic implementation within your application.

For instance, when requesting a list of cities, the system returns a structured list of strings containing city names. This approach emphasizes value-oriented development by abstracting away the implementation details, allowing developers to focus on utilizing the data rather than managing its format and parsing.

You can use Pydantic’s BaseModel instead of ObjectResponse. We created this wrapper to make it easier to understand.

from upsonic import ObjectResponse

class TravelResponse(ObjectResponse):
  cities: str
task = Task(
  "Create a plan to visit cities in Canada", 
  reponse_format=TravelResponse # Specify the response format
)

Running Task on Direct LLM Call

After task definition, you have the flexibility to choose between two runtime options: Agent execution or direct LLM calls. While Agents provide powerful capabilities for complex tasks, they may not always be the most efficient choice. For simple tasks without potential sub-steps, utilizing an Agent can result in unnecessary time and cost overhead.

Direct LLM calls are particularly advantageous for straightforward operations that don’t require sub-task generation or characterization. This runtime selection can be strategically implemented within your Agent application logic, allowing you to optimize performance and resource utilization based on task complexity.

from upsonic import Direct

task = Task("Describe planet Earth")

Direct.print_do(task1) # Direct and fast way to complete task

Direct LLM Calls Support Tools

Don’t sweat it—Direct LLM Calls takes care of all your tools. They’ll work like agents, no sweat!

Running Task on Agent

Tasks can be executed through characterized LLM agents, a key feature that enables specialized, task-focused LLM implementations tailored to your company’s needs. The agent mechanism is governed by “AgentConfiguration” objects, which serve as the foundation for defining and customizing agent characteristics and behaviors.

This characterization system allows for precise control over how agents process and respond to tasks, ensuring alignment with specific business requirements and objectives. Through these configurations, you can create purpose-built agents that maintain consistency and relevance in their operations while adhering to your organization’s specific requirements.

You can view the details of agent creation and customization here.

1

Creating Agent

To create an agent, we’re going to import the “AgentConfiguration” and make a basic customization.

from upsonic import Agent

agent = Agent(
    "Product Manager",
    company_url="https://upsonic.ai",
    company_objective="To build AI Agent framework that helps people get things done",
)
2

Creating Task

task = Task("Make a deep analyze to history of chips")
3

Running Task with Agent

We’ve got a function called “agent” in the client that we’ll use to give product_manager_agent and task1.

agent.print_do(task)

Accessing to Task Results

Tasks can be executed through two distinct runners: direct LLM calls or agents. These runners serve as execution mechanisms for task processing and result generation, offering flexibility in implementation. The system supports both parallel processing and multi-client operations, enhancing scalability and performance.

To maintain a controlled and organized infrastructure, all results are stored within the Task object itself. This architectural decision provides a centralized approach to result management, enabling better monitoring, access, and control over task outcomes. Such design creates a robust and manageable infrastructure that can be effectively tailored to your specific requirements.

When you run the task, the results are stored in the Task.response. You can get it directly.

task = Task("Make a deep analyze to history of chips")

## Run the task

# Direct.do(task)
# agent.do(task)

## After Run the task

result = task.response

print(response)

Hey, just a heads-up: if you set a response_format, the task response will be an object of your class.

from upsonic import ObjectResponse

class TravelResponse(ObjectResponse):
  cities: str

task = Task(
  "Generate a plan to visit cities in Canada", 
  reponse_format=TravelResponse # Specift the response format
)

## Run the task

# Direct.do(task)
# agent.do(task)

## After Run the task


result = task.response

print("Cities")
for i in result.cities:
  print(i)