diff --git a/weave-js/src/common/util/SdkPointCloudToBabylon.test.ts b/weave-js/src/common/util/SdkPointCloudToBabylon.test.ts
index 95c7639dc4a..df5eca57b46 100644
--- a/weave-js/src/common/util/SdkPointCloudToBabylon.test.ts
+++ b/weave-js/src/common/util/SdkPointCloudToBabylon.test.ts
@@ -4,6 +4,7 @@ import {
DEFAULT_POINT_COLOR,
getFilteringOptionsForPointCloud,
getVertexCompatiblePositionsAndColors,
+ loadPointCloud,
MAX_BOUNDING_BOX_LABELS_FOR_DISPLAY,
MaxAlphaValue,
} from './SdkPointCloudToBabylon';
@@ -174,3 +175,16 @@ describe('getFilteringOptionsForPointCloud', () => {
expect(newClassIdToLabel.get(49)).toEqual('label49');
});
});
+describe('loadPointCloud', () => {
+ it('appropriate defaults set when loading point cloud from file', () => {
+ const fileContents = JSON.stringify({
+ boxes: [],
+ points: [[]],
+ type: 'lidar/beta',
+ vectors: [],
+ });
+ const babylonPointCloud = loadPointCloud(fileContents);
+ expect(babylonPointCloud.points).toHaveLength(1);
+ expect(babylonPointCloud.points[0].position).toEqual([0, 0, 0]);
+ });
+});
diff --git a/weave-js/src/common/util/SdkPointCloudToBabylon.ts b/weave-js/src/common/util/SdkPointCloudToBabylon.ts
index 274e1676be4..d52682743ee 100644
--- a/weave-js/src/common/util/SdkPointCloudToBabylon.ts
+++ b/weave-js/src/common/util/SdkPointCloudToBabylon.ts
@@ -160,7 +160,7 @@ export const handlePoints = (object3D: Object3DScene): ScenePoint[] => {
// Draw Points
return truncatedPoints.map(point => {
const [x, y, z, r, g, b] = point;
- const position: Position = [x, y, z];
+ const position: Position = [x ?? 0, y ?? 0, z ?? 0];
const category = r;
if (r !== undefined && g !== undefined && b !== undefined) {
diff --git a/weave-js/src/common/util/render_babylon.ts b/weave-js/src/common/util/render_babylon.ts
index 10aee3f6c51..ebd213c2677 100644
--- a/weave-js/src/common/util/render_babylon.ts
+++ b/weave-js/src/common/util/render_babylon.ts
@@ -394,6 +394,15 @@ const pointCloudScene = (
// Apply vertexData to custom mesh
vertexData.applyToMesh(pcMesh);
+ // A file without any points defined still includes a single, empty "point".
+ // In order to play nice with Babylon, we position this empty point at 0,0,0.
+ // Hence, a pointCloud with a single point at 0,0,0 is likely empty.
+ const isEmpty =
+ pointCloud.points.length === 1 &&
+ pointCloud.points[0].position[0] === 0 &&
+ pointCloud.points[0].position[1] === 0 &&
+ pointCloud.points[0].position[2] === 0;
+
camera.parent = pcMesh;
const pcMaterial = new Babylon.StandardMaterial('mat', scene);
@@ -472,8 +481,8 @@ const pointCloudScene = (
new Vector3(edges.length * 2, edges.length * 2, edges.length * 2)
);
- // If we are iterating over camera, target a box
- if (index === meta?.cameraIndex) {
+ // If we are iterating over camera or the cloud is empty, target a box
+ if (index === meta?.cameraIndex || (index === 0 && isEmpty)) {
camera.position = center.add(new Vector3(0, 0, 1000));
camera.target = center;
camera.zoomOn([lines]);
diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/feedback/StructuredFeedback/FeedbackSidebar.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/feedback/StructuredFeedback/FeedbackSidebar.tsx
index ef6bcbd69ff..0b3c9603fef 100644
--- a/weave-js/src/components/PagePanelComponents/Home/Browse3/feedback/StructuredFeedback/FeedbackSidebar.tsx
+++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/feedback/StructuredFeedback/FeedbackSidebar.tsx
@@ -96,7 +96,7 @@ export const FeedbackSidebar = ({
Feedback
-
+
{humanAnnotationSpecs.length > 0 ? (
<>
diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts
index 38b5c820195..33ced58ec49 100644
--- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts
+++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/hooks.ts
@@ -246,6 +246,63 @@ export const isTraceCallChatFormatGemini = (call: TraceCallSchema): boolean => {
);
};
+export const isAnthropicContentBlock = (block: any): boolean => {
+ if (!_.isPlainObject(block)) {
+ return false;
+ }
+ // TODO: Are there other types?
+ if (block.type !== 'text') {
+ return false;
+ }
+ if (!hasStringProp(block, 'text')) {
+ return false;
+ }
+ return true;
+};
+
+export const isAnthropicCompletionFormat = (output: any): boolean => {
+ if (output !== null) {
+ // TODO: Could have additional checks here on things like usage
+ if (
+ _.isPlainObject(output) &&
+ output.type === 'message' &&
+ output.role === 'assistant' &&
+ hasStringProp(output, 'model') &&
+ _.isArray(output.content) &&
+ output.content.every((c: any) => isAnthropicContentBlock(c))
+ ) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+};
+
+type AnthropicContentBlock = {
+ type: 'text';
+ text: string;
+};
+
+export const anthropicContentBlocksToChoices = (
+ blocks: AnthropicContentBlock[],
+ stopReason: string
+): Choice[] => {
+ const choices: Choice[] = [];
+ for (let i = 0; i < blocks.length; i++) {
+ const block = blocks[i];
+ choices.push({
+ index: i,
+ message: {
+ role: 'assistant',
+ content: block.text,
+ },
+ // TODO: What is correct way to map this?
+ finish_reason: stopReason,
+ });
+ }
+ return choices;
+};
+
export const isTraceCallChatFormatOpenAI = (call: TraceCallSchema): boolean => {
if (!('messages' in call.inputs)) {
return false;
@@ -336,6 +393,19 @@ export const normalizeChatRequest = (request: any): ChatRequest => {
],
};
}
+ // Anthropic has system message as a top-level request field
+ if (hasStringProp(request, 'system')) {
+ return {
+ ...request,
+ messages: [
+ {
+ role: 'system',
+ content: request.system,
+ },
+ ...request.messages,
+ ],
+ };
+ }
return request as ChatRequest;
};
@@ -360,6 +430,24 @@ export const normalizeChatCompletion = (
},
};
}
+ if (isAnthropicCompletionFormat(completion)) {
+ return {
+ id: completion.id,
+ choices: anthropicContentBlocksToChoices(
+ completion.content,
+ completion.stop_reason
+ ),
+ created: 0,
+ model: completion.model,
+ system_fingerprint: '',
+ usage: {
+ prompt_tokens: completion.usage.input_tokens,
+ completion_tokens: completion.usage.output_tokens,
+ total_tokens:
+ completion.usage.input_tokens + completion.usage.output_tokens,
+ },
+ };
+ }
return completion as ChatCompletion;
};
diff --git a/weave-js/src/components/Tag/Pill.tsx b/weave-js/src/components/Tag/Pill.tsx
index 6f734f9cbfc..ed958a0c543 100644
--- a/weave-js/src/components/Tag/Pill.tsx
+++ b/weave-js/src/components/Tag/Pill.tsx
@@ -59,3 +59,42 @@ export const IconOnlyPill: FC
= ({
);
};
+
+export type ExpandingPillProps = {
+ className?: string;
+ color?: TagColorName;
+ icon: IconName;
+ label: string;
+};
+export const ExpandingPill = ({
+ className,
+ color,
+ icon,
+ label,
+}: ExpandingPillProps) => {
+ const classes = useTagClasses({color, isInteractive: true});
+ return (
+
+
+
+
+ {label}
+
+
+
+ );
+};
diff --git a/weave/trace/weave_client.py b/weave/trace/weave_client.py
index 86ba7b8653e..0eca3fcbedb 100644
--- a/weave/trace/weave_client.py
+++ b/weave/trace/weave_client.py
@@ -239,7 +239,9 @@ def func_name(self) -> str:
@property
def feedback(self) -> RefFeedbackQuery:
if not self.id:
- raise ValueError("Can't get feedback for call without ID")
+ raise ValueError(
+ "Can't get feedback for call without ID, was `weave.init` called?"
+ )
if self._feedback is None:
try:
@@ -253,7 +255,9 @@ def feedback(self) -> RefFeedbackQuery:
@property
def ui_url(self) -> str:
if not self.id:
- raise ValueError("Can't get URL for call without ID")
+ raise ValueError(
+ "Can't get URL for call without ID, was `weave.init` called?"
+ )
try:
entity, project = self.project_id.split("/")
@@ -265,7 +269,9 @@ def ui_url(self) -> str:
def ref(self) -> CallRef:
entity, project = self.project_id.split("/")
if not self.id:
- raise ValueError("Can't get ref for call without ID")
+ raise ValueError(
+ "Can't get ref for call without ID, was `weave.init` called?"
+ )
return CallRef(entity, project, self.id)
@@ -273,7 +279,9 @@ def ref(self) -> CallRef:
def children(self) -> CallsIter:
client = weave_client_context.require_weave_client()
if not self.id:
- raise ValueError("Can't get children of call without ID")
+ raise ValueError(
+ "Can't get children of call without ID, was `weave.init` called?"
+ )
client = weave_client_context.require_weave_client()
return CallsIter(