@@ -2,24 +2,232 @@ use alloc::string::String;
22use alloc:: vec:: Vec ;
33use serde:: { Deserialize , Serialize } ;
44
5+ /// Full OpenAI-compatible chat completion request.
6+ /// The challenge WASM builds this completely; the host just proxies it.
57#[ derive( Clone , Debug , Serialize , Deserialize ) ]
68pub struct LlmRequest {
79 pub model : String ,
810 pub messages : Vec < LlmMessage > ,
9- pub max_tokens : u32 ,
10- pub temperature : f32 ,
11+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
12+ pub max_tokens : Option < u32 > ,
13+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
14+ pub temperature : Option < f32 > ,
15+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
16+ pub top_p : Option < f32 > ,
17+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
18+ pub frequency_penalty : Option < f32 > ,
19+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
20+ pub presence_penalty : Option < f32 > ,
21+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
22+ pub stop : Option < Vec < String > > ,
23+ /// OpenAI function calling / tools
24+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
25+ pub tools : Option < Vec < Tool > > ,
26+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
27+ pub tool_choice : Option < ToolChoice > ,
28+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
29+ pub response_format : Option < ResponseFormat > ,
30+ }
31+
32+ impl LlmRequest {
33+ pub fn simple ( model : & str , messages : Vec < LlmMessage > , max_tokens : u32 ) -> Self {
34+ Self {
35+ model : String :: from ( model) ,
36+ messages,
37+ max_tokens : Some ( max_tokens) ,
38+ temperature : Some ( 0.1 ) ,
39+ top_p : None ,
40+ frequency_penalty : None ,
41+ presence_penalty : None ,
42+ stop : None ,
43+ tools : None ,
44+ tool_choice : None ,
45+ response_format : None ,
46+ }
47+ }
48+
49+ pub fn with_tools (
50+ model : & str ,
51+ messages : Vec < LlmMessage > ,
52+ tools : Vec < Tool > ,
53+ max_tokens : u32 ,
54+ ) -> Self {
55+ Self {
56+ model : String :: from ( model) ,
57+ messages,
58+ max_tokens : Some ( max_tokens) ,
59+ temperature : Some ( 0.1 ) ,
60+ top_p : None ,
61+ frequency_penalty : None ,
62+ presence_penalty : None ,
63+ stop : None ,
64+ tools : Some ( tools) ,
65+ tool_choice : Some ( ToolChoice :: Auto ) ,
66+ response_format : None ,
67+ }
68+ }
1169}
1270
1371#[ derive( Clone , Debug , Serialize , Deserialize ) ]
1472pub struct LlmMessage {
1573 pub role : String ,
16- pub content : String ,
74+ pub content : Option < String > ,
75+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
76+ pub name : Option < String > ,
77+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
78+ pub tool_calls : Option < Vec < ToolCall > > ,
79+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
80+ pub tool_call_id : Option < String > ,
81+ }
82+
83+ impl LlmMessage {
84+ pub fn system ( content : & str ) -> Self {
85+ Self {
86+ role : String :: from ( "system" ) ,
87+ content : Some ( String :: from ( content) ) ,
88+ name : None ,
89+ tool_calls : None ,
90+ tool_call_id : None ,
91+ }
92+ }
93+
94+ pub fn user ( content : & str ) -> Self {
95+ Self {
96+ role : String :: from ( "user" ) ,
97+ content : Some ( String :: from ( content) ) ,
98+ name : None ,
99+ tool_calls : None ,
100+ tool_call_id : None ,
101+ }
102+ }
103+
104+ pub fn assistant ( content : & str ) -> Self {
105+ Self {
106+ role : String :: from ( "assistant" ) ,
107+ content : Some ( String :: from ( content) ) ,
108+ name : None ,
109+ tool_calls : None ,
110+ tool_call_id : None ,
111+ }
112+ }
113+
114+ pub fn assistant_tool_calls ( tool_calls : Vec < ToolCall > ) -> Self {
115+ Self {
116+ role : String :: from ( "assistant" ) ,
117+ content : None ,
118+ name : None ,
119+ tool_calls : Some ( tool_calls) ,
120+ tool_call_id : None ,
121+ }
122+ }
123+
124+ pub fn tool ( tool_call_id : & str , content : & str ) -> Self {
125+ Self {
126+ role : String :: from ( "tool" ) ,
127+ content : Some ( String :: from ( content) ) ,
128+ name : None ,
129+ tool_calls : None ,
130+ tool_call_id : Some ( String :: from ( tool_call_id) ) ,
131+ }
132+ }
133+ }
134+
135+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
136+ pub struct Tool {
137+ #[ serde( rename = "type" ) ]
138+ pub tool_type : String ,
139+ pub function : FunctionDef ,
140+ }
141+
142+ impl Tool {
143+ pub fn function ( name : & str , description : & str , parameters : & str ) -> Self {
144+ Self {
145+ tool_type : String :: from ( "function" ) ,
146+ function : FunctionDef {
147+ name : String :: from ( name) ,
148+ description : Some ( String :: from ( description) ) ,
149+ parameters : Some ( String :: from ( parameters) ) ,
150+ } ,
151+ }
152+ }
153+ }
154+
155+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
156+ pub struct FunctionDef {
157+ pub name : String ,
158+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
159+ pub description : Option < String > ,
160+ /// JSON Schema string for the function parameters
161+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
162+ pub parameters : Option < String > ,
163+ }
164+
165+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
166+ #[ serde( untagged) ]
167+ pub enum ToolChoice {
168+ Auto ,
169+ None ,
170+ Required ,
171+ Specific { function : ToolChoiceFunction } ,
172+ }
173+
174+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
175+ pub struct ToolChoiceFunction {
176+ pub name : String ,
177+ }
178+
179+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
180+ pub struct ResponseFormat {
181+ #[ serde( rename = "type" ) ]
182+ pub format_type : String ,
183+ }
184+
185+ impl ResponseFormat {
186+ pub fn json ( ) -> Self {
187+ Self {
188+ format_type : String :: from ( "json_object" ) ,
189+ }
190+ }
191+
192+ pub fn text ( ) -> Self {
193+ Self {
194+ format_type : String :: from ( "text" ) ,
195+ }
196+ }
17197}
18198
199+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
200+ pub struct ToolCall {
201+ pub id : String ,
202+ #[ serde( rename = "type" ) ]
203+ pub call_type : String ,
204+ pub function : FunctionCall ,
205+ }
206+
207+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
208+ pub struct FunctionCall {
209+ pub name : String ,
210+ pub arguments : String ,
211+ }
212+
213+ /// Response from the host LLM proxy.
19214#[ derive( Clone , Debug , Serialize , Deserialize ) ]
20215pub struct LlmResponse {
21- pub content : String ,
216+ pub content : Option < String > ,
217+ #[ serde( default ) ]
218+ pub tool_calls : Vec < ToolCall > ,
22219 pub usage : Option < LlmUsage > ,
220+ pub finish_reason : Option < String > ,
221+ }
222+
223+ impl LlmResponse {
224+ pub fn text ( & self ) -> & str {
225+ self . content . as_deref ( ) . unwrap_or ( "" )
226+ }
227+
228+ pub fn has_tool_calls ( & self ) -> bool {
229+ !self . tool_calls . is_empty ( )
230+ }
23231}
24232
25233#[ derive( Clone , Debug , Serialize , Deserialize ) ]
0 commit comments