+ "Completion Template"
)}
);
}
+
+function CompareButton() {
+ const addInstance = usePlaygroundContext((state) => state.addInstance);
+ return (
+ } />}
+ onClick={() => {
+ addInstance();
+ }}
+ />
+ );
+}
+
+function DeleteButton(props: PlaygroundInstanceProps) {
+ const deleteInstance = usePlaygroundContext((state) => state.deleteInstance);
+ return (
+ } />}
+ onClick={() => {
+ deleteInstance(props.playgroundInstanceId);
+ }}
+ />
+ );
+}
diff --git a/app/src/pages/playground/SpanPlaygroundPage.tsx b/app/src/pages/playground/SpanPlaygroundPage.tsx
new file mode 100644
index 0000000000..23db294101
--- /dev/null
+++ b/app/src/pages/playground/SpanPlaygroundPage.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+
+// import { useLoaderData } from "react-router";
+import { Playground } from "./Playground";
+
+export function SpanPlaygroundPage() {
+ // const data = useLoaderData();
+
+ return ;
+}
diff --git a/app/src/pages/playground/__generated__/spanPlaygroundPageLoaderQuery.graphql.ts b/app/src/pages/playground/__generated__/spanPlaygroundPageLoaderQuery.graphql.ts
new file mode 100644
index 0000000000..720661bdc1
--- /dev/null
+++ b/app/src/pages/playground/__generated__/spanPlaygroundPageLoaderQuery.graphql.ts
@@ -0,0 +1,156 @@
+/**
+ * @generated SignedSource<<60eb99c48e6f8167b1c74edc2a90ce62>>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ConcreteRequest, Query } from 'relay-runtime';
+export type spanPlaygroundPageLoaderQuery$variables = {
+ spanId: string;
+};
+export type spanPlaygroundPageLoaderQuery$data = {
+ readonly span: {
+ readonly __typename: "Span";
+ readonly attributes: string;
+ readonly context: {
+ readonly spanId: string;
+ };
+ } | {
+ // This will never be '%other', but we need some
+ // value in case none of the concrete values match.
+ readonly __typename: "%other";
+ };
+};
+export type spanPlaygroundPageLoaderQuery = {
+ response: spanPlaygroundPageLoaderQuery$data;
+ variables: spanPlaygroundPageLoaderQuery$variables;
+};
+
+const node: ConcreteRequest = (function(){
+var v0 = [
+ {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "spanId"
+ }
+],
+v1 = [
+ {
+ "kind": "Variable",
+ "name": "id",
+ "variableName": "spanId"
+ }
+],
+v2 = {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "__typename",
+ "storageKey": null
+},
+v3 = {
+ "kind": "InlineFragment",
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "SpanContext",
+ "kind": "LinkedField",
+ "name": "context",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "spanId",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "attributes",
+ "storageKey": null
+ }
+ ],
+ "type": "Span",
+ "abstractKey": null
+};
+return {
+ "fragment": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "spanPlaygroundPageLoaderQuery",
+ "selections": [
+ {
+ "alias": "span",
+ "args": (v1/*: any*/),
+ "concreteType": null,
+ "kind": "LinkedField",
+ "name": "node",
+ "plural": false,
+ "selections": [
+ (v2/*: any*/),
+ (v3/*: any*/)
+ ],
+ "storageKey": null
+ }
+ ],
+ "type": "Query",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Operation",
+ "name": "spanPlaygroundPageLoaderQuery",
+ "selections": [
+ {
+ "alias": "span",
+ "args": (v1/*: any*/),
+ "concreteType": null,
+ "kind": "LinkedField",
+ "name": "node",
+ "plural": false,
+ "selections": [
+ (v2/*: any*/),
+ (v3/*: any*/),
+ {
+ "kind": "TypeDiscriminator",
+ "abstractKey": "__isNode"
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "id",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+ ]
+ },
+ "params": {
+ "cacheID": "0736806d2ffe427d21f6183b6c707f1f",
+ "id": null,
+ "metadata": {},
+ "name": "spanPlaygroundPageLoaderQuery",
+ "operationKind": "query",
+ "text": "query spanPlaygroundPageLoaderQuery(\n $spanId: GlobalID!\n) {\n span: node(id: $spanId) {\n __typename\n ... on Span {\n context {\n spanId\n }\n attributes\n }\n __isNode: __typename\n id\n }\n}\n"
+ }
+};
+})();
+
+(node as any).hash = "a49841a5261bf37a73f4dddf55f49311";
+
+export default node;
diff --git a/app/src/pages/playground/constants.tsx b/app/src/pages/playground/constants.tsx
new file mode 100644
index 0000000000..64e5a7be52
--- /dev/null
+++ b/app/src/pages/playground/constants.tsx
@@ -0,0 +1 @@
+export const NUM_MAX_PLAYGROUND_INSTANCES = 2;
diff --git a/app/src/pages/playground/index.tsx b/app/src/pages/playground/index.tsx
index 9bc400653a..ecc7e76730 100644
--- a/app/src/pages/playground/index.tsx
+++ b/app/src/pages/playground/index.tsx
@@ -1 +1,3 @@
export * from "./PlaygroundPage";
+export * from "./SpanPlaygroundPage";
+export * from "./spanPlaygroundPageLoader";
diff --git a/app/src/pages/playground/spanPlaygroundPageLoader.ts b/app/src/pages/playground/spanPlaygroundPageLoader.ts
new file mode 100644
index 0000000000..792e0ef46b
--- /dev/null
+++ b/app/src/pages/playground/spanPlaygroundPageLoader.ts
@@ -0,0 +1,32 @@
+import { fetchQuery, graphql } from "react-relay";
+import { LoaderFunctionArgs } from "react-router";
+
+import RelayEnvironment from "@phoenix/RelayEnvironment";
+
+import { spanPlaygroundPageLoaderQuery } from "./__generated__/spanPlaygroundPageLoaderQuery.graphql";
+export async function spanPlaygroundPageLoader(args: LoaderFunctionArgs) {
+ const { spanId } = args.params;
+ if (!spanId || typeof spanId !== "string") {
+ throw new Error("Invalid spanId");
+ }
+ const loaderData = await fetchQuery(
+ RelayEnvironment,
+ graphql`
+ query spanPlaygroundPageLoaderQuery($spanId: GlobalID!) {
+ span: node(id: $spanId) {
+ __typename
+ ... on Span {
+ context {
+ spanId
+ }
+ attributes
+ }
+ }
+ }
+ `,
+ {
+ spanId,
+ }
+ ).toPromise();
+ return loaderData;
+}
diff --git a/app/src/pages/playground/types.ts b/app/src/pages/playground/types.ts
new file mode 100644
index 0000000000..dc3b097f5e
--- /dev/null
+++ b/app/src/pages/playground/types.ts
@@ -0,0 +1,7 @@
+export interface PlaygroundInstanceProps {
+ /**
+ * Multiple playground instances are supported.
+ * The id is used to identify the instance.
+ */
+ playgroundInstanceId: number;
+}
diff --git a/app/src/pages/trace/SpanDetails.tsx b/app/src/pages/trace/SpanDetails.tsx
index 671d9724ec..628c8c7dd1 100644
--- a/app/src/pages/trace/SpanDetails.tsx
+++ b/app/src/pages/trace/SpanDetails.tsx
@@ -63,6 +63,7 @@ import {
import { SpanKindIcon } from "@phoenix/components/trace";
import { SpanKindLabel } from "@phoenix/components/trace/SpanKindLabel";
import { useNotifySuccess, useTheme } from "@phoenix/contexts";
+import { useFeatureFlag } from "@phoenix/contexts/FeatureFlagsContext";
import { usePreferencesContext } from "@phoenix/contexts/PreferencesContext";
import {
AttributeDocument,
@@ -148,6 +149,8 @@ export function SpanDetails({
spanNodeId: string;
projectId: string;
}) {
+ const isPromptPlaygroundEnabled = useFeatureFlag("playground");
+ const navigate = useNavigate();
const { span } = useLazyLoadQuery(
graphql`
query SpanDetailsQuery($spanId: GlobalID!) {
@@ -244,6 +247,18 @@ export function SpanDetails({
{span.name}
+ {isPromptPlaygroundEnabled ? (
+ } />}
+ disabled={span.spanKind !== "llm"}
+ onClick={() => {
+ navigate(`/playground/spans/${span.id}`);
+ }}
+ >
+ Playground
+
+ ) : null}
;
+}
+
+type DatasetInput = {
+ datasetId: string;
+};
+
+type ManualInput = {
+ variables: Record;
+};
+
+type PlaygroundInput = DatasetInput | ManualInput;
+
+/**
+ * A single instance of the playground that has
+ * - a template
+ * - tools
+ * - input (dataset or manual)
+ * - output (experiment or spans)
+ */
+export interface PlaygroundInstance {
+ /**
+ * An ID to uniquely identify the instance
+ */
+ id: number;
+ template: PlaygroundTemplate;
+ tools: unknown;
+ input: PlaygroundInput;
+ output: unknown;
+}
+
+/**
+ * All actions for a playground instance must contain the index of the playground
+ */
+interface PlaygroundInstanceActionParams {
+ playgroundInstanceId: number;
}
+interface AddMessageParams extends PlaygroundInstanceActionParams {}
export interface PlaygroundState extends PlaygroundProps {
/**
@@ -17,15 +104,155 @@ export interface PlaygroundState extends PlaygroundProps {
* @param operationType
*/
setOperationType: (operationType: GenAIOperationType) => void;
+ /**
+ * Setter for the input mode.
+ */
+ setInputMode: (inputMode: PlaygroundInputMode) => void;
+ /**
+ * Add a comparison instance to the playground
+ */
+ addInstance: () => void;
+ /**
+ * Delete a specific instance of the playground
+ * @param instanceId the instance to delete
+ */
+ deleteInstance: (instanceId: number) => void;
+ /**
+ * Add a message to a playground instance
+ */
+ addMessage: (params: AddMessageParams) => void;
+ /**
+ * Update an instance of the playground
+ */
+ updateInstance: (params: {
+ instanceId: number;
+ patch: Partial;
+ }) => void;
}
+const DEFAULT_CHAT_COMPLETION_TEMPLATE: PlaygroundChatTemplate = {
+ __type: "chat",
+ messages: [
+ {
+ role: "system",
+ content: "You are a chatbot",
+ },
+ {
+ role: "user",
+ content: "{{question}}",
+ },
+ ],
+};
+
+const DEFAULT_TEXT_COMPLETION_TEMPLATE: PlaygroundTextCompletionTemplate = {
+ __type: "text_completion",
+ prompt: "{{question}}",
+};
+
export const createPlaygroundStore = (
initialProps?: Partial
) => {
- const playgroundStore: StateCreator = (set) => ({
+ const playgroundStore: StateCreator = (set, get) => ({
operationType: "chat",
- setOperationType: (operationType: GenAIOperationType) =>
- set({ operationType }),
+ inputMode: "manual",
+ setInputMode: (inputMode: PlaygroundInputMode) => set({ inputMode }),
+ instances: [
+ {
+ id: playgroundInstanceIdIndex++,
+ template: DEFAULT_CHAT_COMPLETION_TEMPLATE,
+ tools: {},
+ input: { variables: {} },
+ output: {},
+ },
+ ],
+ setOperationType: (operationType: GenAIOperationType) => {
+ if (operationType === "chat") {
+ // TODO: this is incorrect, it should only change the template
+ set({
+ instances: [
+ {
+ id: playgroundInstanceIdIndex++,
+ template: DEFAULT_CHAT_COMPLETION_TEMPLATE,
+ tools: {},
+ input: { variables: {} },
+ output: {},
+ },
+ ],
+ });
+ } else {
+ set({
+ instances: [
+ {
+ id: playgroundInstanceIdIndex++,
+ template: DEFAULT_TEXT_COMPLETION_TEMPLATE,
+ tools: {},
+ input: { variables: {} },
+ output: {},
+ },
+ ],
+ });
+ }
+ set({ operationType });
+ },
+ addInstance: () => {
+ const instance = get().instances[0];
+ if (!instance) {
+ return;
+ }
+ // For now just hard-coded to two instances
+ set({
+ instances: [
+ instance,
+ {
+ ...instance,
+ id: playgroundInstanceIdIndex++,
+ },
+ ],
+ });
+ },
+ deleteInstance: (instanceId: number) => {
+ const instances = get().instances;
+ set({
+ instances: instances.filter((instance) => instance.id !== instanceId),
+ });
+ },
+ addMessage: ({ playgroundInstanceId }) => {
+ const instances = get().instances;
+
+ // Update the given instance
+ set({
+ instances: instances.map((instance) => {
+ if (
+ instance.id === playgroundInstanceId &&
+ instance?.template &&
+ instance?.template.__type === "chat"
+ ) {
+ return {
+ ...instance,
+ messages: [
+ ...instance.template.messages,
+ { role: "user", content: "{question}" },
+ ],
+ };
+ }
+ return instance;
+ }),
+ });
+ },
+ updateInstance: ({ instanceId, patch }) => {
+ const instances = get().instances;
+ set({
+ instances: instances.map((instance) => {
+ if (instance.id === instanceId) {
+ return {
+ ...instance,
+ ...patch,
+ };
+ }
+ return instance;
+ }),
+ });
+ },
...initialProps,
});
return create(devtools(playgroundStore));
diff --git a/app/src/utils/__tests__/spanUtils.test.ts b/app/src/utils/__tests__/spanUtils.test.ts
new file mode 100644
index 0000000000..e879ddc575
--- /dev/null
+++ b/app/src/utils/__tests__/spanUtils.test.ts
@@ -0,0 +1,46 @@
+import { llmSpanToInvocation } from "../spanUtils";
+
+const chatCompletionLLMSpanAttributes = {
+ // input: {
+ // mime_type: "application/json",
+ // value:
+ // '{"messages": [{"role": "system", "content": "You are an expert Q&A system that is trusted around the world.\\nAlways answer the query using the provided context information, and not prior knowledge.\\nSome rules to follow:\\n1. Never directly reference the given context in your answer.\\n2. Avoid statements like \'Based on the context, ...\' or \'The context information ...\' or anything along those lines.", "additional_kwargs": {}}, {"role": "user", "content": "Context information is below.\\n---------------------\\nsource: https://docs.arize.com/phoenix/tracing/concepts-tracing/what-are-traces\\ntitle: Traces\\n\\nNot more than 4-1/4 inches high, or more than 6 inches long, or greater than \\\\n0.016 inch thick.\\\\nd. Not more than 3.5 ounces (Charge flat-size prices for First-Class Mail \\\\ncard-type pieces over 3.5 ounces.)\\\\n\\\\n Instruction: Based on the above documents, provide a detailed answer for the user question below.\\\\n Answer \\\\\\"don\'t know\\\\\\" if not present in the document.\\\\n \\",\\n\\n\\n \\n\\"llm.input_messages.1.message.role\\"\\n:\\n \\n\\"user\\"\\n,\\n\\n\\n \\n\\"llm.input_messages.1.message.content\\"\\n:\\n \\n\\"Hello\\"\\n,\\n\\n\\n \\n\\"llm.model_name\\"\\n:\\n \\n\\"gpt-4-turbo-preview\\"\\n,\\n\\n\\n \\n\\"llm.invocation_parameters\\"\\n:\\n \\n\\"{\\\\\\"temperature\\\\\\": 0.1, \\\\\\"model\\\\\\": \\\\\\"gpt-4-turbo-preview\\\\\\"}\\"\\n,\\n\\n\\n \\n\\"output.value\\"\\n:\\n \\n\\"How are you?\\"\\n }\\n,\\n\\n\\n \\n\\"events\\"\\n:\\n []\\n,\\n\\n\\n \\n\\"links\\"\\n:\\n []\\n,\\n\\n\\n \\n\\"resource\\"\\n:\\n {\\n\\n\\n \\n\\"attributes\\"\\n:\\n {}\\n,\\n\\n\\n \\n\\"schema_url\\"\\n:\\n \\n\\"\\"\\n\\n\\n }\\n\\n\\n}\\nSpans can be nested, as is implied by the presence of a parent span ID: child spans represent sub-operations. This allows spans to more accurately capture the work done in an application.\\nTraces\\nA trace records the paths taken by requests (made by an application or end-user) as they propagate through multiple steps.\\nWithout tracing, it is challenging to pinpoint the cause of performance problems in a system.\\nIt improves the visibility of our application or system\\u2019s health and lets us debug behavior that is difficult to reproduce locally. Tracing is essential for LLM applications, which commonly have nondeterministic problems or are too complicated to reproduce locally.\\nTracing makes debugging and understanding LLM applications less daunting by breaking down what happens within a request as it flows through a system.\\nA trace is made of one or more spans. The first span represents the root span. Each root span represents a request from start to finish. The spans underneath the parent provide a more in-depth context of what occurs during a request (or what steps make up a request).\\nProjects\\nA \\nproject\\n is a collection of traces. You can think of a project as a container for all the traces that are related to a single application or service. You can have multiple projects, and each project can have multiple traces. Projects can be useful for various use-cases such as separating out environments, logging traces for evaluation runs, etc. To learn more about how to setup projects, see the \\nhow-to guide.\\nSpan Kind\\nWhen a span is created, it is created as one of the following: Chain, Retriever, Reranker, LLM, Embedding, Agent, or Tool. \\nCHAIN\\nA Chain is a starting point or a link between different LLM application steps. For example, a Chain span could be used to represent the beginning of a request to an LLM application or the glue code that passes context from a retriever to and LLM call.\\nRETRIEVER\\nA Retriever is a span that represents a data retrieval step. For example, a Retriever span could be used to represent a call to a vector store or a database.\\nRERANKER\\nA Reranker is a span that represents the reranking of a set of input documents. For example, a cross-encoder may be used to compute the input documents\' relevance scores with respect to a user query, and the top K documents with the highest scores are then returned by the Reranker.\\nLLM\\nAn LLM is a span that represents a call to an LLM. For example, an LLM span could be used to represent a call to OpenAI or Llama.\\nEMBEDDING\\nAn Embedding is a span that represents a call to an LLM for an embedding. For example, an Embedding span could be used to represent a call OpenAI to get an ada-2 embedding for retrieval.\\nTOOL\\nA Tool is a span that represents a call to an external tool such as a calculator or a weather API.\\nAGENT\\nA span that encompasses calls to LLMs and Tools. An agent describes a reasoning block that acts on tools using the guidance of an LLM.\\n\\nSpan Attributes\\nAttributes are key-value pairs that contain metadata that you can use to annotate a span to carry information about the operation it is tracking.\\nFor example, if a span invokes an LLM, you can capture the model name, the invocation parameters, the token count, and so on.\\nAttributes have the following rules:\\nKeys must be non-null string values\\nValues must be a non-null string, boolean, floating point value, integer, or an array of these values Additionally, there are Semantic Attributes, which are known naming conventions for metadata that is typically present in common operations. It\'s helpful to use semantic attribute naming wherever possible so that common kinds of metadata are standardized across systems. See \\nsemantic conventions\\n for more information.\\n\\nsource: https://docs.arize.com/phoenix/tracing/llm-traces\\ntitle: Overview: Tracing\\n\\nOverview: Tracing\\nTracing the execution of LLM applications using Telemetry\\nLLM tracing records the paths taken by requests as they propagate through multiple steps or components of an LLM application. For example, when a user interacts with an LLM application, tracing can capture the sequence of operations, such as document retrieval, embedding generation, language model invocation, and response generation to provide a detailed timeline of the request\'s execution.\\nTracing is a helpful tool for understanding how your LLM application works. Phoenix offers comprehensive tracing capabilities that are not tied to any specific LLM vendor or framework. Phoenix accepts traces over the OpenTelemetry protocol (OTLP) and supports first-class instrumentation for a variety of frameworks ( \\nLlamaIndex\\n, \\nLangChain\\n,\\n DSPy\\n), SDKs (\\nOpenAI\\n, \\nBedrock\\n, \\nMistral\\n, \\nVertex\\n), and Languages. (Python, Javascript, etc.)\\nUsing Phoenix\'s tracing capabilities can provide important insights into the inner workings of your LLM application. By analyzing the collected trace data, you can identify and address various performance and operational issues and improve the overall reliability and efficiency of your system.\\nApplication Latency\\n: Identify and address slow invocations of LLMs, Retrievers, and other components within your application, enabling you to optimize performance and responsiveness.\\nToken Usage\\n: Gain a detailed breakdown of token usage for your LLM calls, allowing you to identify and optimize the most expensive LLM invocations.\\nRuntime Exceptions\\n: Capture and inspect critical runtime exceptions, such as rate-limiting events, that can help you proactively address and mitigate potential issues.\\nRetrieved Documents\\n: Inspect the documents retrieved during a Retriever call, including the score and order in which they were returned to provide insight into the retrieval process.\\nEmbeddings\\n: Examine the embedding text used for retrieval and the underlying embedding model to allow you to validate and refine your embedding strategies.\\nLLM Parameters\\n: Inspect the parameters used when calling an LLM, such as temperature and system prompts, to ensure optimal configuration and debugging.\\nPrompt Templates\\n: Understand the prompt templates used during the prompting step and the variables that were applied, allowing you to fine-tune and improve your prompting strategies.\\nTool Descriptions\\n: View the descriptions and function signatures of the tools your LLM has been given access to in order to better understand and control your LLM\\u2019s capabilities.\\nLLM Function Calls\\n: For LLMs with function call capabilities (e.g., OpenAI), you can inspect the function selection and function messages in the input to the LLM, further improving your ability to debug and optimize your application.\\nBy using tracing in Phoenix, you can gain increased visibility into your LLM application, empowering you to identify and address performance bottlenecks, optimize resource utilization, and ensure the overall reliability and effectiveness of your system.\\nView the inner workings for your LLM Application\\nTo get started, check out the \\nQuickstart guide\\nAfter that, read through the \\nConcepts Section\\n to get and understanding of the different components.\\nIf you want to learn how to accomplish a particular task, check out the \\nHow-To Guides.\\n\\n\\nPrevious\\nFAQs: Deployment\\nNext\\nQuickstart: Tracing\\nLast updated \\n6 hours ago\\n---------------------\\nGiven the context information and not prior knowledge, answer the query.\\nQuery: Can I use gRPC for trace collection?\\nAnswer: ", "additional_kwargs": {}}]}',
+ // },
+ llm: {
+ output_messages: [
+ {
+ message: {
+ content: "This is an AI Answer",
+ role: "assistant",
+ },
+ },
+ ],
+ model_name: "gpt-3.5-turbo",
+ token_count: { completion: 9.0, prompt: 1881.0, total: 1890.0 },
+ input_messages: [
+ {
+ message: {
+ content: "You are a chatbot",
+ role: "system",
+ },
+ },
+ {
+ message: {
+ content: "Anser me the following question. Are you sentient?",
+ role: "user",
+ },
+ },
+ ],
+ invocation_parameters:
+ '{"context_window": 16384, "num_output": -1, "is_chat_model": true, "is_function_calling_model": true, "model_name": "gpt-3.5-turbo"}',
+ },
+ openinference: { span: { kind: "LLM" } },
+ // output: { value: "assistant: You can use gRPC for trace collection." },
+};
+
+describe("spanUtils", () => {
+ it("should convert a chat completion llm span to an invocation object type", () => {
+ const result = llmSpanToInvocation(chatCompletionLLMSpanAttributes);
+ expect(result).toEqual({});
+ });
+});
diff --git a/app/src/utils/spanUtils.ts b/app/src/utils/spanUtils.ts
new file mode 100644
index 0000000000..f81b76be06
--- /dev/null
+++ b/app/src/utils/spanUtils.ts
@@ -0,0 +1,3 @@
+export function llmSpanToInvocation(_span: unknown): unknown {
+ return {};
+}