dave: include anonymous user identifier in api call

- don't include users pubkey

This could be used to associate requests with real users,
rendering the anonymized user_id pointless

TODO: Implement a new tool call that lets dave ask for your pubkey

Fixes: #834
Fixes: #836
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2025-05-01 18:42:45 -07:00
parent 093189b019
commit 6bbc20471a
4 changed files with 40 additions and 11 deletions

1
Cargo.lock generated
View File

@@ -3281,6 +3281,7 @@ dependencies = [
"rand 0.9.0",
"serde",
"serde_json",
"sha2",
"tokio",
"tracing",
]

View File

@@ -1,4 +1,4 @@
use enostr::Keypair;
use enostr::{Keypair, KeypairUnowned};
use tokenator::{ParseError, TokenParser, TokenSerializable};
use crate::wallet::ZapWallet;
@@ -13,6 +13,13 @@ impl UserAccount {
Self { key, wallet: None }
}
pub fn keypair(&self) -> KeypairUnowned {
KeypairUnowned {
pubkey: &self.key.pubkey,
secret_key: self.key.secret_key.as_ref(),
}
}
pub fn with_wallet(mut self, wallet: ZapWallet) -> Self {
self.wallet = Some(wallet);
self

View File

@@ -6,6 +6,7 @@ version.workspace = true
[dependencies]
async-openai = "0.28.0"
egui = { workspace = true }
sha2 = { workspace = true }
notedeck = { workspace = true }
notedeck_ui = { workspace = true }
eframe = { workspace = true }

View File

@@ -5,6 +5,7 @@ use async_openai::{
};
use chrono::{Duration, Local};
use egui_wgpu::RenderState;
use enostr::KeypairUnowned;
use futures::StreamExt;
use nostrdb::Transaction;
use notedeck::{AppAction, AppContext};
@@ -37,20 +38,31 @@ pub struct Dave {
/// A 3d representation of dave.
avatar: Option<DaveAvatar>,
input: String,
pubkey: String,
tools: Arc<HashMap<String, Tool>>,
client: async_openai::Client<OpenAIConfig>,
incoming_tokens: Option<Receiver<DaveApiResponse>>,
model_config: ModelConfig,
}
/// Calculate an anonymous user_id from a keypair
fn calculate_user_id(keypair: KeypairUnowned) -> String {
use sha2::{Digest, Sha256};
// pubkeys have degraded privacy, don't do that
let key_input = keypair
.secret_key
.map(|sk| sk.as_secret_bytes())
.unwrap_or(keypair.pubkey.bytes());
let hex_key = hex::encode(key_input);
let input = format!("{hex_key}notedeck_dave_user_id");
hex::encode(Sha256::digest(input))
}
impl Dave {
pub fn avatar_mut(&mut self) -> Option<&mut DaveAvatar> {
self.avatar.as_mut()
}
fn system_prompt() -> Message {
let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_string();
let now = Local::now();
let yesterday = now - Duration::hours(24);
let date = now.format("%Y-%m-%d %H:%M:%S");
@@ -65,8 +77,6 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
- Yesterday (-24hrs) was {yesterday_timestamp}. You can use this in combination with `since` queries for pulling notes for summarizing notes the user might have missed while they were away.
- The current users pubkey is {pubkey}
# Response Guidelines
- You *MUST* call the present_notes tool with a list of comma-separated note id references when referring to notes so that the UI can display them. Do *NOT* include note id references in the text response, but you *SHOULD* use ^1, ^2, etc to reference note indices passed to present_notes.
@@ -84,19 +94,18 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
let input = "".to_string();
let avatar = render_state.map(DaveAvatar::new);
let mut tools: HashMap<String, Tool> = HashMap::new();
let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_string();
for tool in tools::dave_tools() {
tools.insert(tool.name().to_string(), tool);
}
Dave {
client,
pubkey: pubkey.clone(),
avatar,
incoming_tokens: None,
tools: Arc::new(tools),
input,
model_config,
chat: vec![Self::system_prompt()],
chat: vec![],
}
}
@@ -170,7 +179,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
}
fn handle_new_chat(&mut self) {
self.chat = vec![Self::system_prompt()];
self.chat = vec![];
self.input.clear();
}
@@ -190,7 +199,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
.collect()
};
tracing::debug!("sending messages, latest: {:?}", messages.last().unwrap());
let pubkey = self.pubkey.clone();
let user_id = app_ctx
.accounts
.get_selected_account()
.map(|sa| calculate_user_id(sa.keypair()))
.unwrap_or_else(|| "unknown_user".to_string());
let ctx = ctx.clone();
let client = self.client.clone();
let tools = self.tools.clone();
@@ -207,7 +222,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
stream: Some(true),
messages,
tools: Some(tools::dave_tools().iter().map(|t| t.to_api()).collect()),
user: Some(pubkey),
user: Some(user_id),
..Default::default()
})
.await
@@ -321,6 +336,11 @@ impl notedeck::App for Dave {
*/
let mut app_action: Option<AppAction> = None;
// always insert system prompt if we have no context
if self.chat.is_empty() {
self.chat.push(Dave::system_prompt());
}
//update_dave(self, ctx, ui.ctx());
let should_send = self.process_events(ctx);
if let Some(action) = self.ui(ctx, ui).action {