c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
110 lines
4.1 KiB
Python
110 lines
4.1 KiB
Python
"""Measure the real token cost of ingesting a chunk with cognee.
|
|
|
|
This is the only module that calls the LLM. Each sampled chunk is run through
|
|
cognee's summary and graph-extraction calls, and the real prompt/completion
|
|
token usage is read off each response (so instruction and schema overhead are
|
|
included). Multiple llm_models are run sequentially, switching cognee's config
|
|
between them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
|
|
import litellm
|
|
import tiktoken
|
|
from cognee.infrastructure.llm.config import get_llm_config
|
|
from cognee.infrastructure.llm.extraction import extract_content_graph, extract_summary
|
|
from cognee.shared.data_models import KnowledgeGraph, SummarizedContent
|
|
|
|
from cost_model import ChunkMeasurement
|
|
|
|
FALLBACK_ENCODING = "o200k_base"
|
|
|
|
|
|
def infer_provider(llm_model: str) -> str:
|
|
"""Map an llm_model name to its provider (only openai/anthropic are used)."""
|
|
leaf = llm_model.split("/")[-1].lower()
|
|
if llm_model.lower().startswith("anthropic/") or leaf.startswith("claude"):
|
|
return "anthropic"
|
|
return "openai"
|
|
|
|
|
|
def count_tokens(text: str, llm_model: str) -> int:
|
|
"""Local token count for the chunk content and the corpus (no API call)."""
|
|
if infer_provider(llm_model) == "anthropic":
|
|
return int(litellm.token_counter(model=llm_model, text=text))
|
|
return len(_openai_encoding(llm_model).encode(text))
|
|
|
|
|
|
def run_measurements(chunks: list[str], llm_models: list[str]) -> list[ChunkMeasurement]:
|
|
"""Measure every chunk under every llm_model. Seals the asyncio entrypoint."""
|
|
return asyncio.run(_measure_all(chunks, llm_models))
|
|
|
|
|
|
async def measure_chunk(chunk: str, llm_model: str) -> ChunkMeasurement:
|
|
"""Run cognee's two ingestion calls on one chunk and record real token usage."""
|
|
summary, graph = await asyncio.gather(
|
|
extract_summary(chunk, SummarizedContent),
|
|
extract_content_graph(chunk, KnowledgeGraph),
|
|
)
|
|
summary_prompt, summary_completion = _usage(summary)
|
|
graph_prompt, graph_completion = _usage(graph)
|
|
return ChunkMeasurement(
|
|
llm_model=llm_model,
|
|
input_tokens=count_tokens(chunk, llm_model),
|
|
summary_prompt_tokens=summary_prompt,
|
|
summary_completion_tokens=summary_completion,
|
|
graph_prompt_tokens=graph_prompt,
|
|
graph_completion_tokens=graph_completion,
|
|
)
|
|
|
|
|
|
async def _measure_all(chunks: list[str], llm_models: list[str]) -> list[ChunkMeasurement]:
|
|
measurements: list[ChunkMeasurement] = []
|
|
for llm_model in llm_models:
|
|
_configure_llm_model(llm_model)
|
|
measurements += await asyncio.gather(*(measure_chunk(chunk, llm_model) for chunk in chunks))
|
|
return measurements
|
|
|
|
|
|
def _configure_llm_model(llm_model: str) -> None:
|
|
"""Switch cognee onto llm_model: set env, then clear the cached config."""
|
|
provider = infer_provider(llm_model)
|
|
os.environ["LLM_PROVIDER"] = provider
|
|
os.environ["LLM_MODEL"] = llm_model
|
|
api_key = os.environ.get(f"{provider.upper()}_API_KEY") or os.environ.get("LLM_API_KEY")
|
|
if api_key:
|
|
os.environ["LLM_API_KEY"] = api_key
|
|
get_llm_config.cache_clear()
|
|
|
|
|
|
def _usage(result) -> tuple[int, int]:
|
|
"""Read (prompt, completion) tokens from an instructor result's raw response.
|
|
|
|
litellm names them prompt_tokens/completion_tokens; the Anthropic client names
|
|
them input_tokens/output_tokens.
|
|
"""
|
|
usage = result._raw_response.usage
|
|
prompt = _pick(usage, "prompt_tokens", "input_tokens")
|
|
completion = _pick(usage, "completion_tokens", "output_tokens")
|
|
return prompt, completion
|
|
|
|
|
|
def _pick(usage, *field_names: str) -> int:
|
|
"""Return the first present usage field (as int), across provider naming."""
|
|
for field_name in field_names:
|
|
value = getattr(usage, field_name, None)
|
|
if value is not None:
|
|
return int(value)
|
|
raise AttributeError(f"usage exposes none of {field_names}: {usage!r}")
|
|
|
|
|
|
def _openai_encoding(llm_model: str) -> tiktoken.Encoding:
|
|
model_name = llm_model.split("/")[-1]
|
|
try:
|
|
return tiktoken.encoding_for_model(model_name)
|
|
except KeyError:
|
|
return tiktoken.get_encoding(FALLBACK_ENCODING)
|