Skip to content

Commit c850042

Browse files
committed
added state and context
1 parent 2ce377e commit c850042

File tree

5 files changed

+149
-49
lines changed

5 files changed

+149
-49
lines changed

packages/adapter-sqlite/src/index.ts

+22
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,28 @@ export class SqliteDatabaseAdapter
106106
}))
107107
.sort((a, b) => Number(a.createdAt) - Number(b.createdAt));
108108
}
109+
110+
async getFormattedConversation(conversationId: UUID): Promise<string> {
111+
const conversation = await this.getConversation(conversationId);
112+
if (!conversation) return "";
113+
114+
const messages = await this.getConversationMessages(conversationId);
115+
116+
// Format each message with timestamp
117+
const formattedMessages = messages.map(msg => {
118+
const timestamp = new Date(msg.createdAt).toLocaleString("en-US", {
119+
hour: "2-digit",
120+
minute: "2-digit",
121+
month: "short",
122+
day: "numeric"
123+
});
124+
const username = msg.content.username || msg.userId;
125+
return `@${username} (${timestamp}):\n${msg.content.text}`;
126+
});
127+
128+
return `Context: ${conversation.context}\n\n${formattedMessages.join('\n\n')}`;
129+
}
130+
109131
async setParticipantUserState(
110132
roomId: UUID,
111133
userId: UUID,

packages/client-twitter/src/utils.ts

+47-40
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { Tweet } from "agent-twitter-client";
2-
import { getEmbeddingZeroVector } from "@elizaos/core";
2+
import { getEmbeddingZeroVector, composeContext, elizaLogger } from "@elizaos/core";
33
import type { Content, Memory, UUID, IAgentRuntime } from "@elizaos/core";
44

55
import { stringToUuid } from "@elizaos/core";
66
import { ClientBase } from "./base";
7-
import { elizaLogger } from "@elizaos/core";
87
import { DEFAULT_MAX_TWEET_LENGTH } from "./environment";
98
import { Media } from "@elizaos/core";
109
import fs from "fs";
@@ -440,46 +439,50 @@ export async function analyzeConversation(
440439
runtime: IAgentRuntime
441440
): Promise<void> {
442441
const conversation = await runtime.databaseAdapter.getConversation(conversationId);
443-
console.log("analyzeConversation", conversation)
444-
// Get all messages in order
445-
const messages = await Promise.all(
446-
JSON.parse(conversation.messageIds).map(id =>
447-
runtime.messageManager.getMemoryById(id)
448-
)
449-
);
450-
451-
// Group messages by user
452-
const userMessages = new Map<string, string[]>();
453-
for (const message of messages) {
454-
if (message.userId === runtime.agentId) continue; // Skip agent's messages
455-
456-
const username = message.content.username || message.userId;
457-
if (!userMessages.has(username)) {
458-
userMessages.set(username, []);
459-
}
460-
userMessages.get(username)?.push(message.content.text);
442+
if (!conversation) {
443+
elizaLogger.error("No conversation found for analysis", conversationId);
444+
return;
461445
}
462446

463-
// Format conversation for per-user analysis
464-
const prompt = `Analyze each user's messages in this conversation and provide a sentiment score from -1.0 (very negative) to 1.0 (very positive).
465-
Consider factors like: politeness, engagement, friendliness, and cooperation.
447+
// Get all messages in order
448+
const messages = await runtime.databaseAdapter.getConversationMessages(conversationId);
449+
if (messages.length === 0) {
450+
elizaLogger.error("No messages found in conversation for analysis", conversationId);
451+
return;
452+
}
466453

467-
Context: ${conversation.context}
454+
// Get the last message to use for state building
455+
const lastMessage = messages[messages.length - 1];
468456

469-
${Array.from(userMessages.entries()).map(([username, msgs]) =>
470-
`Messages from @${username}:\n${msgs.join('\n')}`
471-
).join('\n\n')}
457+
// Build state with conversation context
458+
const state = await runtime.composeState(lastMessage, {
459+
conversationId: conversationId,
460+
twitterUserName: runtime.getSetting("TWITTER_USERNAME")
461+
});
472462

473-
Return ONLY a JSON object with usernames as keys and scores as values. Example format:
474-
{
475-
"@user1": 0.8,
476-
"@user2": -0.3
477-
}`;
463+
// Format conversation for per-user analysis
464+
const analysisTemplate = `
465+
#Recent Conversations:
466+
{{recentUserConversations}}
467+
468+
#Instructions:
469+
Evaluate the messages the other users sent to you in this conversation.
470+
Rate each users messages sent to you as a whole using these metrics: [-5] very bad, [0] neutral, [5] very good.
471+
Evaluates these messages as the character ${runtime.character.name} with the context of the whole conversation.
472+
If you aren't sure if the message was directed to you, or you're missing context to give a good answer, give the score [0] neutral.
473+
474+
Return ONLY a JSON object with usernames as keys and scores as values. Example format:
475+
{
476+
"@user1": 0.8,
477+
"@user2": -0.3
478+
}`;
479+
const context = composeContext({
480+
state,
481+
template: analysisTemplate
482+
});
478483

479484
const analysis = await runtime.generateText({
480-
prompt,
481-
temperature: 0.7,
482-
maxTokens: 500
485+
prompt: context,
483486
});
484487

485488
elizaLogger.log("User sentiment scores:", analysis);
@@ -495,13 +498,17 @@ Return ONLY a JSON object with usernames as keys and scores as values. Example f
495498

496499
// Update user rapport based on sentiment scores
497500
for (const [username, score] of Object.entries(sentimentScores)) {
498-
const userId = messages.find(m => m.content.username === username.replace('@', ''))?.userId;
501+
const userId = messages.find(m =>
502+
(m.content.username || m.userId) === username.replace('@', '')
503+
)?.userId;
504+
499505
if (userId) {
500-
await runtime.databaseAdapter.updateUserRapport({
506+
await runtime.databaseAdapter.setUserRapport(
501507
userId,
502-
agentId: runtime.agentId,
503-
sentimentScore: score as number
504-
});
508+
runtime.agentId,
509+
score as number
510+
);
511+
elizaLogger.log(`Updated rapport for user ${username}:`, score);
505512
}
506513
}
507514
} catch (error) {

packages/core/src/database.ts

+33-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
type UUID,
99
Participant,
1010
IDatabaseAdapter,
11+
Conversation,
1112
} from "./types.ts";
1213
import { CircuitBreaker } from "./database/CircuitBreaker";
1314
import { elizaLogger } from "./logger";
@@ -327,13 +328,6 @@ export abstract class DatabaseAdapter<DB = any> implements IDatabaseAdapter {
327328
*/
328329
abstract getParticipantsForAccount(userId: UUID): Promise<Participant[]>;
329330

330-
/**
331-
* Retrieves participants associated with a specific account.
332-
* @param userId The UUID of the account.
333-
* @returns A Promise that resolves to an array of Participant objects.
334-
*/
335-
abstract getParticipantsForAccount(userId: UUID): Promise<Participant[]>;
336-
337331
/**
338332
* Retrieves participants for a specific room.
339333
* @param roomId The UUID of the room for which to retrieve participants.
@@ -402,4 +396,36 @@ export abstract class DatabaseAdapter<DB = any> implements IDatabaseAdapter {
402396
throw error;
403397
}
404398
}
399+
400+
async getFormattedConversation(conversationId: UUID): Promise<string> {
401+
throw new Error("Method not implemented.");
402+
}
403+
404+
async getConversation(conversationId: UUID): Promise<Conversation | null> {
405+
throw new Error("Method not implemented.");
406+
}
407+
408+
async storeConversation(conversation: Conversation): Promise<void> {
409+
throw new Error("Method not implemented.");
410+
}
411+
412+
async updateConversation(conversation: Partial<Conversation> & { id: UUID }): Promise<void> {
413+
throw new Error("Method not implemented.");
414+
}
415+
416+
async getConversationsByStatus(status: string, limit?: number): Promise<Conversation[]> {
417+
throw new Error("Method not implemented.");
418+
}
419+
420+
async getConversationMessages(conversationId: UUID): Promise<Memory[]> {
421+
throw new Error("Method not implemented.");
422+
}
423+
424+
async setUserRapport(userId: UUID, agentId: UUID, score: number): Promise<void> {
425+
throw new Error("Method not implemented.");
426+
}
427+
428+
async getUserRapport(userId: UUID, agentId: UUID): Promise<number> {
429+
throw new Error("Method not implemented.");
430+
}
405431
}

packages/core/src/runtime.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -934,12 +934,19 @@ Text: ${attachment.text}
934934
const recentInteractionsData = existingMemories.slice(0, 20);
935935
return recentInteractionsData;
936936
};
937-
937+
938938
const recentInteractions =
939939
userId !== this.agentId
940940
? await getRecentInteractions(userId, this.agentId)
941941
: [];
942942

943+
// Get formatted conversation if conversationId is provided
944+
let recentUserConversations = "";
945+
if (additionalKeys.conversationId) {
946+
const currentConversationId = additionalKeys.conversationId as UUID;
947+
recentUserConversations = await this.databaseAdapter.getFormattedConversation(currentConversationId);
948+
}
949+
943950
const getRecentMessageInteractions = async (
944951
recentInteractionsData: Memory[]
945952
): Promise<string> => {

packages/core/src/types.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ export interface State {
328328
userRapport?: number;
329329

330330
/** Recent conversations specific to the current user */
331-
recentUserConversations?: Memory[];
331+
recentUserConversations?: string;
332332

333333
/** Additional dynamic properties */
334334
[key: string]: unknown;
@@ -946,6 +946,22 @@ export interface IDatabaseAdapter {
946946
}): Promise<Relationship | null>;
947947

948948
getRelationships(params: { userId: UUID }): Promise<Relationship[]>;
949+
950+
getFormattedConversation(conversationId: UUID): Promise<string>;
951+
952+
getConversation(conversationId: UUID): Promise<Conversation | null>;
953+
954+
storeConversation(conversation: Conversation): Promise<void>;
955+
956+
updateConversation(conversation: Partial<Conversation> & { id: UUID }): Promise<void>;
957+
958+
getConversationsByStatus(status: string, limit?: number): Promise<Conversation[]>;
959+
960+
getConversationMessages(conversationId: UUID): Promise<Memory[]>;
961+
962+
setUserRapport(userId: UUID, agentId: UUID, score: number): Promise<void>;
963+
964+
getUserRapport(userId: UUID, agentId: UUID): Promise<number>;
949965
}
950966

951967
export interface IDatabaseCacheAdapter {
@@ -1257,3 +1273,25 @@ export interface ActionResponse {
12571273
export interface ISlackService extends Service {
12581274
client: any;
12591275
}
1276+
1277+
export interface Conversation {
1278+
id: UUID;
1279+
messageIds: string; // JSON string array
1280+
participantIds: string; // JSON string array
1281+
startedAt: Date;
1282+
lastMessageAt: Date;
1283+
closedAt?: Date;
1284+
context: string;
1285+
status: 'ACTIVE' | 'CLOSED';
1286+
agentId: UUID;
1287+
rootTweetId?: string;
1288+
}
1289+
// Encode function
1290+
export function encodeString(str: string): string {
1291+
return Buffer.from(str).toString('base64');
1292+
}
1293+
1294+
// Decode function
1295+
export function decodeString(encoded: string): string {
1296+
return Buffer.from(encoded, 'base64').toString();
1297+
}

0 commit comments

Comments
 (0)