diff --git a/packages/client/src/components/agent-action-viewer.tsx b/packages/client/src/components/agent-action-viewer.tsx
index 8df5ba29d29e..e12642e4c750 100644
--- a/packages/client/src/components/agent-action-viewer.tsx
+++ b/packages/client/src/components/agent-action-viewer.tsx
@@ -40,6 +40,7 @@ const ITEMS_PER_PAGE = 15;
enum ActionType {
all = 'all',
llm = 'llm',
+ embedding = 'embedding',
transcription = 'transcription',
image = 'image',
other = 'other',
@@ -59,7 +60,9 @@ type AgentActionViewerProps = {
// Helper functions
function getModelUsageType(modelType: string): string {
if (
- (modelType.includes('TEXT') || modelType.includes('OBJECT')) &&
+ (modelType.includes('TEXT') ||
+ modelType.includes('OBJECT') ||
+ modelType.includes('REASONING')) &&
!modelType.includes('EMBEDDING') &&
!modelType.includes('TRANSCRIPTION')
) {
@@ -74,15 +77,7 @@ function getModelUsageType(modelType: string): string {
if (modelType.includes('IMAGE')) {
return 'Image';
}
- if (
- !modelType.includes('TEXT') &&
- !modelType.includes('IMAGE') &&
- !modelType.includes('EMBEDDING') &&
- !modelType.includes('TRANSCRIPTION')
- ) {
- return 'Other';
- }
- return 'Unknown';
+ return 'Other';
}
function formatDate(timestamp: number | undefined) {
@@ -164,9 +159,10 @@ function ActionCard({ action, onDelete }: ActionCardProps) {
const modelType = action.body?.modelType || '';
const modelKey = action.body?.modelKey || '';
- const isActionLog = action.type === 'action';
+ const logType = action.type || '';
+ const isActionLog = logType === 'action';
const actionName = action.body?.action || '';
- const IconComponent = getModelIcon(isActionLog ? 'ACTION' : modelType);
+ const IconComponent = getModelIcon(isActionLog ? 'ACTION' : modelType || logType);
const usageType = isActionLog ? 'Action' : getModelUsageType(modelType);
const responseObj = typeof action.body?.response === 'object' ? action.body.response : undefined;
const tokenUsage = formatTokenUsage(responseObj?.usage || action.body?.usage);
@@ -361,7 +357,7 @@ function ActionCard({ action, onDelete }: ActionCardProps) {
{isActionLog ? actionName : usageType}
- {isActionLog ? 'Action' : modelType}
+ {isActionLog ? 'Action' : modelType || logType}
{action.body?.promptCount && action.body.promptCount > 1 && (
@@ -601,8 +597,11 @@ export function AgentActionViewer({ agentId, roomId }: AgentActionViewerProps) {
switch (selectedType) {
case ActionType.llm:
- // Include both LLM calls and actions (which often contain LLM prompts)
- if (usageType !== 'LLM' && !isActionLog) return false;
+ // Only show LLM model calls (not actions)
+ if (usageType !== 'LLM') return false;
+ break;
+ case ActionType.embedding:
+ if (usageType !== 'Embedding') return false;
break;
case ActionType.transcription:
if (usageType !== 'Transcription') return false;
@@ -611,6 +610,7 @@ export function AgentActionViewer({ agentId, roomId }: AgentActionViewerProps) {
if (usageType !== 'Image') return false;
break;
case ActionType.other:
+ // "Other" includes actions and unknown model types
if (usageType !== 'Other' && usageType !== 'Unknown' && !isActionLog) return false;
break;
}
@@ -761,6 +761,7 @@ export function AgentActionViewer({ agentId, roomId }: AgentActionViewerProps) {
All Actions
LLM Calls
+ Embeddings
Transcriptions
Image Operations
Other
diff --git a/packages/client/src/hooks/use-query-hooks.ts b/packages/client/src/hooks/use-query-hooks.ts
index b9e6d75375d7..d0c5058cc202 100644
--- a/packages/client/src/hooks/use-query-hooks.ts
+++ b/packages/client/src/hooks/use-query-hooks.ts
@@ -614,8 +614,15 @@ export function useAgentActions(agentId: UUID, roomId?: UUID, excludeTypes?: str
const response = await getClient().agents.getAgentLogs(agentId, {
limit: 50,
});
- // Map the API logs to client format
- return response ? response.map(mapApiLogToClient) : [];
+ if (!response) return [];
+
+ // Filter to only include model calls (useModel:*) and actions
+ const relevantLogs = response.filter((log) => {
+ const logType = log.type || '';
+ return logType.startsWith('useModel:') || logType === 'action';
+ });
+
+ return relevantLogs.map(mapApiLogToClient);
},
refetchInterval: 1000,
staleTime: 1000,
diff --git a/packages/core/src/__tests__/runtime-streaming.test.ts b/packages/core/src/__tests__/runtime-streaming.test.ts
index ff2871a83aa3..43a7979a54a7 100644
--- a/packages/core/src/__tests__/runtime-streaming.test.ts
+++ b/packages/core/src/__tests__/runtime-streaming.test.ts
@@ -403,4 +403,73 @@ describe('useModel Streaming', () => {
expect(result).toBe('XYZ');
});
});
+
+ describe('database logging', () => {
+ it('should log streaming model calls to database', async () => {
+ const mockChunks = ['Hello', ' ', 'World'];
+ const mockAdapter = createMockAdapter();
+
+ const streamingRuntime = new AgentRuntime({
+ agentId: stringToUuid('test-logging-agent'),
+ character: mockCharacter,
+ adapter: mockAdapter,
+ });
+
+ streamingRuntime.registerModel(
+ ModelType.TEXT_LARGE,
+ async (_rt, params) => {
+ if ((params as any).stream) {
+ return createMockTextStreamResult(mockChunks);
+ }
+ return mockChunks.join('');
+ },
+ 'test-provider'
+ );
+
+ await streamingRuntime.useModel(ModelType.TEXT_LARGE, {
+ prompt: 'Test prompt',
+ onStreamChunk: () => {},
+ });
+
+ // Verify adapter.log was called
+ const logCalls = (mockAdapter.log as any).mock.calls;
+ expect(logCalls.length).toBeGreaterThan(0);
+
+ // Verify the log contains correct model info
+ const logCall = logCalls[0][0];
+ expect(logCall.type).toBe('useModel:TEXT_LARGE');
+ expect(logCall.body.modelKey).toBe('TEXT_LARGE');
+ expect(logCall.body.response).toBe('Hello World');
+ });
+
+ it('should log non-streaming model calls to database', async () => {
+ const mockAdapter = createMockAdapter();
+
+ const nonStreamingRuntime = new AgentRuntime({
+ agentId: stringToUuid('test-logging-agent-2'),
+ character: mockCharacter,
+ adapter: mockAdapter,
+ });
+
+ nonStreamingRuntime.registerModel(
+ ModelType.TEXT_LARGE,
+ async () => 'Non-streamed response',
+ 'test-provider'
+ );
+
+ await nonStreamingRuntime.useModel(ModelType.TEXT_LARGE, {
+ prompt: 'Test prompt',
+ });
+
+ // Verify adapter.log was called
+ const logCalls = (mockAdapter.log as any).mock.calls;
+ expect(logCalls.length).toBeGreaterThan(0);
+
+ // Verify the log contains correct model info
+ const logCall = logCalls[0][0];
+ expect(logCall.type).toBe('useModel:TEXT_LARGE');
+ expect(logCall.body.modelKey).toBe('TEXT_LARGE');
+ expect(logCall.body.response).toBe('Non-streamed response');
+ });
+ });
});
diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts
index 96d9bab869d9..0f09337c795c 100644
--- a/packages/core/src/runtime.ts
+++ b/packages/core/src/runtime.ts
@@ -2166,6 +2166,61 @@ export class AgentRuntime implements IAgentRuntime {
return Object.keys(modelSettings).length > 0 ? modelSettings : null;
}
+ /**
+ * Helper to log model calls to the database (used by both streaming and non-streaming paths)
+ */
+ private logModelCall(
+ modelType: string,
+ modelKey: string,
+ params: unknown,
+ promptContent: string | null,
+ elapsedTime: number,
+ provider: string | undefined,
+ response: unknown
+ ): void {
+ // Log prompts to action context (except embeddings)
+ if (modelKey !== ModelType.TEXT_EMBEDDING && promptContent) {
+ if (this.currentActionContext) {
+ this.currentActionContext.prompts.push({
+ modelType: modelKey,
+ prompt: promptContent,
+ timestamp: Date.now(),
+ });
+ }
+ }
+
+ // Log to database
+ this.adapter.log({
+ entityId: this.agentId,
+ roomId: this.currentRoomId ?? this.agentId,
+ body: {
+ modelType,
+ modelKey,
+ params: {
+ ...(typeof params === 'object' && !Array.isArray(params) && params ? params : {}),
+ prompt: promptContent,
+ },
+ prompt: promptContent,
+ systemPrompt: this.character?.system || null,
+ runId: this.getCurrentRunId(),
+ timestamp: Date.now(),
+ executionTime: elapsedTime,
+ provider: provider || this.models.get(modelKey)?.[0]?.provider || 'unknown',
+ actionContext: this.currentActionContext
+ ? {
+ actionName: this.currentActionContext.actionName,
+ actionId: this.currentActionContext.actionId,
+ }
+ : undefined,
+ response:
+ Array.isArray(response) && response.every((x) => typeof x === 'number')
+ ? '[array]'
+ : response,
+ },
+ type: `useModel:${modelKey}`,
+ });
+ }
+
async useModel(
modelType: T,
params: ModelParamsMap[T],
@@ -2332,6 +2387,15 @@ export class AgentRuntime implements IAgentRuntime {
'Model output (stream with callback complete)'
);
+ this.logModelCall(
+ modelType,
+ modelKey,
+ params,
+ promptContent,
+ elapsedTime,
+ provider,
+ fullText
+ );
return fullText as R;
}
@@ -2351,49 +2415,7 @@ export class AgentRuntime implements IAgentRuntime {
'Model output'
);
- // Log all prompts except TEXT_EMBEDDING to track agent behavior
- if (modelKey !== ModelType.TEXT_EMBEDDING && promptContent) {
- // If we're in an action context, collect the prompt
- if (this.currentActionContext) {
- this.currentActionContext.prompts.push({
- modelType: modelKey,
- prompt: promptContent,
- timestamp: Date.now(),
- });
- }
- }
-
- // Keep the existing model logging for backward compatibility
- this.adapter.log({
- entityId: this.agentId,
- roomId: this.currentRoomId ?? this.agentId,
- body: {
- modelType,
- modelKey,
- params: {
- ...(typeof params === 'object' && !Array.isArray(params) && params ? params : {}),
- prompt: promptContent,
- },
- prompt: promptContent,
- systemPrompt: this.character?.system || null,
- runId: this.getCurrentRunId(),
- timestamp: Date.now(),
- executionTime: elapsedTime,
- provider: provider || this.models.get(modelKey)?.[0]?.provider || 'unknown',
- actionContext: this.currentActionContext
- ? {
- actionName: this.currentActionContext.actionName,
- actionId: this.currentActionContext.actionId,
- }
- : undefined,
- response:
- Array.isArray(response) && response.every((x) => typeof x === 'number')
- ? '[array]'
- : response,
- },
- type: `useModel:${modelKey}`,
- });
-
+ this.logModelCall(modelType, modelKey, params, promptContent, elapsedTime, provider, response);
return response as R;
}