From f755d981145029c783a2b65fd57a74cfb702217a Mon Sep 17 00:00:00 2001 From: Mikyo King Date: Tue, 3 Dec 2024 12:10:16 -0700 Subject: [PATCH] feat(sessions): getting started guide (#5592) * feat(sessions): getting started guide * minimum working sessions example * add python getting started guide * add typescript guide * add tutorials * cleanup * fix format --- app/src/pages/project/PythonSessionsGuide.tsx | 101 +++++++++ app/src/pages/project/SessionsTableEmpty.tsx | 54 ++++- .../pages/project/TypeScriptSessionsGuide.tsx | 115 ++++++++++ cspell.json | 1 + .../tracing_openai_sessions_tutorial.ipynb | 196 ++++++++++++++++++ .../tracing/openai_sessions_tutorial.ipynb | 187 +++++++++++++++++ 6 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 app/src/pages/project/PythonSessionsGuide.tsx create mode 100644 app/src/pages/project/TypeScriptSessionsGuide.tsx create mode 100644 js/examples/notebooks/tracing_openai_sessions_tutorial.ipynb create mode 100644 tutorials/tracing/openai_sessions_tutorial.ipynb diff --git a/app/src/pages/project/PythonSessionsGuide.tsx b/app/src/pages/project/PythonSessionsGuide.tsx new file mode 100644 index 0000000000..ed659bbefb --- /dev/null +++ b/app/src/pages/project/PythonSessionsGuide.tsx @@ -0,0 +1,101 @@ +import React from "react"; + +import { Heading, Text, View } from "@arizeai/components"; + +import { ExternalLink } from "@phoenix/components"; +import { CodeWrap, PythonBlockWithCopy } from "@phoenix/components/code"; + +const INSTALL_OPENINFERENCE_INSTRUMENTATION_PYTHON = `pip install openinference-instrumentation`; +const ADD_SESSION_ID_PYTHON = `import uuid + +import openai +from openinference.instrumentation import using_session +from openinference.semconv.trace import SpanAttributes +from opentelemetry import trace + +client = openai.Client() +session_id = str(uuid.uuid4()) + +tracer = trace.get_tracer(__name__) + +@tracer.start_as_current_span(name="agent", attributes={SpanAttributes.OPENINFERENCE_SPAN_KIND: "agent"}) +def assistant( + messages: list[dict], + session_id: str = str, +): + current_span = trace.get_current_span() + current_span.set_attribute(SpanAttributes.SESSION_ID, session_id) + current_span.set_attribute(SpanAttributes.INPUT_VALUE, messages[-1].get('content')) + + # Propagate the session_id down to spans crated by the OpenAI instrumentation + # This is not strictly necessary, but it helps to correlate the spans to the same session + with using_session(session_id): + response = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[{"role": "system", "content": "You are a helpful assistant."}] + messages, + ).choices[0].message + + current_span.set_attribute(SpanAttributes.OUTPUT_VALUE, response.content) + return response + +messages = [ + {"role": "user", "content": "hi! im bob"} +] +response = assistant( + messages, + session_id=session_id, +) +messages = messages + [ + response, + {"role": "user", "content": "what's my name?"} +] +response = assistant( + messages, + session_id=session_id, +)`; + +export function PythonSessionsGuide() { + return ( +
+ + + Install Dependencies + + + + + Sessions are tracked via the OpenTelemetry attribute session.id + . The easiest way to get started with sessions is to use the + OpenInference instrumentation package. + + + + + + + + Add Session ID to your Traces + + + + + Below is an example of how to add session IDs to a simple OpenAI + application. + + + + + + + + For more information on how to use sessions, consult the{" "} + + documentation + + + +
+ ); +} diff --git a/app/src/pages/project/SessionsTableEmpty.tsx b/app/src/pages/project/SessionsTableEmpty.tsx index a35e5cee5c..a6162ff1d2 100644 --- a/app/src/pages/project/SessionsTableEmpty.tsx +++ b/app/src/pages/project/SessionsTableEmpty.tsx @@ -1,9 +1,45 @@ -import React from "react"; +import React, { ReactNode, useState } from "react"; import { css } from "@emotion/react"; -import { Flex } from "@arizeai/components"; +import { + Button, + Dialog, + DialogContainer, + Flex, + Icon, + Icons, + View, +} from "@arizeai/components"; +import { CodeLanguage, CodeLanguageRadioGroup } from "@phoenix/components/code"; + +import { PythonSessionsGuide } from "./PythonSessionsGuide"; +import { TypeScriptSessionsGuide } from "./TypeScriptSessionsGuide"; + +function SetupSessionsDialog() { + const [language, setLanguage] = useState("Python"); + return ( + + + + + + {language === "Python" ? ( + + ) : ( + + )} + + + ); +} export function SessionsTableEmpty() { + const [dialog, setDialog] = useState(null); + + const onGettingStartedClick = () => { + setDialog(); + }; + return ( @@ -16,9 +52,23 @@ export function SessionsTableEmpty() { > No sessions found for this project + + setDialog(null)} + isDismissable + type="slideOver" + > + {dialog} + ); } diff --git a/app/src/pages/project/TypeScriptSessionsGuide.tsx b/app/src/pages/project/TypeScriptSessionsGuide.tsx new file mode 100644 index 0000000000..66cc6bcb45 --- /dev/null +++ b/app/src/pages/project/TypeScriptSessionsGuide.tsx @@ -0,0 +1,115 @@ +import React from "react"; + +import { Heading, Text, View } from "@arizeai/components"; + +import { ExternalLink } from "@phoenix/components"; +import { CodeWrap, PythonBlockWithCopy } from "@phoenix/components/code"; +import { TypeScriptBlockWithCopy } from "@phoenix/components/code/TypeScriptBlockWithCopy"; + +const INSTALL_OPENINFERENCE_CORE_TYPESCRIPT = `npm install @arizeai/openinference-core --save`; + +const ADD_SESSION_ID_TYPESCRIPT = `import { trace } from "npm:@opentelemetry/api"; +import { SemanticConventions } from "npm:@arizeai/openinference-semantic-conventions"; +import { context } from "npm:@opentelemetry/api"; +import { setSession } from "npm:@arizeai/openinference-core"; + +const tracer = trace.getTracer("agent"); + +const client = new OpenAI({ + apiKey: process.env["OPENAI_API_KEY"], // This is the default and can be omitted +}); + +async function assistant(params: { + messages: { role: string; content: string }[]; + sessionId: string; +}) { + return tracer.startActiveSpan("agent", async (span: Span) => { + span.setAttribute(SemanticConventions.OPENINFERENCE_SPAN_KIND, "agent"); + span.setAttribute(SemanticConventions.SESSION_ID, params.sessionId); + span.setAttribute( + SemanticConventions.INPUT_VALUE, + messages[messages.length - 1].content, + ); + try { + // This is not strictly necessary but it helps propagate the session ID + // to all child spans + return context.with( + setSession(context.active(), { sessionId: params.sessionId }), + async () => { + // Calls within this block will generate spans with the session ID set + const chatCompletion = await client.chat.completions.create({ + messages: params.messages, + model: "gpt-3.5-turbo", + }); + const response = chatCompletion.choices[0].message; + span.setAttribute(SemanticConventions.OUTPUT_VALUE, response.content); + span.end(); + return response; + }, + ); + } catch (e) { + span.error(e); + } + }); +} + +const sessionId = crypto.randomUUID(); + +let messages = [{ role: "user", content: "hi! im Tim" }]; + +const res = await assistant({ + messages, + sessionId: sessionId, +}); + +messages = [res, { role: "assistant", content: "What is my name?" }]; + +await assistant({ + messages, + sessionId: sessionId, +}); +`; + +export function TypeScriptSessionsGuide() { + return ( +
+ + + Install Dependencies + + + + + Sessions are tracked via the OpenTelemetry attribute session.id + . The easiest way to get started with sessions is to use the + OpenInference instrumentation package. + + + + + + + + Add Session ID to your Traces + + + + + Below is an example of how to add session IDs to a simple OpenAI + application. + + + + + + + + For more information on how to use sessions, consult the{" "} + + documentation + + + +
+ ); +} diff --git a/cspell.json b/cspell.json index 0ac8a3cc89..f880d8fb7d 100644 --- a/cspell.json +++ b/cspell.json @@ -56,6 +56,7 @@ "rgba", "rowid", "seafoam", + "SEMRESATTRS", "sqlalchemy", "Starlette", "tanstack", diff --git a/js/examples/notebooks/tracing_openai_sessions_tutorial.ipynb b/js/examples/notebooks/tracing_openai_sessions_tutorial.ipynb new file mode 100644 index 0000000000..8e6e65a890 --- /dev/null +++ b/js/examples/notebooks/tracing_openai_sessions_tutorial.ipynb @@ -0,0 +1,196 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "

\n", + " \"phoenix\n", + "
\n", + "
\n", + " Docs\n", + " |\n", + " GitHub\n", + " |\n", + " Community\n", + "

\n", + "
\n", + "

Setting Up Sessions

\n", + "\n", + "A Session is a sequence of traces representing a single session (e.g. a session or a thread). Each response is represented as it's own trace, but these traces are linked together by being part of the same session.\n", + "To associate traces together, you need to pass in a special metadata key where the value is the unique identifier for that thread.\n", + "\n", + "In this tutorial we will setup sessions using OpenAI and OpenInference instrumentation.\n", + "\n", + "> Note: that this example requires the OPENAI_API_KEY environment variable to be set and assumes you are running the Phoenix server on localhost:6006." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "👀 OpenInference initialized\n" + ] + } + ], + "source": [ + "import {\n", + " NodeTracerProvider,\n", + " SimpleSpanProcessor,\n", + "} from \"npm:@opentelemetry/sdk-trace-node\";\n", + "import { Resource } from \"npm:@opentelemetry/resources\";\n", + "import { OTLPTraceExporter } from \"npm:@opentelemetry/exporter-trace-otlp-proto\";\n", + "import { SEMRESATTRS_PROJECT_NAME } from \"npm:@arizeai/openinference-semantic-conventions\";\n", + "import { diag, DiagConsoleLogger, DiagLogLevel } from \"npm:@opentelemetry/api\";\n", + "\n", + "// For troubleshooting, set the log level to DiagLogLevel.DEBUG\n", + "diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);\n", + "\n", + "const provider = new NodeTracerProvider({\n", + " resource: new Resource({\n", + " [SEMRESATTRS_PROJECT_NAME]: \"openai-node-sessions-example\",\n", + " }),\n", + "});\n", + "\n", + "provider.addSpanProcessor(\n", + " new SimpleSpanProcessor(\n", + " new OTLPTraceExporter({\n", + " url: \"http://localhost:6006/v1/traces\",\n", + " }),\n", + " ),\n", + ");\n", + "\n", + "provider.register();\n", + "\n", + "console.log(\"👀 OpenInference initialized\");" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import OpenAI from 'npm:openai';\n", + "import { OpenAIInstrumentation } from \"npm:@arizeai/openinference-instrumentation-openai\";\n", + "\n", + "const oaiInstrumentor = new OpenAIInstrumentation();\n", + "oaiInstrumentor.manuallyInstrument(OpenAI);" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{\n", + " role: \u001b[32m\"assistant\"\u001b[39m,\n", + " content: \u001b[32m\"Your name is Tim. How can I assist you further today?\"\u001b[39m,\n", + " refusal: \u001b[1mnull\u001b[22m\n", + "}" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import { trace } from \"npm:@opentelemetry/api\";\n", + "import { SemanticConventions } from \"npm:@arizeai/openinference-semantic-conventions\";\n", + "import { context } from \"npm:@opentelemetry/api\";\n", + "import { setSession } from \"npm:@arizeai/openinference-core\";\n", + "\n", + "const tracer = trace.getTracer(\"agent\");\n", + "\n", + "const client = new OpenAI({\n", + " apiKey: process.env[\"OPENAI_API_KEY\"], // This is the default and can be omitted\n", + "});\n", + "\n", + "async function assistant(params: {\n", + " messages: { role: string; content: string }[];\n", + " sessionId: string;\n", + "}) {\n", + " return tracer.startActiveSpan(\"agent\", async (span: Span) => {\n", + " span.setAttribute(SemanticConventions.OPENINFERENCE_SPAN_KIND, \"agent\");\n", + " span.setAttribute(SemanticConventions.SESSION_ID, params.sessionId);\n", + " span.setAttribute(\n", + " SemanticConventions.INPUT_VALUE,\n", + " messages[messages.length - 1].content,\n", + " );\n", + " try {\n", + " // This is not strictly necessary but it helps propagate the session ID\n", + " // to all child spans\n", + " return context.with(\n", + " setSession(context.active(), { sessionId: params.sessionId }),\n", + " async () => {\n", + " // Calls within this block will generate spans with the session ID set\n", + " const chatCompletion = await client.chat.completions.create({\n", + " messages: params.messages,\n", + " model: \"gpt-3.5-turbo\",\n", + " });\n", + " const response = chatCompletion.choices[0].message;\n", + " span.setAttribute(SemanticConventions.OUTPUT_VALUE, response.content);\n", + " span.end();\n", + " return response;\n", + " },\n", + " );\n", + " } catch (e) {\n", + " span.error(e);\n", + " }\n", + " });\n", + "}\n", + "\n", + "const sessionId = crypto.randomUUID();\n", + "\n", + "let messages = [{ role: \"user\", content: \"hi! im Tim\" }];\n", + "\n", + "const res = await assistant({\n", + " messages,\n", + " sessionId: sessionId,\n", + "});\n", + "\n", + "messages = [res, { role: \"assistant\", content: \"What is my name?\" }];\n", + "\n", + "await assistant({\n", + " messages,\n", + " sessionId: sessionId,\n", + "});\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Deno", + "language": "typescript", + "name": "deno" + }, + "language_info": { + "codemirror_mode": "typescript", + "file_extension": ".ts", + "mimetype": "text/x.typescript", + "name": "typescript", + "nbconvert_exporter": "script", + "pygments_lexer": "typescript", + "version": "5.6.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/tracing/openai_sessions_tutorial.ipynb b/tutorials/tracing/openai_sessions_tutorial.ipynb new file mode 100644 index 0000000000..d12c607922 --- /dev/null +++ b/tutorials/tracing/openai_sessions_tutorial.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "

\n", + " \"phoenix\n", + "
\n", + " Docs\n", + " |\n", + " GitHub\n", + " |\n", + " Community\n", + "

\n", + "
\n", + "

Setting up Sessions

\n", + "\n", + "A Session is a sequence of traces representing a single session (e.g. a session or a thread). Each response is represented as it's own trace, but these traces are linked together by being part of the same session.\n", + "To associate traces together, you need to pass in a special metadata key where the value is the unique identifier for that thread.\n", + "\n", + "In this tutorial we will setup sessions using OpenAI and OpenInference instrumentation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Install Dependencies and Import Libraries\n", + "\n", + "Install dependencies." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install \"openai>=1.0.0\" arize-phoenix jsonschema openinference-instrumentation-openai openinference-instrumentation opentelemetry-api opentelemetry-sdk openinference-semantic-conventions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure Your OpenAI API Key and Instantiate Your OpenAI Client\n", + "\n", + "Set your OpenAI API key if it is not already set as an environment variable." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from getpass import getpass\n", + "\n", + "from openai import OpenAI\n", + "\n", + "if not (openai_api_key := os.getenv(\"OPENAI_API_KEY\")):\n", + " openai_api_key = getpass(\"🔑 Enter your OpenAI API key: \")\n", + "client = OpenAI(api_key=openai_api_key)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Instrument Your OpenAI Client\n", + "\n", + "Instrument your OpenAI client with a tracer that emits telemetry data in OpenInference format. [OpenInference](https://arize-ai.github.io/open-inference-spec/trace/) is an open standard for capturing and storing LLM application traces that enables LLM applications to seamlessly integrate with LLM observability solutions such as Phoenix." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from openinference.instrumentation.openai import OpenAIInstrumentor\n", + "\n", + "from phoenix.otel import register\n", + "\n", + "tracer_provider = register(project_name=\"openai-sessions-example\")\n", + "OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run Phoenix in the Background\n", + "\n", + "Launch Phoenix as a background session to collect the trace data emitted by your instrumented OpenAI client. Note that Phoenix should be run in a container in a production environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import phoenix as px\n", + "\n", + "px.launch_app()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a bare-bones Agent\n", + "\n", + "Let's create the outline of an agent that will leverage OpenAI." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "\n", + "import openai\n", + "from openinference.instrumentation import using_session\n", + "from openinference.semconv.trace import SpanAttributes\n", + "from opentelemetry import trace\n", + "\n", + "client = openai.Client()\n", + "session_id = str(uuid.uuid4())\n", + "\n", + "tracer = trace.get_tracer(__name__)\n", + "\n", + "\n", + "@tracer.start_as_current_span(\n", + " name=\"agent\", attributes={SpanAttributes.OPENINFERENCE_SPAN_KIND: \"agent\"}\n", + ")\n", + "def assistant(\n", + " messages: list[dict],\n", + " session_id: str = str,\n", + "):\n", + " current_span = trace.get_current_span()\n", + " current_span.set_attribute(SpanAttributes.SESSION_ID, session_id)\n", + " current_span.set_attribute(SpanAttributes.INPUT_VALUE, messages[-1].get(\"content\"))\n", + "\n", + " # Propagate the session_id down to spans crated by the OpenAI instrumentation\n", + " # This is not strictly necessary, but it helps to correlate the spans to the same session\n", + " with using_session(session_id):\n", + " response = (\n", + " client.chat.completions.create(\n", + " model=\"gpt-3.5-turbo\",\n", + " messages=[{\"role\": \"system\", \"content\": \"You are a helpful assistant.\"}] + messages,\n", + " )\n", + " .choices[0]\n", + " .message\n", + " )\n", + "\n", + " current_span.set_attribute(SpanAttributes.OUTPUT_VALUE, response.content)\n", + " return response\n", + "\n", + "\n", + "messages = [{\"role\": \"user\", \"content\": \"hi! im bob\"}]\n", + "response = assistant(\n", + " messages,\n", + " session_id=session_id,\n", + ")\n", + "messages = messages + [response, {\"role\": \"user\", \"content\": \"what's my name?\"}]\n", + "response = assistant(\n", + " messages,\n", + " session_id=session_id,\n", + ")" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}