
Youtube video for this section is still under creation. Please be patient ^^
The simplest way to get JSON output is to use the json_output=True
parameter on a task:
message = Task("Tell me 1 fact about Canada using the format {'countryName': '', 'fact': ''}", agent, json_output=True).solve()
However, this approach is "best effort". This means the agent will do its best to generate valid
JSON, but there's no
guarantee on the syntactic quality of the generated JSON as no grammar is enforced.
Also, always ask for JSON output in the prompt or else the LLM will have trouble generating
anything.
Optionnaly, you can pass a struture for the LLM to follow. This way you can parse the output.
To get more reliable and typed JSON outputs, Yacana offers structured_output
.
This feature uses Pydantic to define a strict schema that the response must follow.
Let's write an example using a pydantic class:
from pydantic import BaseModel
class CountryFact(BaseModel):
name: str
fact: str
class Facts(BaseModel):
countryFacts: list[CountryFact]
countryFacts
which
is a list of
CountryFact
. And this new class has 2 members a name
(string) and an
associated fact
(string).
[
{
"name": "France",
"fact": "Has the eiffel tower"
},
{
"name": "USA",
"fact": "Has the manhattan bridge"
}
]
from pydantic import BaseModel
from yacana import Task, OllamaAgent
class CountryFact(BaseModel):
name: str
fact: str
class Facts(BaseModel):
countryFacts: list[CountryFact]
agent = OllamaAgent("AI assistant", "llama3.1:8b", system_prompt="You are a helpful AI assistant")
message = Task("Tell me 3 facts about Canada.", agent, structured_output=Facts).solve()
# Prints the response as a pure JSON string
print(message.content)
# Typed access to data through the structured_output object
print("Name = ", message.structured_output.countryFacts[0].name)
print("Fact = ", message.structured_output.countryFacts[0].fact)
The benefits of this approach are numerous:
The structured_output
is particularly useful when you need to process responses
programmatically and want to guarantee the data structure.
Streaming allows you to get the output of an LLM token by token instead of waiting for the whole
response to come back.
It's particularly useful when you want to display the response to the user in real-time or need to
process the response incrementally.
To enable streaming, you can define a streaming callback that will receive the tokens as they are
generated:
from yacana import Task, OllamaAgent, GenericMessage
def streaming(chunk: str):
print(f"chunk = |{chunk}|")
agent = OllamaAgent("AI assistant", "llama3.1:8b", system_prompt="You are a helpful AI assistant")
message: GenericMessage = Task("Tell me 1 facts about France.", agent, streaming_callback=streaming).solve()
print("Full response = ", message.content)
INFO: [PROMPT][To: AI assistant]: Tell me 1 facts about France.
chunk = |Here|
chunk = |'s|
chunk = | one|
chunk = | fact|
chunk = |:
|
chunk = |The|
chunk = | E|
chunk = |iff|
chunk = |el|
chunk = | Tower|
chunk = | in|
chunk = | Paris|
chunk = |,|
...
Full response = Here's one fact:
The Eiffel Tower in Paris, France was originally intended to be a temporary structure, but it has become an iconic symbol of the country and a popular tourist destination, standing at over 324 meters (1,063 feet) tall!
You can give medias to Agents and make them interact with images, audios and more.
You can even mix tools and medias in the same task!
To use medias with Ollama you'll need to install a multi-modal model like
llama3.2-vision or Llava.
ollama pull llama3.2-vision:11b
You can use the OpenAiAgent with 'gpt-4o-mini' as its multi modal by default and supports images and sound. However, every medias is transformed into tokens and will count in your rate limit! The media is encoded to base64 before being sent.
from yacana import Task, OllamaAgent, GenericMessage
vision_agent = OllamaAgent("AI assistant", "llama3.2-vision:11b", system_prompt="You are a helpful AI assistant")
Task("Describe this image", vision_agent, medias=["./tests/assets/burger.jpg"]).solve()
INFO: [PROMPT][To: AI assistant]: Describe this image
INFO: [AI_RESPONSE][From: AI assistant]: This black and white photo showcases a close-up view of a hamburger. The burger is centered on the image, with its bun covered in sesame seeds and two patties visible beneath. A slice of cheese is positioned between the buns, while lettuce peeks out from underneath. A small amount of ketchup or mustard is visible at the bottom of the patty.
The background is blurred, suggesting that the burger was photographed on a table or countertop. The overall mood and atmosphere of this photo are casual and informal, as if it was taken by someone enjoying their meal in a relaxed setting.
from yacana import Task, OllamaAgent, GenericMessage, Tool
# Defining a simple function to be used as tool. It translates a common color name to its hexa version
def color_name_to_hexa(color_name: str) -> str:
color_map = {
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
"yellow": "#FFFF00",
"black": "#000000",
"white": "#FFFFFF"
}
return color_map.get(color_name.lower(), "#000000")
# Defining the tool itself, referencing the function
color_name_to_hexa_tool = Tool("ColorNameToHexa", "Converts a color name to its hexadecimal representation. For example red -> #FF0000", color_name_to_hexa)
vision_agent = OllamaAgent("AI assistant", "llama3.2-vision:11b", system_prompt="You are a helpful AI assistant")
Task("What color is the object ?", vision_agent, medias=["https://avatars.githubusercontent.com/u/12821004"], tools=[color_name_to_hexa_tool]).solve()
INFO: [PROMPT][To: AI assistant]: I give you the following tool definition that you must use to fulfill a future task: color_name_to_hexa(color_name: str) -> str - Converts a color name to its hexadecimal representation. For example red -> #FF0000. Please acknowledge the given tool.
INFO: [AI_RESPONSE][From: AI assistant]: I have taken note of the provided tool:
`color_name_to_hexa(color_name: str) -> str`
This function takes a string representing a color name as input and returns a string representing the corresponding hexadecimal color code.
I'm ready to use this tool for any future task that requires converting color names to their hexadecimal representations. What's the next step?
INFO: [PROMPT][To: AI assistant]: To use the tool you MUST extract each parameter and use it as a JSON key like this: {"arg1": "<value1>", "arg2": "<value2>"}. You must respect arguments type. For instance, the tool `getWeather(city: str, lat: int, long: int)` would be structured like this {"city": "new-york", "lat": 10, "lon": 20}. In our case, the tool call you must use must look like that: {'color_name': 'arg 0'}
INFO: [AI_RESPONSE][From: AI assistant]: I understand the requirement now. To use the `color_name_to_hexa(color_name: str) -> str` tool, I will extract each parameter and create a JSON object with the extracted parameters as keys.
In this case, since there is only one parameter, `color_name`, which is of type `str`, I will structure the call like this:
{'color_name': 'red'}
Please let me know when to proceed!
INFO: [PROMPT][To: AI assistant]: You have a task to solve. Use the tool at your disposition to solve the task by outputting as JSON the correct arguments. In return you will get an answer from the tool. The task is:
What color is the object ?
INFO: [AI_RESPONSE][From: AI assistant]: { "color_name": "blue" }
INFO: [TOOL_RESPONSE][ColorNameToHexa]: #0000FF
Thinking LLMs are a new breed of LLMs that can reason and think step by step to solve complex
problems.
They work in a similar way to Yacana's tool calling feature as the LLM makes it own reasoning loop
before giving the final answer.
The most famous opensource thinking LLM is Deepseek.
However, the result of a Task(...)
with a thinking LLM returns the complete reasoning
process, not just the final answer.
This means that the message.content
will have the tokens <think></think>
followed by the final response.
The content in between these tokens can disrupt Yacana so you should provide the framework's Agent
class the correct delimiters to use.
from yacana import OllamaAgent, Task, Tool
def get_weather(city: str) -> str:
# Faking the weather API response
return "Foggy"
def send_weather(city: str, weather: str) -> None:
print(f"Sending weather for {city}: {weather}")
# Creating a Deepseek agent and specifying the thinking tokens for this LLM
agent = OllamaAgent("Ai assistant", "deepseek-r1:latest", thinking_tokens=("<think>", "</think>"))
# Defining 2 tools
get_weather_tool = Tool("get_weather", "Returns the weather for a given city.", get_weather)
send_weather_tool = Tool("send_weather", "Sends the weather for a given city.", send_weather)
Task(f"Send the current weather in L.A to the weather service. Use the tools in the correct order.", agent, tools=[get_weather_tool, send_weather_tool]).solve()
print("\n--history--\n")
agent.history.pretty_print()
In the above snippet we used the thinking_tokens=("start_token", "end_token")
to tell
Yacana what is Deepseek's self reasoning process and what's the actual answer.
This way it will not disrupt Yacana's features anymore and you can use this LLM as any normal
LLM.
https://mcp.deepwiki.com/mcp
.
from yacana import Mcp
deepwiki = Mcp("https://mcp.deepwiki.com/mcp")
deepwiki.connect()
INFO: [MCP] Connecting to MCP server (https://mcp.deepwiki.com/mcp)...
INFO: [MCP] Connected to MCP server: DeepWiki v0.0.1
INFO: [MCP] Available tool: read_wiki_structure - Get a list of documentation topics for a GitHub repository
INFO: [MCP] Available tool: read_wiki_contents - View documentation about a GitHub repository
INFO: [MCP] Available tool: ask_question - Ask any question about a GitHub repository
deepwiki.forget_tool("read_wiki_structure")
. This way when you get the tools from the
Mcp object it will not return this tool anymore.
get_tools(<tool_type>)
on the Mcp object will return a list of all
the remote tools using the required execution type.
from yacana import Task, Tool, OllamaAgent, Mcp, ToolType
deepwiki = Mcp("https://mcp.deepwiki.com/mcp")
deepwiki.connect()
ollama_agent = OllamaAgent("Ai assistant", "llama3.1:8b")
Task("Asking question about repo: In the repo 'rememberSoftwares/yacana' how do you instanciate an ollama agent ?", ollama_agent, tools=deepwiki.get_tools_as(ToolType.YACANA)).solve()
optional=False
in the Tool constructor.get_tools_as(...)
method with value ToolType.OPENAI
Task("Asking question about repo: XXXXX ?", ollama_agent,
tools=deepwiki.get_tools_as(ToolType.YACANA)).solve()
.
actualize_server
tool to our previous Task and ask the LLM to
use it:
from yacana import Task, Tool, OllamaAgent, Mcp, ToolType
def update_server() -> None:
"""Updates the server."""
return None
update_server_tool = Tool("update_server", "Triggers a server update.", update_server)
deepwiki = Mcp("https://mcp.deepwiki.com/mcp")
deepwiki.connect()
ollama_agent = OllamaAgent("Ai assistant", "llama3.1:8b")
Task("Please update the server.", ollama_agent, tools=deepwiki.get_tools_as(ToolType.YACANA) + [update_server_tool]).solve()
tools=deepwiki.get_tools_as(ToolType.YACANA) +
[update_server_tool]
. The tools=
parameter takes a list of tools. Python can
concatenate lists so because get_tools_as(...)
returns a list of tools, we can
concatenate it with our local tool list between [...]
.
deepwiki = Mcp("https://mcp.deepwiki.com/mcp", {"lookatthis": "header"})