langfuse

Форк
0
/
ObservationPreview.tsx 
220 строк · 7.8 Кб
1
import { JSONView } from "@/src/components/ui/code";
2
import { type Score } from "@prisma/client";
3
import {
4
  Card,
5
  CardContent,
6
  CardDescription,
7
  CardHeader,
8
  CardTitle,
9
} from "@/src/components/ui/card";
10
import { Badge } from "@/src/components/ui/badge";
11
import {
12
  Table,
13
  TableBody,
14
  TableCell,
15
  TableHead,
16
  TableHeader,
17
  TableRow,
18
} from "@/src/components/ui/table";
19
import { ManualScoreButton } from "@/src/features/manual-scoring/components/ManualScoreButton";
20
import { NewDatasetItemFromObservationButton } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
21
import { type ObservationReturnType } from "@/src/server/api/routers/traces";
22
import { api } from "@/src/utils/api";
23
import { IOPreview } from "@/src/components/trace/IOPreview";
24
import { formatIntervalSeconds } from "@/src/utils/dates";
25
import Link from "next/link";
26
import { usdFormatter } from "@/src/utils/numbers";
27
import { calculateDisplayTotalCost } from "@/src/components/trace";
28

29
export const ObservationPreview = (props: {
30
  observations: Array<ObservationReturnType>;
31
  projectId: string;
32
  scores: Score[];
33
  currentObservationId: string;
34
  traceId: string;
35
}) => {
36
  const observationWithInputAndOutput = api.observations.byId.useQuery({
37
    observationId: props.currentObservationId,
38
    traceId: props.traceId,
39
  });
40

41
  const preloadedObservation = props.observations.find(
42
    (o) => o.id === props.currentObservationId,
43
  );
44

45
  const totalCost = calculateDisplayTotalCost(
46
    preloadedObservation ? [preloadedObservation] : [],
47
  );
48

49
  if (!preloadedObservation) return <div className="flex-1">Not found</div>;
50
  return (
51
    <Card className="flex-1">
52
      <CardHeader className="flex flex-row flex-wrap justify-between gap-2">
53
        <div className="flex flex-col gap-1">
54
          <CardTitle>
55
            <span className="mr-2 rounded-sm bg-gray-200 p-1 text-xs">
56
              {preloadedObservation.type}
57
            </span>
58
            <span>{preloadedObservation.name}</span>
59
          </CardTitle>
60
          <CardDescription className="flex gap-2">
61
            {preloadedObservation.startTime.toLocaleString()}
62
          </CardDescription>
63
          <div className="flex flex-wrap gap-2">
64
            {preloadedObservation.promptId ? (
65
              <PromptBadge
66
                promptId={preloadedObservation.promptId}
67
                projectId={preloadedObservation.projectId}
68
              />
69
            ) : undefined}
70
            {preloadedObservation.completionStartTime ? (
71
              <Badge variant="outline">
72
                Time to first token:{" "}
73
                {formatIntervalSeconds(
74
                  (preloadedObservation.completionStartTime.getTime() -
75
                    preloadedObservation.startTime.getTime()) /
76
                    1000,
77
                )}
78
              </Badge>
79
            ) : null}
80
            {preloadedObservation.endTime ? (
81
              <Badge variant="outline">
82
                Latency:{" "}
83
                {formatIntervalSeconds(
84
                  (preloadedObservation.endTime.getTime() -
85
                    preloadedObservation.startTime.getTime()) /
86
                    1000,
87
                )}
88
              </Badge>
89
            ) : null}
90
            {preloadedObservation.type === "GENERATION" && (
91
              <Badge variant="outline">
92
                {preloadedObservation.promptTokens} prompt →{" "}
93
                {preloadedObservation.completionTokens} completion (∑{" "}
94
                {preloadedObservation.totalTokens})
95
              </Badge>
96
            )}
97
            {preloadedObservation.version ? (
98
              <Badge variant="outline">
99
                Version: {preloadedObservation.version}
100
              </Badge>
101
            ) : undefined}
102
            {preloadedObservation.model ? (
103
              <Badge variant="outline">{preloadedObservation.model}</Badge>
104
            ) : null}
105
            {totalCost ? (
106
              <Badge variant="outline">
107
                {usdFormatter(totalCost.toNumber())}
108
              </Badge>
109
            ) : undefined}
110

111
            {preloadedObservation.modelParameters &&
112
            typeof preloadedObservation.modelParameters === "object"
113
              ? Object.entries(preloadedObservation.modelParameters)
114
                  .filter(Boolean)
115
                  .map(([key, value]) => (
116
                    <Badge variant="outline" key={key}>
117
                      {key}: {value?.toString()}
118
                    </Badge>
119
                  ))
120
              : null}
121
          </div>
122
        </div>
123
        <div className="flex gap-2">
124
          <ManualScoreButton
125
            projectId={props.projectId}
126
            traceId={preloadedObservation.traceId}
127
            observationId={preloadedObservation.id}
128
            scores={props.scores}
129
          />
130
          {observationWithInputAndOutput.data ? (
131
            <NewDatasetItemFromObservationButton
132
              observationId={preloadedObservation.id}
133
              projectId={props.projectId}
134
              observationInput={observationWithInputAndOutput.data.input}
135
              observationOutput={observationWithInputAndOutput.data.output}
136
              key={preloadedObservation.id}
137
            />
138
          ) : null}
139
        </div>
140
      </CardHeader>
141
      <CardContent className="flex flex-col gap-4">
142
        <IOPreview
143
          key={preloadedObservation.id + "-input"}
144
          input={observationWithInputAndOutput.data?.input ?? undefined}
145
          output={observationWithInputAndOutput.data?.output ?? undefined}
146
          isLoading={observationWithInputAndOutput.isLoading}
147
        />
148
        {preloadedObservation.statusMessage ? (
149
          <JSONView
150
            key={preloadedObservation.id + "-status"}
151
            title="Status Message"
152
            json={preloadedObservation.statusMessage}
153
          />
154
        ) : null}
155

156
        {preloadedObservation.metadata ? (
157
          <JSONView
158
            key={preloadedObservation.id + "-metadata"}
159
            title="Metadata"
160
            json={preloadedObservation.metadata}
161
          />
162
        ) : null}
163

164
        {props.scores.find(
165
          (s) => s.observationId === preloadedObservation.id,
166
        ) ? (
167
          <div className="flex flex-col gap-2">
168
            <h3>Scores</h3>
169
            <Table>
170
              <TableHeader>
171
                <TableRow>
172
                  <TableHead className="w-[100px]">Timestamp</TableHead>
173
                  <TableHead>Name</TableHead>
174
                  <TableHead className="text-right">Value</TableHead>
175
                  <TableHead>Comment</TableHead>
176
                </TableRow>
177
              </TableHeader>
178
              <TableBody>
179
                {props.scores
180
                  .filter((s) => s.observationId === preloadedObservation.id)
181
                  .map((s) => (
182
                    <TableRow key={s.id}>
183
                      <TableCell className="text-xs">
184
                        {s.timestamp.toLocaleString()}
185
                      </TableCell>
186
                      <TableCell className="text-xs">{s.name}</TableCell>
187
                      <TableCell className="text-right text-xs">
188
                        {s.value}
189
                      </TableCell>
190
                      <TableCell className="text-xs">{s.comment}</TableCell>
191
                    </TableRow>
192
                  ))}
193
              </TableBody>
194
            </Table>
195
          </div>
196
        ) : null}
197
      </CardContent>
198
    </Card>
199
  );
200
};
201

202
const PromptBadge = (props: { promptId: string; projectId: string }) => {
203
  const prompt = api.prompts.byId.useQuery({
204
    id: props.promptId,
205
    projectId: props.projectId,
206
  });
207

208
  if (prompt.isLoading || !prompt.data) return null;
209
  return (
210
    <Link
211
      href={`/project/${props.projectId}/prompts/${prompt.data.name}?version=${prompt.data.version}`}
212
    >
213
      <Badge>
214
        Prompt: {prompt.data.name}
215
        {" - v"}
216
        {prompt.data.version}
217
      </Badge>
218
    </Link>
219
  );
220
};
221

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.