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

1"""Type definitions for LLM module.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from datetime import UTC, datetime 

7from typing import Any 

8 

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 

21 

22 

23@dataclass(init=False) 

24class ChatMessage(DomainModel): 

25 """A single chat message. 

26 

27 Implements ChatMessageProtocol with DomainModel semantics for validation. 

28 

29 Example: 

30 >>> msg = ChatMessage(role="user", content="Hello, how are you?") 

31 """ 

32 

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 ) 

55 

56 

57@dataclass(init=False) 

58class Completion(DomainModel): 

59 """LLM completion response. 

60 

61 Implements completion semantics with DomainModel for validation and additional fields. 

62 

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 """ 

70 

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 ) 

108 

109 

110@dataclass(init=False) 

111class StreamChunk(DomainModel): 

112 """A chunk of streamed completion. 

113 

114 Implements streaming semantics with DomainModel for validation. 

115 

116 Example: 

117 >>> chunk = StreamChunk(delta="Hello", model="gpt-4-turbo", finish_reason=None) 

118 """ 

119 

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 ) 

146 

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 

158 

159 

160from lexigram.ai.llm.exceptions import ( 

161 InvalidRequestError, 

162 LLMAuthenticationError, 

163 LLMRateLimitError, 

164) 

165 

166AIError = LLMError 

167 

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]