Skip to content

LLMResult.llm_output is None in BaseCallbackHandler.on_llm_end #151

@ai-learner-00

Description

@ai-learner-00

I use a callback to calculate tokens for an openai model. When trying it with cohere, I noticed that response.llm_output is None. Is this normal?

"""
Custom openai callback

https://python.langchain.com/docs/concepts/callbacks/
"""

from datetime import datetime, timezone
from typing import Any, Dict, List
from uuid import UUID

from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.messages import BaseMessage
from langchain_core.outputs import LLMResult
from pydantic import BaseModel

from app.usage import get_service_id

from .get_service_id import get_service_id
from .task import LLMTask, LLMTokenUsage, Task


class TokenUsage(BaseModel):
    """Token usage"""

    input_tokens: int
    output_tokens: int
    total_tokens: int


class LLMOutput(BaseModel):
    """
    Output included in llm response
    """

    token_usage: TokenUsage
    model: str
    # system_fingerprint: str


class EmbeddingStartInfo(BaseModel):
    start_date_time: datetime
    total_tokens: int


class CustomOpenAICallback(BaseCallbackHandler):
    """Callback to intercept token information"""

    def __init__(self):
        self.tasks: list[Task] = []
        self.run_start_times: dict[UUID, datetime] = {}
        """Dictionnary of run_id -> start_time to be able to execute multiple requests in parallel and get the correct timings"""
        self.run_embedding_start_info: dict[UUID, EmbeddingStartInfo] = {}

    def on_chat_model_start(
        self,
        serialized: Dict[str, Any],
        messages: List[List[BaseMessage]],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: List[str] | None = None,
        metadata: Dict[str, Any] | None = None,
        **kwargs: Any,
    ):
        """When a chat model starts."""
        self.run_start_times[run_id] = datetime.now(tz=timezone.utc)

    def on_llm_start(
        self,
        serialized: Dict[str, Any],
        prompts: List[str],
        *,
        run_id: UUID,
        parent_run_id: UUID | None = None,
        tags: List[str] | None = None,
        metadata: Dict[str, Any] | None = None,
        **kwargs: Any,
    ):
        """When a llm starts"""
        # TODO do we use completion models? Should the logic be the same?

    def on_llm_end(self, response: LLMResult, *, run_id: UUID, parent_run_id: UUID | None = None, **kwargs: Any):
        """
        When an llm OR chat model ends.
        Use information returned by the OpenAI API response.
        """
        if response.llm_output is None:
            return

        end_date_time = datetime.now(tz=timezone.utc)
        start_date_time = self.run_start_times.get(run_id)
        if start_date_time is None:
            raise RuntimeError(f"Start time not found for run_id: {run_id}")

        output = LLMOutput(**response.llm_output)

        task = LLMTask(
            service_id=get_service_id(model_name=output.model),
            model_name=output.model,
            token_usage=LLMTokenUsage(
                input_tokens=output.token_usage.input_tokens,
                output_tokens=output.token_usage.output_tokens,
            ),
            start_date_time=start_date_time,
            end_date_time=end_date_time,
        )
        self.tasks.append(task)

Here is what the response object looks like:

LLMResult(
    generations=[
        [
            ChatGeneration(
                text='...',
                generation_info={
                    'id': '2a87fc55-57e6-4065-9afa-a283265b1699',
                    'finish_reason': 'COMPLETE',
                    'content': '...',
                    'token_count': {'input_tokens': 5037.0, 'output_tokens': 1189.0}
                },
                message=AIMessage(
                    content='...',
                    additional_kwargs={
                        'id': '2a87fc55-57e6-4065-9afa-a283265b1699',
                        'finish_reason': 'COMPLETE',
                        'content': '...',
                        'token_count': {'input_tokens': 5037.0, 'output_tokens': 1189.0}
                    },
                    response_metadata={
                        'id': '2a87fc55-57e6-4065-9afa-a283265b1699',
                        'finish_reason': 'COMPLETE',
                        'content': '...',
                        'token_count': {'input_tokens': 5037.0, 'output_tokens': 1189.0}
                    },
                    id='run--6fa795d7-75e1-4e84-b9bb-07a4c554e6af-0',
                    usage_metadata={'input_tokens': 5037, 'output_tokens': 1189, 'total_tokens': 6226}
                )
            )
        ]
    ],
    llm_output=None,
    run=None,
    type='LLMResult'
)

I am using version 0.4.6

root@f313e273197e:/app# poetry debug resolve --tree --install langchain-cohere
Resolving dependencies... (22.9s)

Resolution results:

langchain-cohere 0.4.6 An integration package connecting Cohere and LangChain
├── cohere >=5.18.0,<6.0
│   ├── fastavro >=1.9.4,<2.0.0 
│   ├── httpx >=0.21.2 
│   │   ├── anyio * 
│   │   │   ├── exceptiongroup >=1.0.2 
│   │   │   │   └── typing-extensions >=4.6.0 
│   │   │   ├── idna >=2.8 
│   │   │   ├── sniffio >=1.1 
│   │   │   └── typing-extensions >=4.5 (circular dependency aborted here)
│   │   ├── certifi * 
│   │   ├── httpcore ==1.* 
│   │   │   ├── certifi * (circular dependency aborted here)
│   │   │   └── h11 >=0.16 
│   │   └── idna * (circular dependency aborted here)
│   ├── httpx-sse 0.4.0 
│   ├── pydantic >=1.9.2 
│   │   ├── annotated-types >=0.6.0 
│   │   ├── pydantic-core 2.33.2 
│   │   │   └── typing-extensions >=4.6.0,<4.7.0 || >4.7.0 (circular dependency aborted here)
│   │   ├── typing-extensions >=4.12.2 (circular dependency aborted here)
│   │   └── typing-inspection >=0.4.0 
│   │       └── typing-extensions >=4.12.0 (circular dependency aborted here)
│   ├── pydantic-core >=2.18.2,<3.0.0 (circular dependency aborted here)
│   ├── requests >=2.0.0,<3.0.0 
│   │   ├── certifi >=2017.4.17 (circular dependency aborted here)
│   │   ├── charset-normalizer >=2,<4 
│   │   ├── idna >=2.5,<4 (circular dependency aborted here)
│   │   └── urllib3 >=1.21.1,<3 
│   ├── tokenizers >=0.15,<1 
│   │   └── huggingface-hub >=0.16.4,<2.0 
│   │       ├── filelock * 
│   │       ├── fsspec >=2023.5.0 
│   │       ├── hf-xet >=1.1.3,<2.0.0 
│   │       ├── packaging >=20.9 
│   │       ├── pyyaml >=5.1 
│   │       ├── requests * (circular dependency aborted here)
│   │       ├── tqdm >=4.42.1 
│   │       │   └── colorama * 
│   │       └── typing-extensions >=3.7.4.3 (circular dependency aborted here)
│   ├── types-requests >=2.0.0,<3.0.0 
│   │   └── urllib3 >=2 (circular dependency aborted here)
│   └── typing-extensions >=4.0.0 (circular dependency aborted here)
├── langchain-community >=0.3.0,<0.4.0
│   ├── aiohttp >=3.8.3,<4.0.0 
│   │   ├── aiohappyeyeballs >=2.5.0 
│   │   ├── aiosignal >=1.4.0 
│   │   │   ├── frozenlist >=1.1.0 
│   │   │   └── typing-extensions >=4.2 
│   │   ├── async-timeout >=4.0,<6.0 
│   │   ├── attrs >=17.3.0 
│   │   ├── frozenlist >=1.1.1 (circular dependency aborted here)
│   │   ├── multidict >=4.5,<7.0 
│   │   │   └── typing-extensions >=4.1.0 (circular dependency aborted here)
│   │   ├── propcache >=0.2.0 
│   │   └── yarl >=1.17.0,<2.0 
│   │       ├── idna >=2.0 
│   │       ├── multidict >=4.0 (circular dependency aborted here)
│   │       └── propcache >=0.2.1 (circular dependency aborted here)
│   ├── dataclasses-json >=0.6.7,<0.7 
│   │   ├── marshmallow >=3.18.0,<4.0.0 
│   │   │   └── packaging >=17.0 
│   │   └── typing-inspect >=0.4.0,<1 
│   │       ├── mypy-extensions >=0.3.0 
│   │       └── typing-extensions >=3.7.4 (circular dependency aborted here)
│   ├── httpx-sse >=0.4.0,<1.0.0 
│   ├── langchain >=0.3.27,<2.0.0 
│   │   ├── async-timeout >=4.0.0,<5.0.0 (circular dependency aborted here)
│   │   ├── langchain-core >=0.3.72,<1.0.0 
│   │   │   ├── jsonpatch >=1.33,<2.0 
│   │   │   │   └── jsonpointer >=1.9 
│   │   │   ├── langsmith >=0.3.45 
│   │   │   │   ├── httpx >=0.23.0,<1 
│   │   │   │   │   ├── anyio * 
│   │   │   │   │   │   ├── exceptiongroup >=1.0.2 
│   │   │   │   │   │   │   └── typing-extensions >=4.6.0 (circular dependency aborted here)
│   │   │   │   │   │   ├── idna >=2.8 (circular dependency aborted here)
│   │   │   │   │   │   ├── sniffio >=1.1 
│   │   │   │   │   │   └── typing-extensions >=4.5 (circular dependency aborted here)
│   │   │   │   │   ├── certifi * 
│   │   │   │   │   ├── httpcore ==1.* 
│   │   │   │   │   │   ├── certifi * (circular dependency aborted here)
│   │   │   │   │   │   └── h11 >=0.16 
│   │   │   │   │   └── idna * (circular dependency aborted here)
│   │   │   │   ├── orjson >=3.9.14 
│   │   │   │   ├── packaging >=23.2 (circular dependency aborted here)
│   │   │   │   ├── pydantic >=1,<3 
│   │   │   │   │   ├── annotated-types >=0.6.0 
│   │   │   │   │   ├── pydantic-core 2.33.2 
│   │   │   │   │   │   └── typing-extensions >=4.6.0,<4.7.0 || >4.7.0 (circular dependency aborted here)
│   │   │   │   │   ├── typing-extensions >=4.12.2 (circular dependency aborted here)
│   │   │   │   │   └── typing-inspection >=0.4.0 
│   │   │   │   │       └── typing-extensions >=4.12.0 (circular dependency aborted here)
│   │   │   │   ├── requests >=2.0.0 
│   │   │   │   │   ├── certifi >=2017.4.17 (circular dependency aborted here)
│   │   │   │   │   ├── charset-normalizer >=2,<4 
│   │   │   │   │   ├── idna >=2.5,<4 (circular dependency aborted here)
│   │   │   │   │   └── urllib3 >=1.21.1,<3 
│   │   │   │   ├── requests-toolbelt >=1.0.0 
│   │   │   │   │   └── requests >=2.0.1,<3.0.0 (circular dependency aborted here)
│   │   │   │   └── zstandard >=0.23.0 
│   │   │   ├── packaging >=23.2 (circular dependency aborted here)
│   │   │   ├── pydantic >=2.7.4 (circular dependency aborted here)
│   │   │   ├── pyyaml >=5.3 
│   │   │   ├── tenacity >=8.1.0,<8.4.0 || >8.4.0,<10.0.0 
│   │   │   └── typing-extensions >=4.7 (circular dependency aborted here)
│   │   ├── langchain-text-splitters >=0.3.9,<1.0.0 
│   │   │   └── langchain-core >=0.3.75,<2.0.0 (circular dependency aborted here)
│   │   ├── langsmith >=0.1.17 (circular dependency aborted here)
│   │   ├── pydantic >=2.7.4,<3.0.0 (circular dependency aborted here)
│   │   ├── pyyaml >=5.3 (circular dependency aborted here)
│   │   ├── requests >=2,<3 (circular dependency aborted here)
│   │   └── sqlalchemy >=1.4,<3 
│   │       ├── greenlet >=1 
│   │       └── typing-extensions >=4.6.0 (circular dependency aborted here)
│   ├── langchain-core >=0.3.75,<2.0.0 (circular dependency aborted here)
│   ├── langsmith >=0.1.125 (circular dependency aborted here)
│   ├── numpy >=1.26.2 
│   ├── numpy >=2.1.0 (circular dependency aborted here)
│   ├── pydantic-settings >=2.10.1,<3.0.0 
│   │   ├── pydantic >=2.7.0 (circular dependency aborted here)
│   │   ├── python-dotenv >=0.21.0 
│   │   └── typing-inspection >=0.4.0 (circular dependency aborted here)
│   ├── pyyaml >=5.3 (circular dependency aborted here)
│   ├── requests >=2.32.5,<3 (circular dependency aborted here)
│   ├── sqlalchemy >=1.4,<3 (circular dependency aborted here)
│   └── tenacity >=8.1.0,<8.4.0 || >8.4.0,<10 (circular dependency aborted here)
├── langchain-core >=0.3.76,<0.4.0
│   ├── jsonpatch >=1.33,<2.0 
│   │   └── jsonpointer >=1.9 
│   ├── langsmith >=0.3.45 
│   │   ├── httpx >=0.23.0,<1 
│   │   │   ├── anyio * 
│   │   │   │   ├── exceptiongroup >=1.0.2 
│   │   │   │   │   └── typing-extensions >=4.6.0 
│   │   │   │   ├── idna >=2.8 
│   │   │   │   ├── sniffio >=1.1 
│   │   │   │   └── typing-extensions >=4.5 (circular dependency aborted here)
│   │   │   ├── certifi * 
│   │   │   ├── httpcore ==1.* 
│   │   │   │   ├── certifi * (circular dependency aborted here)
│   │   │   │   └── h11 >=0.16 
│   │   │   └── idna * (circular dependency aborted here)
│   │   ├── orjson >=3.9.14 
│   │   ├── packaging >=23.2 
│   │   ├── pydantic >=1,<3 
│   │   │   ├── annotated-types >=0.6.0 
│   │   │   ├── pydantic-core 2.33.2 
│   │   │   │   └── typing-extensions >=4.6.0,<4.7.0 || >4.7.0 (circular dependency aborted here)
│   │   │   ├── typing-extensions >=4.12.2 (circular dependency aborted here)
│   │   │   └── typing-inspection >=0.4.0 
│   │   │       └── typing-extensions >=4.12.0 (circular dependency aborted here)
│   │   ├── requests >=2.0.0 
│   │   │   ├── certifi >=2017.4.17 (circular dependency aborted here)
│   │   │   ├── charset-normalizer >=2,<4 
│   │   │   ├── idna >=2.5,<4 (circular dependency aborted here)
│   │   │   └── urllib3 >=1.21.1,<3 
│   │   ├── requests-toolbelt >=1.0.0 
│   │   │   └── requests >=2.0.1,<3.0.0 (circular dependency aborted here)
│   │   └── zstandard >=0.23.0 
│   ├── packaging >=23.2 (circular dependency aborted here)
│   ├── pydantic >=2.7.4 (circular dependency aborted here)
│   ├── pyyaml >=5.3 
│   ├── tenacity >=8.1.0,<8.4.0 || >8.4.0,<10.0.0 
│   └── typing-extensions >=4.7 (circular dependency aborted here)
├── pydantic >=2,<3
│   ├── annotated-types >=0.6.0 
│   ├── pydantic-core 2.33.2 
│   │   └── typing-extensions >=4.6.0,<4.7.0 || >4.7.0 
│   ├── typing-extensions >=4.12.2 (circular dependency aborted here)
│   └── typing-inspection >=0.4.0 
│       └── typing-extensions >=4.12.0 (circular dependency aborted here)
└── types-pyyaml >=6.0.12.20240917,<7.0.0.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions