never executed always true always false
1 module PureClaw.Providers.Class
2 ( -- * Message types
3 Role (..)
4 , ContentBlock (..)
5 , Message (..)
6 , roleToText
7 -- * Convenience constructors
8 , textMessage
9 , toolResultMessage
10 -- * Content block queries
11 , responseText
12 , toolUseCalls
13 -- * Tool definitions
14 , ToolDefinition (..)
15 , ToolChoice (..)
16 -- * Request and response
17 , CompletionRequest (..)
18 , CompletionResponse (..)
19 , Usage (..)
20 -- * Provider typeclass
21 , Provider (..)
22 -- * Existential wrapper
23 , SomeProvider (..)
24 ) where
25
26 import Data.Aeson (Value)
27 import Data.Text (Text)
28 import Data.Text qualified as T
29
30 import PureClaw.Core.Types
31
32 -- | Role in a conversation. System prompts are handled separately
33 -- via 'CompletionRequest._cr_systemPrompt' rather than as messages,
34 -- since providers differ on how they handle system content.
35 data Role = User | Assistant
36 deriving stock (Show, Eq, Ord)
37
38 -- | A single content block within a message. Messages contain one or
39 -- more content blocks, allowing mixed text and tool interactions.
40 data ContentBlock
41 = TextBlock Text
42 | ToolUseBlock
43 { _tub_id :: ToolCallId
44 , _tub_name :: Text
45 , _tub_input :: Value
46 }
47 | ToolResultBlock
48 { _trb_toolUseId :: ToolCallId
49 , _trb_content :: Text
50 , _trb_isError :: Bool
51 }
52 deriving stock (Show, Eq)
53
54 -- | A single message in a conversation. Content is a list of blocks
55 -- to support tool use/result interleaving with text.
56 data Message = Message
57 { _msg_role :: Role
58 , _msg_content :: [ContentBlock]
59 }
60 deriving stock (Show, Eq)
61
62 -- | Convert a role to its API text representation.
63 roleToText :: Role -> Text
64 roleToText User = "user"
65 roleToText Assistant = "assistant"
66
67 -- | Create a simple text message (the common case).
68 textMessage :: Role -> Text -> Message
69 textMessage role txt = Message role [TextBlock txt]
70
71 -- | Create a tool result message (user role with tool results).
72 toolResultMessage :: [(ToolCallId, Text, Bool)] -> Message
73 toolResultMessage results = Message User
74 [ ToolResultBlock callId content isErr
75 | (callId, content, isErr) <- results
76 ]
77
78 -- | Extract concatenated text from a response's content blocks.
79 responseText :: CompletionResponse -> Text
80 responseText resp =
81 let texts = [t | TextBlock t <- _crsp_content resp]
82 in T.intercalate "\n" texts
83
84 -- | Extract tool use calls from a response's content blocks.
85 toolUseCalls :: CompletionResponse -> [(ToolCallId, Text, Value)]
86 toolUseCalls resp =
87 [ (_tub_id b, _tub_name b, _tub_input b)
88 | b@ToolUseBlock {} <- _crsp_content resp
89 ]
90
91 -- | Tool definition for offering tools to the provider.
92 data ToolDefinition = ToolDefinition
93 { _td_name :: Text
94 , _td_description :: Text
95 , _td_inputSchema :: Value
96 }
97 deriving stock (Show, Eq)
98
99 -- | Tool choice constraint for the provider.
100 data ToolChoice
101 = AutoTool
102 | AnyTool
103 | SpecificTool Text
104 deriving stock (Show, Eq)
105
106 -- | Request to an LLM provider.
107 data CompletionRequest = CompletionRequest
108 { _cr_model :: ModelId
109 , _cr_messages :: [Message]
110 , _cr_systemPrompt :: Maybe Text
111 , _cr_maxTokens :: Maybe Int
112 , _cr_tools :: [ToolDefinition]
113 , _cr_toolChoice :: Maybe ToolChoice
114 }
115 deriving stock (Show, Eq)
116
117 -- | Token usage information.
118 data Usage = Usage
119 { _usage_inputTokens :: Int
120 , _usage_outputTokens :: Int
121 }
122 deriving stock (Show, Eq)
123
124 -- | Response from an LLM provider.
125 data CompletionResponse = CompletionResponse
126 { _crsp_content :: [ContentBlock]
127 , _crsp_model :: ModelId
128 , _crsp_usage :: Maybe Usage
129 }
130 deriving stock (Show, Eq)
131
132 -- | LLM provider interface. Each provider (Anthropic, OpenAI, etc.)
133 -- implements this typeclass.
134 class Provider p where
135 complete :: p -> CompletionRequest -> IO CompletionResponse
136
137 -- | Existential wrapper for runtime provider selection (e.g. from config).
138 data SomeProvider where
139 MkProvider :: Provider p => p -> SomeProvider
140
141 instance Provider SomeProvider where
142 complete (MkProvider p) = complete p