Pass up intent with completion requests (#31710)

This PR adds a new `intent` field to completion requests to assist in
categorizing them correctly.

Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
Marshall Bowers 2025-05-29 16:43:12 -04:00 committed by GitHub
parent 38e45e828b
commit a23ee61a4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 110 additions and 34 deletions

7
Cargo.lock generated
View file

@ -559,6 +559,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zed_llm_client",
]
[[package]]
@ -5047,6 +5048,7 @@ dependencies = [
"util",
"uuid",
"workspace-hack",
"zed_llm_client",
]
[[package]]
@ -6149,6 +6151,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zed_llm_client",
"zlog",
]
@ -19876,9 +19879,9 @@ dependencies = [
[[package]]
name = "zed_llm_client"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a8b9575b215536ed8ad254ba07171e4e13bd029eda3b54cca4b184d2768050"
checksum = "de7d9523255f4e00ee3d0918e5407bd252d798a4a8e71f6d37f23317a1588203"
dependencies = [
"anyhow",
"serde",

View file

@ -617,7 +617,7 @@ wasmtime = { version = "29", default-features = false, features = [
wasmtime-wasi = "29"
which = "6.0.0"
workspace-hack = "0.1.0"
zed_llm_client = "0.8.3"
zed_llm_client = "0.8.4"
zstd = "0.11"
[workspace.dependencies.async-stripe]

View file

@ -55,6 +55,7 @@ use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
use workspace::{CollaboratorId, Workspace};
use zed_actions::assistant::OpenRulesLibrary;
use zed_llm_client::CompletionIntent;
pub struct ActiveThread {
context_store: Entity<ContextStore>,
@ -1436,6 +1437,7 @@ impl ActiveThread {
let request = language_model::LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: None,
mode: None,
messages: vec![request_message],
tools: vec![],
@ -1610,7 +1612,12 @@ impl ActiveThread {
this.thread.update(cx, |thread, cx| {
thread.advance_prompt_id();
thread.send_to_model(model.model, Some(window.window_handle()), cx);
thread.send_to_model(
model.model,
CompletionIntent::UserPrompt,
Some(window.window_handle()),
cx,
);
});
this._load_edited_message_context_task = None;
cx.notify();
@ -3702,7 +3709,8 @@ mod tests {
// Stream response to user message
thread.update(cx, |thread, cx| {
let request = thread.to_completion_request(model.clone(), cx);
let request =
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx);
thread.stream_completion(request, model, cx.active_window(), cx)
});
// Follow the agent

View file

@ -52,7 +52,7 @@ use workspace::{
use zed_actions::agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding};
use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};
use zed_llm_client::UsageLimit;
use zed_llm_client::{CompletionIntent, UsageLimit};
use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
@ -1310,7 +1310,12 @@ impl AgentPanel {
active_thread.thread().update(cx, |thread, cx| {
thread.insert_invisible_continue_message(cx);
thread.advance_prompt_id();
thread.send_to_model(model, Some(window.window_handle()), cx);
thread.send_to_model(
model,
CompletionIntent::UserPrompt,
Some(window.window_handle()),
cx,
);
});
});
} else {

View file

@ -34,6 +34,7 @@ use std::{
};
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use zed_llm_client::CompletionIntent;
pub struct BufferCodegen {
alternatives: Vec<Entity<CodegenAlternative>>,
@ -464,6 +465,7 @@ impl CodegenAlternative {
LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(CompletionIntent::InlineAssist),
mode: None,
tools: Vec::new(),
tool_choice: None,

View file

@ -42,6 +42,7 @@ use theme::ThemeSettings;
use ui::{Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
use util::{ResultExt as _, maybe};
use workspace::{CollaboratorId, Workspace};
use zed_llm_client::CompletionIntent;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_store::ContextStore;
@ -375,7 +376,12 @@ impl MessageEditor {
thread
.update(cx, |thread, cx| {
thread.advance_prompt_id();
thread.send_to_model(model, Some(window_handle), cx);
thread.send_to_model(
model,
CompletionIntent::UserPrompt,
Some(window_handle),
cx,
);
})
.log_err();
})
@ -1280,6 +1286,7 @@ impl MessageEditor {
let request = language_model::LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: None,
mode: None,
messages: vec![request_message],
tools: vec![],

View file

@ -25,6 +25,7 @@ use terminal_view::TerminalView;
use ui::prelude::*;
use util::ResultExt;
use workspace::{Toast, Workspace, notifications::NotificationId};
use zed_llm_client::CompletionIntent;
pub fn init(
fs: Arc<dyn Fs>,
@ -291,6 +292,7 @@ impl TerminalInlineAssistant {
thread_id: None,
prompt_id: None,
mode: None,
intent: Some(CompletionIntent::TerminalInlineAssist),
messages: vec![request_message],
tools: Vec::new(),
tool_choice: None,

View file

@ -38,7 +38,7 @@ use thiserror::Error;
use ui::Window;
use util::{ResultExt as _, post_inc};
use uuid::Uuid;
use zed_llm_client::CompletionRequestStatus;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus};
use crate::ThreadStore;
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
@ -1184,6 +1184,7 @@ impl Thread {
pub fn send_to_model(
&mut self,
model: Arc<dyn LanguageModel>,
intent: CompletionIntent,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
@ -1193,7 +1194,7 @@ impl Thread {
self.remaining_turns -= 1;
let request = self.to_completion_request(model.clone(), cx);
let request = self.to_completion_request(model.clone(), intent, cx);
self.stream_completion(request, model, window, cx);
}
@ -1213,11 +1214,13 @@ impl Thread {
pub fn to_completion_request(
&self,
model: Arc<dyn LanguageModel>,
intent: CompletionIntent,
cx: &mut Context<Self>,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
thread_id: Some(self.id.to_string()),
prompt_id: Some(self.last_prompt_id.to_string()),
intent: Some(intent),
mode: None,
messages: vec![],
tools: Vec::new(),
@ -1371,12 +1374,14 @@ impl Thread {
fn to_summarize_request(
&self,
model: &Arc<dyn LanguageModel>,
intent: CompletionIntent,
added_user_message: String,
cx: &App,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(intent),
mode: None,
messages: vec![],
tools: Vec::new(),
@ -1854,7 +1859,12 @@ impl Thread {
If the conversation is about a specific subject, include it in the title. \
Be descriptive. DO NOT speak in the first person.";
let request = self.to_summarize_request(&model.model, added_user_message.into(), cx);
let request = self.to_summarize_request(
&model.model,
CompletionIntent::ThreadSummarization,
added_user_message.into(),
cx,
);
self.summary = ThreadSummary::Generating;
@ -1955,7 +1965,12 @@ impl Thread {
4. Any action items or next steps if any\n\
Format it in Markdown with headings and bullet points.";
let request = self.to_summarize_request(&model, added_user_message.into(), cx);
let request = self.to_summarize_request(
&model,
CompletionIntent::ThreadContextSummarization,
added_user_message.into(),
cx,
);
*self.detailed_summary_tx.borrow_mut() = DetailedSummaryState::Generating {
message_id: last_message_id,
@ -2047,7 +2062,8 @@ impl Thread {
model: Arc<dyn LanguageModel>,
) -> Vec<PendingToolUse> {
self.auto_capture_telemetry(cx);
let request = Arc::new(self.to_completion_request(model.clone(), cx));
let request =
Arc::new(self.to_completion_request(model.clone(), CompletionIntent::ToolResults, cx));
let pending_tool_uses = self
.tool_use
.pending_tool_uses()
@ -2243,7 +2259,7 @@ impl Thread {
if self.all_tools_finished() {
if let Some(ConfiguredModel { model, .. }) = self.configured_model.as_ref() {
if !canceled {
self.send_to_model(model.clone(), window, cx);
self.send_to_model(model.clone(), CompletionIntent::ToolResults, window, cx);
}
self.auto_capture_telemetry(cx);
}
@ -2934,7 +2950,7 @@ fn main() {{
// Check message in request
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.messages.len(), 2);
@ -3029,7 +3045,7 @@ fn main() {{
// Check entire request to make sure all contexts are properly included
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// The request should contain all 3 messages
@ -3136,7 +3152,7 @@ fn main() {{
// Check message in request
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.messages.len(), 2);
@ -3162,7 +3178,7 @@ fn main() {{
// Check that both messages appear in the request
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.messages.len(), 3);
@ -3207,7 +3223,7 @@ fn main() {{
// Create a request and check that it doesn't have a stale buffer warning yet
let initial_request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// Make sure we don't have a stale file warning yet
@ -3243,7 +3259,7 @@ fn main() {{
// Create a new request and check for the stale buffer warning
let new_request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// We should have a stale file warning as the last message
@ -3293,7 +3309,7 @@ fn main() {{
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, Some(0.66));
@ -3313,7 +3329,7 @@ fn main() {{
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, Some(0.66));
@ -3333,7 +3349,7 @@ fn main() {{
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, Some(0.66));
@ -3353,7 +3369,7 @@ fn main() {{
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, None);
}
@ -3385,7 +3401,12 @@ fn main() {{
// Send a message
thread.update(cx, |thread, cx| {
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
thread.send_to_model(model.clone(), None, cx);
thread.send_to_model(
model.clone(),
CompletionIntent::ThreadSummarization,
None,
cx,
);
});
let fake_model = model.as_fake();
@ -3480,7 +3501,7 @@ fn main() {{
vec![],
cx,
);
thread.send_to_model(model.clone(), None, cx);
thread.send_to_model(model.clone(), CompletionIntent::UserPrompt, None, cx);
});
let fake_model = model.as_fake();
@ -3518,7 +3539,12 @@ fn main() {{
) {
thread.update(cx, |thread, cx| {
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
thread.send_to_model(model.clone(), None, cx);
thread.send_to_model(
model.clone(),
CompletionIntent::ThreadSummarization,
None,
cx,
);
});
let fake_model = model.as_fake();

View file

@ -57,6 +57,7 @@ uuid.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
language_model = { workspace = true, features = ["test-support"] }

View file

@ -45,6 +45,7 @@ use text::{BufferSnapshot, ToPoint};
use ui::IconName;
use util::{ResultExt, TryFutureExt, post_inc};
use uuid::Uuid;
use zed_llm_client::CompletionIntent;
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String);
@ -2272,6 +2273,7 @@ impl AssistantContext {
let mut completion_request = LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(CompletionIntent::UserPrompt),
mode: None,
messages: Vec::new(),
tools: Vec::new(),

View file

@ -28,6 +28,7 @@ use std::{cmp, iter, mem, ops::Range, path::PathBuf, pin::Pin, sync::Arc, task::
use streaming_diff::{CharOperation, StreamingDiff};
use streaming_fuzzy_matcher::StreamingFuzzyMatcher;
use util::debug_panic;
use zed_llm_client::CompletionIntent;
#[derive(Serialize)]
struct CreateFilePromptTemplate {
@ -106,7 +107,9 @@ impl EditAgent {
edit_description,
}
.render(&this.templates)?;
let new_chunks = this.request(conversation, prompt, cx).await?;
let new_chunks = this
.request(conversation, CompletionIntent::CreateFile, prompt, cx)
.await?;
let (output, mut inner_events) = this.overwrite_with_chunks(buffer, new_chunks, cx);
while let Some(event) = inner_events.next().await {
@ -213,7 +216,9 @@ impl EditAgent {
edit_description,
}
.render(&this.templates)?;
let edit_chunks = this.request(conversation, prompt, cx).await?;
let edit_chunks = this
.request(conversation, CompletionIntent::EditFile, prompt, cx)
.await?;
this.apply_edit_chunks(buffer, edit_chunks, events_tx, cx)
.await
});
@ -589,6 +594,7 @@ impl EditAgent {
async fn request(
&self,
mut conversation: LanguageModelRequest,
intent: CompletionIntent,
prompt: String,
cx: &mut AsyncApp,
) -> Result<BoxStream<'static, Result<String, LanguageModelCompletionError>>> {
@ -646,6 +652,7 @@ impl EditAgent {
let request = LanguageModelRequest {
thread_id: conversation.thread_id,
prompt_id: conversation.prompt_id,
intent: Some(intent),
mode: conversation.mode,
messages: conversation.messages,
tool_choice,

View file

@ -67,3 +67,4 @@ unindent.workspace = true
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true

View file

@ -19,6 +19,7 @@ use collections::HashMap;
use futures::{FutureExt as _, StreamExt, channel::mpsc, select_biased};
use gpui::{App, AppContext, AsyncApp, Entity};
use language_model::{LanguageModel, Role, StopReason};
use zed_llm_client::CompletionIntent;
pub const THREAD_EVENT_TIMEOUT: Duration = Duration::from_secs(60 * 2);
@ -308,7 +309,7 @@ impl ExampleContext {
let message_count_before = self.app.update_entity(&self.agent_thread, |thread, cx| {
thread.set_remaining_turns(iterations);
thread.send_to_model(model, None, cx);
thread.send_to_model(model, CompletionIntent::UserPrompt, None, cx);
thread.messages().len()
})?;

View file

@ -576,6 +576,7 @@ impl ExampleInstance {
thread_id: None,
prompt_id: None,
mode: None,
intent: None,
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::Text(to_prompt(assertion.description))],

View file

@ -59,6 +59,7 @@ util.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View file

@ -13,7 +13,6 @@ use agent_settings::AgentSettings;
use anyhow::Context as _;
use askpass::AskPassDelegate;
use db::kvp::KEY_VALUE_STORE;
use editor::{
Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar,
scroll::ScrollbarAutoHide,
@ -42,6 +41,7 @@ use language_model::{
};
use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
use multi_buffer::ExcerptInfo;
use notifications::status_toast::{StatusToast, ToastIcon};
use panel::{
PanelHeader, panel_button, panel_editor_container, panel_editor_style, panel_filled_button,
panel_icon_button,
@ -64,13 +64,12 @@ use ui::{
};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::AppState;
use notifications::status_toast::{StatusToast, ToastIcon};
use workspace::{
Workspace,
dock::{DockPosition, Panel, PanelEvent},
notifications::DetachAndPromptErr,
};
use zed_llm_client::CompletionIntent;
actions!(
git_panel,
@ -1767,6 +1766,7 @@ impl GitPanel {
let request = LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(CompletionIntent::GenerateGitCommitMessage),
mode: None,
messages: vec![LanguageModelRequestMessage {
role: Role::User,

View file

@ -12,7 +12,7 @@ use gpui::{
use image::codecs::png::PngEncoder;
use serde::{Deserialize, Serialize};
use util::ResultExt;
use zed_llm_client::CompletionMode;
use zed_llm_client::{CompletionIntent, CompletionMode};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct LanguageModelImage {
@ -384,6 +384,7 @@ pub enum LanguageModelToolChoice {
pub struct LanguageModelRequest {
pub thread_id: Option<String>,
pub prompt_id: Option<String>,
pub intent: Option<CompletionIntent>,
pub mode: Option<CompletionMode>,
pub messages: Vec<LanguageModelRequestMessage>,
pub tools: Vec<LanguageModelRequestTool>,

View file

@ -809,6 +809,7 @@ impl LanguageModel for CloudLanguageModel {
> {
let thread_id = request.thread_id.clone();
let prompt_id = request.prompt_id.clone();
let intent = request.intent;
let mode = request.mode;
let app_version = cx.update(|cx| AppVersion::global(cx)).ok();
match self.model.provider {
@ -841,6 +842,7 @@ impl LanguageModel for CloudLanguageModel {
CompletionBody {
thread_id,
prompt_id,
intent,
mode,
provider: zed_llm_client::LanguageModelProvider::Anthropic,
model: request.model.clone(),
@ -897,6 +899,7 @@ impl LanguageModel for CloudLanguageModel {
CompletionBody {
thread_id,
prompt_id,
intent,
mode,
provider: zed_llm_client::LanguageModelProvider::OpenAi,
model: request.model.clone(),
@ -934,6 +937,7 @@ impl LanguageModel for CloudLanguageModel {
CompletionBody {
thread_id,
prompt_id,
intent,
mode,
provider: zed_llm_client::LanguageModelProvider::Google,
model: request.model.model_id.clone(),

View file

@ -816,6 +816,7 @@ mod tests {
tool_choice: None,
thread_id: None,
prompt_id: None,
intent: None,
mode: None,
stop: Vec::new(),
};

View file

@ -860,6 +860,7 @@ mod tests {
let request = LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: None,
mode: None,
messages: vec![LanguageModelRequestMessage {
role: Role::User,

View file

@ -923,6 +923,7 @@ impl RulesLibrary {
LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: None,
mode: None,
messages: vec![LanguageModelRequestMessage {
role: Role::System,

View file

@ -560,6 +560,7 @@ impl SummaryIndex {
thread_id: None,
prompt_id: None,
mode: None,
intent: None,
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: vec![prompt.into()],