Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions cookbook/models/qianfan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Qianfan Cookbook

> Note: Fork and clone this repository if needed

### 1. Create and activate a virtual environment

```shell
python3 -m venv ~/.venvs/aienv
source ~/.venvs/aienv/bin/activate
```

### 2. Export your `QIANFAN_API_KEY`

```shell
export QIANFAN_API_KEY=***
```

### 3. Install libraries

```shell
pip install -U openai agno
```

### 4. Run basic Agent

- Streaming on

```shell
python cookbook/models/qianfan/basic_stream.py
```

- Streaming off

```shell
python cookbook/models/qianfan/basic.py
```

### 5. Run Agent with Tools


- DuckDuckGo search

```shell
python cookbook/models/qianfan/tool_use.py
```
Empty file.
12 changes: 12 additions & 0 deletions cookbook/models/qianfan/async_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Basic async example using Qianfan.
"""

import asyncio

from agno.agent import Agent
from agno.models.qianfan import Qianfan

agent = Agent(model=Qianfan(id="deepseek-v3"), markdown=True)

asyncio.run(agent.aprint_response("Share a 2 sentence horror story"))
12 changes: 12 additions & 0 deletions cookbook/models/qianfan/async_basic_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Basic streaming async example using Qianfan.
"""

import asyncio

from agno.agent import Agent
from agno.models.qianfan import Qianfan

agent = Agent(model=Qianfan(id="deepseek-v3"), markdown=True)

asyncio.run(agent.aprint_response("Share a 2 sentence horror story", stream=True))
18 changes: 18 additions & 0 deletions cookbook/models/qianfan/async_tool_use.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
Async example using Mistral with tool calls.
"""

import asyncio

from agno.agent import Agent
from agno.models.qianfan import Qianfan
from agno.tools.duckduckgo import DuckDuckGoTools

agent = Agent(
model=Qianfan(id="deepseek-v3"),
tools=[DuckDuckGoTools()],
show_tool_calls=True,
markdown=True,
)

asyncio.run(agent.aprint_response("Whats happening in France?", stream=True))
11 changes: 11 additions & 0 deletions cookbook/models/qianfan/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from agno.agent import Agent, RunResponse # noqa
from agno.models.qianfan import Qianfan

agent = Agent(model=Qianfan(id="deepseek-v3"), markdown=True)

# Get the response in a variable
# run: RunResponse = agent.run("Share a 2 sentence horror story")
# print(run.content)

# Print the response in the terminal
agent.print_response("Share a 2 sentence horror story")
12 changes: 12 additions & 0 deletions cookbook/models/qianfan/basic_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from agno.agent import Agent, RunResponseEvent # noqa
from agno.models.qianfan import Qianfan

agent = Agent(model=Qianfan(id="deepseek-v3"), markdown=True)

# Get the response in a variable
# run_response: Iterator[RunResponseEvent] = agent.run("Share a 2 sentence horror story", stream=True)
# for chunk in run_response:
# print(chunk.content)

# Print the response in the terminal
agent.print_response("Share a 2 sentence horror story", stream=True)
16 changes: 16 additions & 0 deletions cookbook/models/qianfan/tool_use.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Run `pip install duckduckgo-search` to install dependencies."""

import os

from agno.agent import Agent
from agno.models.qianfan import Qianfan
from agno.tools.duckduckgo import DuckDuckGoTools

agent = Agent(
model=Qianfan(id="deepseek-v3"),
tools=[DuckDuckGoTools()],
show_tool_calls=True,
markdown=True,
)

agent.print_response("Whats happening in France?", stream=True)
6 changes: 3 additions & 3 deletions libs/agno/agno/embedder/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def client(self):

_client_params: Dict[str, Any] = {}
vertexai = self.vertexai or getenv("GOOGLE_GENAI_USE_VERTEXAI", "false").lower() == "true"

if not vertexai:
self.api_key = self.api_key or getenv("GOOGLE_API_KEY")
if not self.api_key:
Expand All @@ -46,9 +46,9 @@ def client(self):
_client_params["vertexai"] = True
_client_params["project"] = self.project_id or getenv("GOOGLE_CLOUD_PROJECT")
_client_params["location"] = self.location or getenv("GOOGLE_CLOUD_LOCATION")

_client_params = {k: v for k, v in _client_params.items() if v is not None}

if self.client_params:
_client_params.update(self.client_params)

Expand Down
11 changes: 11 additions & 0 deletions libs/agno/agno/embedder/qianfan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from agno.embedder.openai import OpenAIEmbedder


class QianfanEmbedder(OpenAIEmbedder):
id: str = "tao-8k"
base_url = "https://qianfan.baidubce.com/v2"
dimensions = 1024

def __post_init__(self):
if self.dimensions is None:
self.dimensions = 1024 if self.id == "tao-8k" else 1536
5 changes: 5 additions & 0 deletions libs/agno/agno/models/qianfan/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from agno.models.qianfan.chat import Qianfan

__all__ = [
"Qianfan",
]
36 changes: 36 additions & 0 deletions libs/agno/agno/models/qianfan/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from dataclasses import dataclass
from os import getenv
from typing import Optional

from agno.models.openai import OpenAILike


@dataclass
class Qianfan(OpenAILike):
"""
A class for interacting with Nvidia models.

Attributes:
id (str): The id of the Nvidia model to use. Default is "nvidia/llama-3.1-nemotron-70b-instruct".
name (str): The name of this chat model instance. Default is "Nvidia"
provider (str): The provider of the model. Default is "Nvidia".
api_key (str): The api key to authorize request to Nvidia.
base_url (str): The base url to which the requests are sent.
"""

id: str = "deepseek-v3"
name: str = "Qianfan"
provider: str = "Qianfan"

api_key: Optional[str] = getenv("QIANFAN_API_KEY")
base_url: str = "https://qianfan.baidubce.com/v2"

supports_native_structured_outputs: bool = True

role_map = {
"system": "system",
"user": "user",
"assistant": "assistant",
"tool": "tool",
"model": "assistant",
}
Empty file.
165 changes: 165 additions & 0 deletions libs/agno/tests/integration/models/qianfan/test_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import pytest
from pydantic import BaseModel, Field

from agno.agent import Agent, RunResponse
from agno.models.qianfan import Qianfan
from agno.storage.sqlite import SqliteStorage


def _assert_metrics(response: RunResponse):
input_tokens = response.metrics.get("input_tokens", [])
output_tokens = response.metrics.get("output_tokens", [])
total_tokens = response.metrics.get("total_tokens", [])

assert sum(input_tokens) > 0
assert sum(output_tokens) > 0
assert sum(total_tokens) > 0
assert sum(total_tokens) == sum(input_tokens) + sum(output_tokens)


def _get_model():
return Qianfan(id="deepseek-v3")


def test_basic():
agent = Agent(model=_get_model(), markdown=True, telemetry=False, monitoring=False)

# Print the response in the terminal
response: RunResponse = agent.run("Share a 2 sentence horror story")

assert response.content is not None
assert len(response.messages) == 3
assert [m.role for m in response.messages] == ["system", "user", "assistant"]

_assert_metrics(response)


def test_basic_stream():
agent = Agent(model=_get_model(), markdown=True, telemetry=False, monitoring=False)

response_stream = agent.run("Share a 2 sentence horror story", stream=True)

# Verify it's an iterator
assert hasattr(response_stream, "__iter__")

responses = list(response_stream)
assert len(responses) > 0
for response in responses:
assert response.content is not None

_assert_metrics(agent.run_response)


@pytest.mark.asyncio
async def test_async_basic():
agent = Agent(model=_get_model(), markdown=True, telemetry=False, monitoring=False)

response = await agent.arun("Share a 2 sentence horror story")

assert response.content is not None
assert len(response.messages) == 3
assert [m.role for m in response.messages] == ["system", "user", "assistant"]
_assert_metrics(response)


@pytest.mark.asyncio
async def test_async_basic_stream():
agent = Agent(model=_get_model(), markdown=True, telemetry=False, monitoring=False, debug_mode=True)

response_stream = await agent.arun("Share a 2 sentence horror story", stream=True)

async for response in response_stream:
assert response.content is not None

_assert_metrics(agent.run_response)


def test_with_memory():
agent = Agent(
model=_get_model(),
add_history_to_messages=True,
num_history_responses=5,
markdown=True,
telemetry=False,
monitoring=False,
)

# First interaction
response1 = agent.run("My name is John Smith")
assert response1.content is not None

# Second interaction should remember the name
response2 = agent.run("What's my name?")
assert "John Smith" in response2.content

# Verify memories were created
messages = agent.get_messages_for_session()
assert len(messages) == 5
assert [m.role for m in messages] == ["system", "user", "assistant", "user", "assistant"]

# Test metrics structure and types
_assert_metrics(response2)


def test_response_model():
class MovieScript(BaseModel):
title: str = Field(..., description="Movie title")
genre: str = Field(..., description="Movie genre")
plot: str = Field(..., description="Brief plot summary")

agent = Agent(
model=_get_model(),
markdown=True,
telemetry=False,
monitoring=False,
response_model=MovieScript,
)

response = agent.run("Create a movie about time travel")

# Verify structured output
assert isinstance(response.content, MovieScript)
assert response.content.title is not None
assert response.content.genre is not None
assert response.content.plot is not None


def test_json_response_mode():
class MovieScript(BaseModel):
title: str = Field(..., description="Movie title")
genre: str = Field(..., description="Movie genre")
plot: str = Field(..., description="Brief plot summary")

agent = Agent(
model=_get_model(),
use_json_mode=True,
telemetry=False,
monitoring=False,
response_model=MovieScript,
)

response = agent.run("Create a movie about time travel")

# Verify structured output
assert isinstance(response.content, MovieScript)
assert response.content.title is not None
assert response.content.genre is not None
assert response.content.plot is not None


def test_history():
agent = Agent(
model=_get_model(),
storage=SqliteStorage(table_name="agent_sessions", db_file="tmp/agent_storage.db"),
add_history_to_messages=True,
telemetry=False,
monitoring=False,
)
agent.run("Hello")
assert len(agent.run_response.messages) == 2
agent.run("Hello 2")
assert len(agent.run_response.messages) == 4
agent.run("Hello 3")
assert len(agent.run_response.messages) == 6
agent.run("Hello 4")
assert len(agent.run_response.messages) == 8
Loading