1use std::borrow::Borrow;
14use std::collections::HashSet;
15use std::sync::Arc;
16
17use super::{Author, Entry, MessageId};
18use crate::message::{
19 AssistantContent, AssistantMessage, CallId, Message, ToolCall, ToolName, UserContent,
20};
21use crate::tool::feedback::tool_result_message;
22
23#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
26pub enum PairingError {
27 #[error("consecutive assistant messages at index {index}")]
29 ConsecutiveAssistant {
30 index: usize,
32 },
33 #[error("tool call `{call_id}` at index {index} has no result in the following message")]
36 UnansweredToolCall {
37 index: usize,
39 call_id: CallId,
41 },
42 #[error(
44 "tool result `{call_id}` at index {index} answers no call from the preceding assistant message"
45 )]
46 OrphanToolResult {
47 index: usize,
49 call_id: CallId,
51 },
52}
53
54pub fn validate(messages: &[impl Borrow<Message>]) -> Result<(), PairingError> {
63 repair(
64 messages
65 .iter()
66 .map(|message| message.borrow().clone())
67 .collect(),
68 )
69 .repairs
70 .into_iter()
71 .next()
72 .map_or(Ok(()), Err)
73}
74
75pub const NO_RESULT_PROVIDED: &str = "No result provided";
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct Pairing {
81 pub stored: bool,
84 pub answers: bool,
87}
88
89impl Pairing {
90 pub const fn canonical() -> Self {
92 Self {
93 stored: false,
94 answers: true,
95 }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq)]
101pub struct Paired {
102 pub messages: Vec<Message>,
104 pub repairs: Vec<PairingError>,
106}
107
108pub fn repair(messages: Vec<Message>) -> Paired {
115 let shaped = shape(messages, true);
116 Paired {
117 messages: shaped.messages,
118 repairs: shaped.repairs,
119 }
120}
121
122#[derive(Debug, Clone, PartialEq)]
127pub struct Normalized {
128 pub entries: Vec<Entry>,
132 pub repairs: Vec<PairingError>,
134 pub merged: Vec<(MessageId, MessageId)>,
137 pub dropped: Vec<MessageId>,
140}
141
142pub fn normalize(entries: Vec<Entry>) -> Normalized {
152 let (messages, metas): (Vec<Message>, Vec<Meta>) = entries
153 .into_iter()
154 .map(|entry| {
155 let message = Arc::unwrap_or_clone(entry.message);
156 (message, (entry.author, entry.id, entry.made))
157 })
158 .unzip();
159 let shaped = shape(messages, false);
160 let mut used = HashSet::new();
161 let mut merged = Vec::new();
162 let mut entries = Vec::with_capacity(shaped.messages.len());
163 for (message, sources) in shaped.messages.into_iter().zip(shaped.sources) {
164 let mut free = sources.iter().filter(|source| used.insert(**source));
165 let kept = free.next().and_then(|source| metas.get(*source));
166 let into = kept.and_then(|(_, id, _)| *id);
167 for gone in free.filter_map(|source| metas.get(*source)?.1) {
168 if let Some(into) = into {
169 merged.push((into, gone));
170 }
171 }
172 let entry = match kept {
173 Some((author, id, made)) => Entry {
174 message: Arc::new(message),
175 author: author.clone(),
176 id: *id,
177 made: *made,
178 },
179 None => {
182 let first = sources.first().and_then(|source| metas.get(*source));
183 Entry {
184 message: Arc::new(message),
185 author: first
186 .map_or_else(|| Some(Author::rig()), |(author, ..)| author.clone()),
187 id: None,
188 made: None,
189 }
190 }
191 };
192 entries.push(entry);
193 }
194 let dropped = metas.iter().enumerate();
195 let dropped = dropped.filter(|(source, _)| !used.contains(source));
196 Normalized {
197 entries,
198 repairs: shaped.repairs,
199 merged,
200 dropped: dropped.filter_map(|(_, (_, id, _))| *id).collect(),
201 }
202}
203
204type Meta = (Option<Author>, Option<MessageId>, Option<u64>);
206
207struct Shaped {
211 messages: Vec<Message>,
212 sources: Vec<Vec<usize>>,
213 repairs: Vec<PairingError>,
214}
215
216fn shape(messages: Vec<Message>, merge_users: bool) -> Shaped {
220 let canonical = |messages: Vec<Message>| {
221 let history = messages.into_iter().map(Some).collect();
222 walk(history, Pairing::canonical(), merge_users, true)
223 };
224 let mut shaped = canonical(messages);
225 while merge_turns(&mut shaped) {
227 let again = canonical(std::mem::take(&mut shaped.messages));
228 let sources = again.sources.iter().map(|sources| {
229 let earlier = sources.iter().filter_map(|at| shaped.sources.get(*at));
230 earlier.flatten().copied().collect()
231 });
232 shaped.sources = sources.collect();
233 shaped.messages = again.messages;
234 }
235 shaped
236}
237
238fn merge_turns(shaped: &mut Shaped) -> bool {
241 let before = shaped.messages.len();
242 let mut messages: Vec<Message> = Vec::with_capacity(before);
243 let mut sources: Vec<Vec<usize>> = Vec::with_capacity(before);
244 let all = shaped.messages.drain(..).zip(shaped.sources.drain(..));
245 for (mut message, mut from) in all {
246 if let Message::Assistant(turn) = &mut message
247 && let Some(Message::Assistant(previous)) = messages.last_mut()
248 {
249 previous.content.append(&mut turn.content);
250 previous.stop = turn.stop.take();
251 if let Some(into) = sources.last_mut() {
252 into.append(&mut from);
253 }
254 continue;
255 }
256 messages.push(message);
257 sources.push(from);
258 }
259 shaped.messages = messages;
260 shaped.sources = sources;
261 shaped.messages.len() < before
262}
263
264pub fn pair(history: Vec<Option<Message>>, pairing: Pairing) -> Paired {
277 let shaped = walk(history, pairing, true, false);
278 Paired {
279 messages: shaped.messages,
280 repairs: shaped.repairs,
281 }
282}
283
284fn walk(
289 history: Vec<Option<Message>>,
290 pairing: Pairing,
291 merge_users: bool,
292 sourced: bool,
293) -> Shaped {
294 let mut stored = pairing.stored.then(Vec::new);
296 let mut walk = Walk {
297 answers: pairing.answers,
298 merge_users,
299 sourced,
300 ..Walk::default()
301 };
302 for step in steps(history) {
303 match step {
304 Step::Turn(index, turn) => {
305 if walk.after_assistant && turn.is_some() {
306 walk.repairs
307 .push(PairingError::ConsecutiveAssistant { index });
308 }
309 walk.close();
310 stored = None;
311 walk.gap = turn.is_none();
312 walk.after_assistant = turn.is_some();
313 let Some(turn) = turn else { continue };
314 if !turn.stop.as_ref().is_some_and(|stop| stop.is_failure()) {
315 let calls = turn.tool_calls();
316 let calls = calls.map(|call| (call.id.clone(), call.function.name.clone()));
317 walk.waiting = calls.collect();
318 walk.waiting_at = index;
319 }
320 walk.put(Message::Assistant(turn), [index]);
321 }
322 Step::System(index, system) => {
323 walk.after_assistant = false;
324 if walk.waiting.is_empty() {
325 walk.gap = false;
326 walk.put(system, [index]);
327 } else {
328 walk.held.push((index, system));
329 }
330 }
331 Step::User(first, parts) => {
332 walk.after_assistant = false;
333 if parts.is_empty() {
334 walk.close();
335 let empty = Message::User {
336 content: Vec::new(),
337 };
338 walk.put(empty, [first]);
339 walk.gap = false;
340 continue;
341 }
342 let mut content = Vec::with_capacity(parts.len());
343 for (index, part) in parts {
344 let UserContent::ToolResult(result) = part else {
345 content.push((Some(index), part));
346 continue;
347 };
348 let answers = match &mut stored {
349 Some(taken) if !taken.contains(&result.call) => {
350 taken.push(result.call.clone());
351 true
352 }
353 Some(_) => false,
354 None => answer(&mut walk.waiting, &result.call, |(id, _)| id),
355 };
356 if answers {
357 content.push((Some(index), UserContent::ToolResult(result)));
358 } else {
359 walk.repairs.push(PairingError::OrphanToolResult {
360 index,
361 call_id: result.call,
362 });
363 }
364 }
365 if content.is_empty() && walk.waiting.is_empty() {
366 continue;
368 }
369 let only_results =
370 !content.is_empty() && content.iter().all(|(_, part)| is_result(part));
371 if walk.pending.is_empty() {
372 walk.pending_gap = walk.gap;
373 }
374 walk.pending.extend(content);
375 walk.gap = false;
376 if only_results && !walk.waiting.is_empty() {
379 continue;
380 }
381 walk.close();
382 }
383 }
384 }
385 walk.close();
386 Shaped {
387 messages: walk.shaped,
388 sources: walk.sources,
389 repairs: walk.repairs,
390 }
391}
392
393pub(crate) fn is_result(part: &UserContent) -> bool {
394 matches!(part, UserContent::ToolResult(_))
395}
396
397#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
399pub enum AnswerError {
400 #[error("received content that is not a tool result")]
402 NotAResult,
403 #[error("received a result for unknown or already-answered tool call id `{0}`")]
405 Unknown(CallId),
406 #[error("left pending tool call id(s) unanswered: {0:?}")]
408 Unanswered(Vec<CallId>),
409}
410
411pub fn answers(pending: &[CallId], results: &[UserContent]) -> Result<(), AnswerError> {
414 let mut waiting = pending.to_vec();
415 for result in results {
416 let UserContent::ToolResult(result) = result else {
417 return Err(AnswerError::NotAResult);
418 };
419 if !answer(&mut waiting, &result.call, |id| id) {
420 return Err(AnswerError::Unknown(result.call.clone()));
421 }
422 }
423 if waiting.is_empty() {
424 Ok(())
425 } else {
426 Err(AnswerError::Unanswered(waiting))
427 }
428}
429
430pub fn close_pending<'a>(calls: impl IntoIterator<Item = &'a ToolCall>) -> Message {
433 close_pending_with(calls, NO_RESULT_PROVIDED)
434}
435
436pub fn close_pending_with<'a>(calls: impl IntoIterator<Item = &'a ToolCall>, why: &str) -> Message {
439 let calls = calls.into_iter();
440 Message::User {
441 content: unanswered(calls.map(|call| (&call.id, &call.function.name)), why),
442 }
443}
444
445fn unanswered<'a>(
446 calls: impl IntoIterator<Item = (&'a CallId, &'a ToolName)>,
447 why: &str,
448) -> Vec<UserContent> {
449 calls
450 .into_iter()
451 .map(|(id, name)| tool_result_message(id.clone(), name.clone(), why.to_owned()))
452 .collect()
453}
454
455fn answer<T>(waiting: &mut Vec<T>, id: &CallId, id_of: impl Fn(&T) -> &CallId) -> bool {
457 match waiting.iter().position(|call| id_of(call) == id) {
458 Some(at) => {
459 waiting.remove(at);
460 true
461 }
462 None => false,
463 }
464}
465
466enum Step {
468 Turn(usize, Option<AssistantMessage>),
470 System(usize, Message),
471 User(usize, Vec<(usize, UserContent)>),
474}
475
476fn steps(history: Vec<Option<Message>>) -> Vec<Step> {
479 let mut steps: Vec<Step> = Vec::with_capacity(history.len());
480 for (index, message) in history.into_iter().enumerate() {
481 match message {
482 None => steps.push(Step::Turn(index, None)),
483 Some(Message::Assistant(turn)) => steps.push(Step::Turn(index, Some(turn))),
484 Some(system @ Message::System { .. }) => steps.push(Step::System(index, system)),
485 Some(Message::User { content }) => {
486 let joins = !content.is_empty();
487 let parts = content.into_iter().map(|part| (index, part));
488 match steps.last_mut() {
489 Some(Step::User(_, previous)) if joins && !previous.is_empty() => {
490 previous.extend(parts)
491 }
492 _ => steps.push(Step::User(index, parts.collect())),
493 }
494 }
495 }
496 }
497 steps
498}
499
500#[derive(Default)]
502struct Walk {
503 answers: bool,
504 merge_users: bool,
506 sourced: bool,
508 shaped: Vec<Message>,
509 sources: Vec<Vec<usize>>,
512 repairs: Vec<PairingError>,
513 waiting: Vec<(CallId, ToolName)>,
516 waiting_at: usize,
517 held: Vec<(usize, Message)>,
519 gap: bool,
521 pending: Vec<(Option<usize>, UserContent)>,
524 pending_gap: bool,
525 after_assistant: bool,
526}
527
528impl Walk {
529 fn put(&mut self, message: Message, sources: impl IntoIterator<Item = usize>) {
530 self.shaped.push(message);
531 if self.sourced {
532 self.sources.push(sources.into_iter().collect());
533 }
534 }
535
536 fn close(&mut self) {
544 let mut content = std::mem::take(&mut self.pending);
545 let merge = std::mem::take(&mut self.pending_gap);
546 let waiting = std::mem::take(&mut self.waiting);
547 let missing =
548 if self.answers {
549 let index = self.waiting_at;
550 self.repairs.extend(waiting.iter().map(|(id, _)| {
551 PairingError::UnansweredToolCall {
552 index,
553 call_id: id.clone(),
554 }
555 }));
556 unanswered(
557 waiting.iter().map(|(id, name)| (id, name)),
558 NO_RESULT_PROVIDED,
559 )
560 } else {
561 Vec::new()
562 };
563 if !self.merge_users {
564 self.close_apart(content, missing);
565 return;
566 }
567 let at = content
568 .iter()
569 .position(|(_, part)| !is_result(part))
570 .unwrap_or(content.len());
571 content.splice(at..at, missing.into_iter().map(|part| (None, part)));
572 content.sort_by_key(|(_, part)| !is_result(part));
575 let at = content
578 .iter()
579 .position(|(_, part)| !is_result(part))
580 .unwrap_or(content.len());
581 let text = if self.held.is_empty() {
582 Vec::new()
583 } else {
584 content.split_off(at)
585 };
586 if !content.is_empty() {
587 let (sources, content) = sourced(content);
588 match self.shaped.last_mut() {
589 Some(Message::User { content: previous }) if merge => previous.extend(content),
592 _ => self.put(Message::User { content }, sources),
593 }
594 }
595 for (index, system) in std::mem::take(&mut self.held) {
596 self.put(system, [index]);
597 }
598 if !text.is_empty() {
599 let (sources, content) = sourced(text);
600 self.put(Message::User { content }, sources);
601 }
602 }
603
604 fn close_apart(
609 &mut self,
610 content: Vec<(Option<usize>, UserContent)>,
611 missing: Vec<UserContent>,
612 ) {
613 let mut messages: Vec<(Option<usize>, Vec<UserContent>)> = Vec::new();
614 for (source, part) in content {
615 match messages.last_mut() {
616 Some((last, parts)) if *last == source => parts.push(part),
617 _ => messages.push((source, vec![part])),
618 }
619 }
620 match messages.first_mut() {
621 Some((_, parts)) => {
622 let at = parts.iter().position(|part| !is_result(part));
623 let at = at.unwrap_or(parts.len());
624 parts.splice(at..at, missing);
625 }
626 None if !missing.is_empty() => messages.push((None, missing)),
627 None => {}
628 }
629 for (source, mut content) in messages {
630 content.sort_by_key(|part| !is_result(part));
631 self.put(Message::User { content }, source);
632 }
633 for (index, system) in std::mem::take(&mut self.held) {
634 self.put(system, [index]);
635 }
636 }
637}
638
639fn sourced(content: Vec<(Option<usize>, UserContent)>) -> (Vec<usize>, Vec<UserContent>) {
642 let mut sources: Vec<usize> = Vec::new();
643 let parts = content.into_iter().map(|(source, part)| {
644 if let Some(source) = source
645 && !sources.contains(&source)
646 {
647 sources.push(source);
648 }
649 part
650 });
651 let parts = parts.collect();
652 (sources, parts)
653}
654
655pub fn pending_calls(messages: &[impl Borrow<Message>]) -> Vec<ToolCall> {
659 let Some((at, Message::Assistant(reply))) = messages
660 .iter()
661 .map(Borrow::borrow)
662 .enumerate()
663 .rfind(|(_, message)| matches!(message, Message::Assistant(_)))
664 else {
665 return Vec::new();
666 };
667 let answered: Vec<&CallId> = messages
668 .iter()
669 .skip(at + 1)
670 .flat_map(|message| match message.borrow() {
671 Message::User { content } => content.as_slice(),
672 _ => &[],
673 })
674 .filter_map(|item| match item {
675 UserContent::ToolResult(result) => Some(&result.call),
676 _ => None,
677 })
678 .collect();
679 reply
680 .content
681 .iter()
682 .filter_map(|item| match item {
683 AssistantContent::ToolCall(call) if !answered.contains(&&call.id) => Some(call.clone()),
684 _ => None,
685 })
686 .collect()
687}
688
689pub fn safe_start(messages: &[impl Borrow<Message>], at: usize) -> usize {
694 let mut start = at.min(messages.len());
695 for (index, message) in messages.iter().enumerate().skip(at) {
696 match message.borrow() {
697 Message::Assistant(_) => break,
698 Message::User { content }
699 if content
700 .iter()
701 .any(|item| matches!(item, UserContent::ToolResult(_))) =>
702 {
703 start = index + 1;
704 }
705 _ => {}
706 }
707 }
708 start
709}
710
711#[cfg(test)]
712mod tests;