Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback inspector in Assistant builder #9680

Merged
merged 16 commits into from
Jan 8, 2025
Merged

Conversation

overmode
Copy link
Contributor

@overmode overmode commented Dec 31, 2024

Description

  • Add a bubble Performance tab to the assistant builder to inspect feedbacks
Screen.Recording.2025-01-08.at.12.45.20.mov

Risk

Break assistant builder

Deploy Plan

Deploy Front

@overmode overmode changed the title Main code brought in Feedback inspector in Assistant builder Dec 31, 2024
if (!config.versionCreatedAt) {
return `v${config.version}`;
}
return new Date(config.versionCreatedAt).toLocaleDateString("en-US", {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use a date instead of timeAgo because the iterations might be close in time, and you still want to easily distinguish them.
This is consistent with the date given in the prompt versioning Popover

@@ -565,6 +569,7 @@ export default function AssistantBuilder({
rightPanelStatus={rightPanelStatus}
openRightPanelTab={openRightPanelTab}
builderState={builderState}
agentConfigurationId={agentConfigurationId}
Copy link
Contributor Author

@overmode overmode Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passing Id instead of LightAgentConfigurationType because a larger refactoring is needed (wouldn't be consistent with submitAssistantBuilderForm, SharingButton, InstructionScreen, useSlackChannel, ...)

shouldAnimatePreviewDrawer &&
rightPanelStatus.tab === "Preview" &&
rightPanelStatus.openedAt != null &&
className={cn(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This keeps the same display except with a white background for the feedback tab

)}
{agentConfigurationId && (
<TabsTrigger
value="Performance"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Analytics will be added to this tab later -> Performance is a sensible name

feedback.conversationId &&
feedback.messageId &&
feedback.isConversationShared
? `${process.env.NEXT_PUBLIC_DUST_CLIENT_FACING_URL}/w/${owner.sId}/assistant/${feedback.conversationId}#${feedback.messageId}`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add a messageId anchor, because another PR will make it possible to auto-scroll to the message.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO this should be returned by the endpoint directly since this is very brittle the day we decide to change the endpoint, pretty sure we won't find this occurrence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean getting the link from the endpoint ?
I don't see how it would be easier to spot in the endpoint vs in this file, given that the endpoint is not the conversation one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that the endpoint is not the conversation one.
Not sure to get this one 🤔

@@ -20,7 +20,7 @@ export function PrevNextButtons({
<Button
label="Previous"
size="md"
variant="highlight"
variant="primary"
Copy link
Contributor Author

@overmode overmode Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes buttons black -> Asked from design team

| AgentMessageFeedbackType
| AgentMessageFeedbackWithMetadataType
)[];
totalFeedbackCount: number;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The count is needed, because pagination relies on it

};

// Get the total feedback count, because needed for pagination
const totalFeedbackCountPromise = AgentMessageFeedback.count({
Copy link
Contributor Author

@overmode overmode Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hits the highest number of feedbacks

explain analyze SELECT count(*) AS "count" FROM "agent_message_feedbacks" AS "agent_message_feedback" WHERE "agent_message_feedback"."workspaceId" = 44603 AND "agent_message_feedback"."agentConfigurationId" = 'claude-3-sonnet';
Aggregate (cost=40.55..40.56 rows=1 width=8) (actual time=0.281..0.281 rows=1 loops=1)
-> Seq Scan on agent_message_feedbacks agent_message_feedback (cost=0.00..39.62 rows=372 width=0) (actual time=0.012..0.249 rows=628 loops=1)
Filter: (("workspaceId" = 44603) AND (("agentConfigurationId")::text = 'claude-3-sonnet'::text))

Rows Removed by Filter: 640
Planning Time: 0.097 ms
Execution Time: 0.307 ms

}
className="hidden lg:flex"
>
<TabsList className="inline-flex items-center gap-2 border-b border-separator">
Copy link
Contributor Author

@overmode overmode Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the h-10 because it was producing a vertical scroll bar. The rest is unchanged.

@overmode overmode marked this pull request as ready for review January 2, 2025 15:30
@overmode overmode requested a review from flvndvd January 2, 2025 15:58
rightPanelStatus.openedAt != null &&
className={cn(
"grow-1 mb-5 h-full overflow-y-auto border-structure-200",
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it becomes pretty hard to figure the style by looking at this code...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's complex, I tried not to touch the design, imho we already had a good bunch of complexity and this syntax is easier to read

| AgentMessageFeedbackWithMetadataType
)[];
totalFeedbackCount: number;
}> {
const where: WhereOptions<AgentMessageFeedback> = {
// IMPORTANT: Necessary for global models who share ids across workspaces.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that global agent could not get feedback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They used to (at the time I wrote this comment), and an extra layer of security doesn't hurt I guess ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this layer should happened before hitting our DB since this does not require any DB interaction and can be disregard earlier.

front/lib/resources/agent_message_feedback_resource.ts Outdated Show resolved Hide resolved
front/lib/resources/agent_message_feedback_resource.ts Outdated Show resolved Hide resolved
@@ -91,35 +92,42 @@ export class AgentMessageFeedbackResource extends BaseResource<AgentMessageFeedb
});
}

static async fetch({
static async getAgentConfigurationFeedbacksByDescVersion({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes against the Resource pattern approach. All those functions should actually return a Resource not an object. Then if you need. serialized object you should use a toJSON. Any reason to not follow this pattern?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes,
The main obstacle is that you need to do several includes (on AgentMessage, Message and Conversation) which prevents you from only getting a resource back, and associating a toJSON to only a feedback resource.
I got this hint from @Fraggle https://dust4ai.slack.com/archives/C050SM8NSPK/p1734706622967029?thread_ts=1734705178.946699&cid=C050SM8NSPK

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok did not get this info. OOC why don't we use class variable to have those on the resource like we do for many other resources?

Example with DataSourceViewResource here:

readonly editedByUser?: Attributes<UserModel>;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that the returned object is too complex is that we should try to return resources instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will spend some time with @overmode today to align the pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please share your conclusions, i'm interested :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We ended up creating an attribute in the resource class to allow easy access to related objects. My main takeaways are that we should indeed refrain from returning anything else than a resource in a _resource file, that toJSON should be kept simple even if that means adding pose-serialize logic to /lib/api files, and that sequelize is really bad a typing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

front/lib/swr/assistants.ts Outdated Show resolved Hide resolved
@overmode overmode requested a review from flvndvd January 3, 2025 09:58
@overmode overmode force-pushed the lucas/feedbacks-inspector branch from 8090417 to 4c61121 Compare January 3, 2025 15:29
@@ -91,35 +92,42 @@ export class AgentMessageFeedbackResource extends BaseResource<AgentMessageFeedb
});
}

static async fetch({
static async getAgentConfigurationFeedbacksByDescVersion({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that the returned object is too complex is that we should try to return resources instead.

const { data, error, mutate, size, setSize, isLoading, isValidating } =
useSWRInfiniteWithDefaults(
(pageIndex: number, previousPageData) => {
console.log("previousPageData", previousPageData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✂️

@overmode overmode force-pushed the lucas/feedbacks-inspector branch 2 times, most recently from 3aa78fe to 5cd4c98 Compare January 7, 2025 14:48
});

// Intersection observer to detect when the user has scrolled to the bottom of the list.
const bottomRef = useRef<HTMLDivElement>(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to use the existing hook useLastMessageGroupObserver (ok naming is not generic, but the code is!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would need to refactor the version to not accept message but a generic entity (because I'm observing over feedbacks, not messages), and accept the id as a parameter (to avoid conflicts).
2nd point is fine but 1st one is painful, you don't want to have a 'any' type even though that's ~the idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more type, we could leverage duck typing to ensure that we accept all entities that have at least id set 🙃.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As seen irl the go-to type is unknown

agentConfigurationFeedbacks[index - 1].agentConfigurationVersion;
return (
<div key={feedback.id} className="animate-fadeIn">
{isNewVersion && (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard to understand how isNewVersion influences isLatestVersion, can we align naming?

@@ -91,35 +92,42 @@ export class AgentMessageFeedbackResource extends BaseResource<AgentMessageFeedb
});
}

static async fetch({
static async getAgentConfigurationFeedbacksByDescVersion({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

// If we have reached the last page and there are no more
// messages or the previous page has no messages, return null.
if (previousPageData && previousPageData.feedbacks.length < limit) {
setHasMore(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we expose hasMore? IIRC, returning null here will stop the fetch like it's being done in the message.

Copy link
Contributor Author

@overmode overmode Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is used to avoid calling setSize for ever, we also have a variation of it in messages https://github.com/dust-tt/dust/blob/lucas/feedbacks-inspector/front/components/assistant/conversation/ConversationViewer.tsx#L203

isAgentConfigurationFeedbacksLoading,
isValidating,
agentConfigurationFeedbacks,
hasMore: feedbacksNotExhausted,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit
Pretty confusing naming, we are loosing the boolean meaning.

<AgentConfigurationVersionHeader
agentConfiguration={agentConfigurationHistory[0]}
agentConfigurationVersion={agentConfigurationHistory[0].version}
isLatestVersion={true}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
isLatestVersion={true}
isLatestVersion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicitly setting to true is used widely across the codebase (59 files), I have no easy way to look for the other method to compare, but seems like I'm keeping this one. Cool syntax trick though 👍

@overmode overmode requested a review from flvndvd January 8, 2025 10:14
@overmode overmode force-pushed the lucas/feedbacks-inspector branch from 44edbfe to e693865 Compare January 8, 2025 12:59
@overmode overmode merged commit 6f0be19 into main Jan 8, 2025
6 of 7 checks passed
@overmode overmode deleted the lucas/feedbacks-inspector branch January 8, 2025 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants