Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-llm/src/lexigram/ai/llm/types.py: 90%
58 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
1"""Type definitions for LLM module."""
3from __future__ import annotations
5from dataclasses import dataclass
6from datetime import UTC, datetime
7from typing import Any
9from lexigram.ai.llm.exceptions import LLMError
10from lexigram.contracts.ai.llm import (
11 FunctionCall,
12 Role,
13 TokenUsage,
14 ToolCall,
15)
16from lexigram.contracts.ai.multimodal import MessageContent
17from lexigram.contracts.ai.thinking import ThinkingResult
18from lexigram.contracts.core import Metadata
19from lexigram.domain import DomainModel
20from lexigram.validation import Field
23@dataclass(init=False)
24class ChatMessage(DomainModel):
25 """A single chat message.
27 Implements ChatMessageProtocol with DomainModel semantics for validation.
29 Example:
30 >>> msg = ChatMessage(role="user", content="Hello, how are you?")
31 """
33 role: Role = Field(description="Message role")
34 content: MessageContent = Field(description="Message content")
35 name: str | None = Field(default=None, description="Optional name for the message")
36 tool_call_id: str | None = Field(
37 default=None,
38 description="ID of tool call this message responds to",
39 )
40 tool_calls: list[ToolCall] | None = Field(
41 default=None,
42 description=(
43 "Native tool calls for an assistant turn, re-emitted to the model "
44 "before the matching ``tool`` role responses"
45 ),
46 )
47 thinking_blocks: list[dict[str, Any]] | None = Field(
48 default=None,
49 description=(
50 "Raw provider thinking/reasoning blocks for multi-turn re-injection. "
51 "Anthropic: list of {type, thinking, signature} dicts from a prior "
52 "assistant turn with extended thinking enabled."
53 ),
54 )
57@dataclass(init=False)
58class Completion(DomainModel):
59 """LLM completion response.
61 Implements completion semantics with DomainModel for validation and additional fields.
63 Example:
64 >>> completion = Completion(
65 ... content="Hello! I'm doing well, thank you.",
66 ... model="gpt-4-turbo",
67 ... usage=TokenUsage(prompt_tokens=10, completion_tokens=8, total_tokens=18)
68 ... )
69 """
71 content: str = Field(description="Generated completion text")
72 model: str = Field(description="Model used for generation")
73 provider: str = Field(default="", description="Provider that handled the request")
74 model_revision: str = Field(
75 default="", description="Provider-reported model revision"
76 )
77 prompt_hash: bytes | None = Field(
78 default=None, description="SHA-256 hash of the prompt"
79 )
80 completion_tokens: int = Field(default=0, description="Tokens in the completion")
81 prompt_tokens: int = Field(default=0, description="Tokens in the prompt")
82 request_id: str | None = Field(default=None, description="Provider-side request ID")
83 finish_reason: str | None = Field(
84 default=None,
85 description="Reason completion finished",
86 )
87 role: Role | None = Field(
88 default=None,
89 description="Role associated with this completion",
90 )
91 tool_calls: list[ToolCall] | None = Field(
92 default=None,
93 description="Tool calls requested by LLM",
94 )
95 usage: TokenUsage | None = Field(default=None, description="Token usage stats")
96 thinking: ThinkingResult | None = Field(
97 default=None,
98 description=(
99 "Normalised thinking/reasoning output from the model. "
100 "None when thinking is disabled or the provider does not support it."
101 ),
102 )
103 metadata: Metadata = Field(default_factory=dict, description="Additional metadata")
104 timestamp: datetime = Field(
105 default_factory=lambda: datetime.now(UTC),
106 description="Completion timestamp",
107 )
110@dataclass(init=False)
111class StreamChunk(DomainModel):
112 """A chunk of streamed completion.
114 Implements streaming semantics with DomainModel for validation.
116 Example:
117 >>> chunk = StreamChunk(delta="Hello", model="gpt-4-turbo", finish_reason=None)
118 """
120 delta: str | None = Field(default=None, description="Text delta for this chunk")
121 content: str | None = Field(
122 default=None, description="Compatibility alias for text delta"
123 )
124 tokens_used: int = Field(default=0, description="Tokens represented by this chunk")
125 metadata: Metadata = Field(
126 default_factory=dict, description="Additional chunk metadata"
127 )
128 model: str | None = Field(default=None, description="Model generating the stream")
129 finish_reason: str | None = Field(
130 default=None,
131 description="Reason stream finished (on last chunk)",
132 )
133 role: Role | None = Field(
134 default=None,
135 description="Role associated with this chunk",
136 )
137 index: int = Field(default=0, description="Chunk index in stream")
138 thinking_delta: str | None = Field(
139 default=None,
140 description="Thinking content delta for this chunk (during streaming).",
141 )
142 is_thinking: bool = Field(
143 default=False,
144 description="True when this chunk contains thinking content rather than answer text.",
145 )
147 def __post_init__(self) -> None:
148 if (
149 self.delta is not None
150 and self.content is not None
151 and self.delta != self.content
152 ):
153 raise ValueError("delta and content must match when both are provided")
154 if self.delta is None and self.content is not None:
155 self.delta = self.content
156 if self.content is None and self.delta is not None:
157 self.content = self.delta
160from lexigram.ai.llm.exceptions import (
161 InvalidRequestError,
162 LLMAuthenticationError,
163 LLMRateLimitError,
164)
166AIError = LLMError
168__all__ = [
169 "AIError",
170 "ChatMessage",
171 "Completion",
172 "FunctionCall",
173 "InvalidRequestError",
174 "LLMAuthenticationError",
175 "LLMError",
176 "LLMRateLimitError",
177 "Role",
178 "StreamChunk",
179 "ThinkingResult",
180 "TokenUsage",
181 "ToolCall",
182]