From 6f8ee409f5d635206bdf2d5843e83e0cfc4c713d Mon Sep 17 00:00:00 2001 From: Arnau Gris Date: Tue, 1 Sep 2026 11:05:51 +0200 Subject: [PATCH] Add explicit operation type to external usage --- agents/google/src/hyperforge_google/agent.py | 3 ++- agents/google/tests/test_google.py | 3 ++- .../src/hyperforge_perplexity/perplexity.py | 3 ++- agents/perplexity/tests/test_perplexity.py | 3 ++- .../src/hyperforge_perplexity_search/agent.py | 9 +++++++-- .../tests/test_perplexity_search.py | 3 ++- hyperforge/src/hyperforge/models.py | 8 ++++++++ hyperforge/tests/models/test_external_usage.py | 12 ++++++++++-- 8 files changed, 35 insertions(+), 9 deletions(-) diff --git a/agents/google/src/hyperforge_google/agent.py b/agents/google/src/hyperforge_google/agent.py index c362c9ea..41f0649b 100644 --- a/agents/google/src/hyperforge_google/agent.py +++ b/agents/google/src/hyperforge_google/agent.py @@ -16,7 +16,7 @@ from hyperforge.definition import FunctionDefinition from hyperforge.manager import Manager from hyperforge.memory.memory import Chunk, Context, QuestionMemory -from hyperforge.models import ExternalUsage +from hyperforge.models import ExternalUsage, ExternalUsageOperation from hyperforge.utils.http import safe_http_client from hyperforge_google.config import GoogleAgentConfig @@ -161,6 +161,7 @@ async def internet_search( timeit=time() - t0, external_usage=[ ExternalUsage( + operation=ExternalUsageOperation.INTERNET_SEARCH, provider="google", model=(response.model_version if response is not None else None) or self.config.gen_model_id, diff --git a/agents/google/tests/test_google.py b/agents/google/tests/test_google.py index a7c7bd97..b650a783 100644 --- a/agents/google/tests/test_google.py +++ b/agents/google/tests/test_google.py @@ -7,7 +7,7 @@ from hyperforge.memory import Context from hyperforge.memory.memory import EphemeralSessionMemory from hyperforge.minimal_fixtures import cassette_nua_key -from hyperforge.models import MemoryConfig, Rules +from hyperforge.models import ExternalUsageOperation, MemoryConfig, Rules from nuclia.lib.nua import AsyncNuaClient from hyperforge_google.agent import GoogleAgent @@ -93,6 +93,7 @@ async def test_google(): usage = next(step.external_usage for step in memory.steps if step.external_usage) assert len(usage) == 1 event = usage[0] + assert event.operation == ExternalUsageOperation.INTERNET_SEARCH assert event.provider == "google" assert event.model == "gemini-2.5-flash" assert event.input_tokens > 0 diff --git a/agents/perplexity/src/hyperforge_perplexity/perplexity.py b/agents/perplexity/src/hyperforge_perplexity/perplexity.py index 369c9518..79422001 100644 --- a/agents/perplexity/src/hyperforge_perplexity/perplexity.py +++ b/agents/perplexity/src/hyperforge_perplexity/perplexity.py @@ -9,7 +9,7 @@ from hyperforge.definition import FunctionDefinition from hyperforge.manager import Manager from hyperforge.memory import Chunk, Context, QuestionMemory -from hyperforge.models import ExternalUsage +from hyperforge.models import ExternalUsage, ExternalUsageOperation from hyperforge.utils.http import safe_http_client from nuclia.lib.nua_responses import Image from perplexity.types import ChatMessageInput @@ -157,6 +157,7 @@ async def internet_search( timeit=time() - t0, external_usage=[ ExternalUsage( + operation=ExternalUsageOperation.INTERNET_SEARCH, provider="perplexity", model="sonar-pro", input_tokens=(usage.prompt_tokens or 0) if usage else 0, diff --git a/agents/perplexity/tests/test_perplexity.py b/agents/perplexity/tests/test_perplexity.py index 81250769..afe73efe 100644 --- a/agents/perplexity/tests/test_perplexity.py +++ b/agents/perplexity/tests/test_perplexity.py @@ -7,7 +7,7 @@ from hyperforge.memory import Context from hyperforge.memory.memory import EphemeralSessionMemory from hyperforge.minimal_fixtures import cassette_nua_key -from hyperforge.models import MemoryConfig, Rules +from hyperforge.models import ExternalUsageOperation, MemoryConfig, Rules from nuclia.lib.nua import AsyncNuaClient from hyperforge_perplexity.config import PerplexityAgentConfig @@ -89,6 +89,7 @@ async def test_perplexity(): usage = next(step.external_usage for step in memory.steps if step.external_usage) assert len(usage) == 1 event = usage[0] + assert event.operation == ExternalUsageOperation.INTERNET_SEARCH assert event.provider == "perplexity" assert event.model == "sonar-pro" assert event.input_tokens > 0 diff --git a/agents/perplexity_search/src/hyperforge_perplexity_search/agent.py b/agents/perplexity_search/src/hyperforge_perplexity_search/agent.py index 1069a69d..82387249 100644 --- a/agents/perplexity_search/src/hyperforge_perplexity_search/agent.py +++ b/agents/perplexity_search/src/hyperforge_perplexity_search/agent.py @@ -8,7 +8,7 @@ from hyperforge.definition import FunctionDefinition from hyperforge.manager import Manager from hyperforge.memory import Chunk, Context, QuestionMemory -from hyperforge.models import ExternalUsage +from hyperforge.models import ExternalUsage, ExternalUsageOperation from hyperforge_perplexity.driver import PerplexityDriver from hyperforge_perplexity_search.config import PerplexitySearchAgentConfig @@ -103,7 +103,12 @@ async def search( else "No results found", timeit=time() - t0, external_usage=[ - ExternalUsage(provider="perplexity", model="search", requests=1) + ExternalUsage( + operation=ExternalUsageOperation.INTERNET_SEARCH, + provider="perplexity", + model="search", + requests=1, + ) ], ) return context diff --git a/agents/perplexity_search/tests/test_perplexity_search.py b/agents/perplexity_search/tests/test_perplexity_search.py index 2a5b51a2..0ea5876b 100644 --- a/agents/perplexity_search/tests/test_perplexity_search.py +++ b/agents/perplexity_search/tests/test_perplexity_search.py @@ -7,7 +7,7 @@ from hyperforge.memory import Context from hyperforge.memory.memory import EphemeralSessionMemory from hyperforge.minimal_fixtures import cassette_nua_key -from hyperforge.models import MemoryConfig, Rules +from hyperforge.models import ExternalUsageOperation, MemoryConfig, Rules from nuclia.lib.nua import AsyncNuaClient from hyperforge_perplexity_search.agent import PerplexitySearchAgent @@ -83,6 +83,7 @@ async def test_perplexity_search(): usage = next(step.external_usage for step in memory.steps if step.external_usage) assert len(usage) == 1 event = usage[0] + assert event.operation == ExternalUsageOperation.INTERNET_SEARCH assert event.provider == "perplexity" assert event.model == "search" assert event.input_tokens == 0 diff --git a/hyperforge/src/hyperforge/models.py b/hyperforge/src/hyperforge/models.py index 97cb7347..160eda73 100644 --- a/hyperforge/src/hyperforge/models.py +++ b/hyperforge/src/hyperforge/models.py @@ -1,6 +1,7 @@ from __future__ import annotations import uuid +from enum import Enum from typing import ( Any, Dict, @@ -110,7 +111,14 @@ class VegaLiteVisualization(BaseModel): Visualization = Union[VegaLiteVisualization] +class ExternalUsageOperation(str, Enum): + INTERNET_SEARCH = "internet_search" + + class ExternalUsage(BaseModel): + operation: ExternalUsageOperation = Field( + description="The external operation that generated this usage.", + ) provider: str = Field( description="The external provider that generated this usage.", examples=["perplexity", "google", "brave"], diff --git a/hyperforge/tests/models/test_external_usage.py b/hyperforge/tests/models/test_external_usage.py index dbfca5c6..90f3909d 100644 --- a/hyperforge/tests/models/test_external_usage.py +++ b/hyperforge/tests/models/test_external_usage.py @@ -2,15 +2,22 @@ from hyperforge.interaction import AragAnswer from hyperforge.memory.memory import EphemeralSessionMemory -from hyperforge.models import ExternalUsage, MemoryConfig, Rules +from hyperforge.models import ExternalUsage, ExternalUsageOperation, MemoryConfig, Rules def test_external_usage_defaults_and_roundtrip(): - usage = ExternalUsage(provider="google", model="gemini-2.5-flash") + usage = ExternalUsage( + operation=ExternalUsageOperation.INTERNET_SEARCH, + provider="perplexity", + model="search", + ) + assert usage.operation == ExternalUsageOperation.INTERNET_SEARCH assert usage.input_tokens == 0 assert usage.output_tokens == 0 assert usage.requests == 1 + assert usage.provider == "perplexity" + assert usage.model == "search" assert ExternalUsage.model_validate(usage.model_dump()) == usage @@ -31,6 +38,7 @@ async def callback(answer: AragAnswer): memory.set_callback_fn(callback) usage = ExternalUsage( + operation=ExternalUsageOperation.INTERNET_SEARCH, provider="perplexity", model="sonar-pro", input_tokens=12,