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

Deno Serverless compatibility & providing settings object to init #3380

Open
janzheng opened this issue Jan 11, 2025 · 3 comments
Open

Deno Serverless compatibility & providing settings object to init #3380

janzheng opened this issue Jan 11, 2025 · 3 comments

Comments

@janzheng
Copy link

Hi everyone,

I had trouble getting weave working with Deno Deploy since it doesn't allow for fs so it breaks when looking for the .netrc file. Instead, I made a change to supply the API key w/ a settings object instead to override .netrc.

I'd be happy to do a PR, and I currently published a working version here: https://github.com/janzheng/weave-lite

Here's the code:

import {Api as TraceServerApi} from './generated/traceServerApi';
import {Settings} from './settings';
import {defaultHost, getUrls, setGlobalDomain} from './urls';
import {ConcurrencyLimiter} from './utils/concurrencyLimit';
// import {Netrc} from './utils/netrc';
import {createFetchWithRetry} from './utils/retry';
import {getWandbConfigs} from './wandb/settings';
import {WandbServerApi} from './wandb/wandbServerApi';
import {CallStackEntry, WeaveClient} from './weaveClient';

// Global client instance
export let globalClient: WeaveClient | null = null;

// Add new interface for initialization options
interface InitOptions {
  project?: string;
  apiKey?: string;
  host?: string;
  settings?: Settings;
}

interface LoginOptions {
  apiKey: string;
  host?: string;
}

/**
 * Log in to Weights & Biases (W&B) using the provided API key.
 *
 * @param {string} apiKey - Your W&B API key.
 * @param {string} [host] - (Optional) The host name (usually only needed if you're using a custom W&B server).
 * @throws {Error} If the API key is not specified or if the connection to the weave trace server cannot be verified.
 */
export async function login(apiKeyOrOptions: string | LoginOptions) {
  let apiKey: string;
  let host: string = defaultHost;

  // Parse input parameters
  if (typeof apiKeyOrOptions === 'string') {
    apiKey = apiKeyOrOptions;
  } else {
    apiKey = apiKeyOrOptions.apiKey;
    host = apiKeyOrOptions.host || defaultHost;
  }

  if (!apiKey.trim()) {
    throw new Error('API key is required for login. Please provide a valid API key.');
  }

  const {traceBaseUrl} = getUrls(host);

  // Test the connection to the traceServerApi
  const testTraceServerApi = new TraceServerApi({
    baseUrl: traceBaseUrl,
    baseApiParams: {
      headers: {
        'User-Agent': `W&B Weave JS Client ${process.env.VERSION || 'unknown'}`,
        Authorization: `Basic ${Buffer.from(`api:${apiKey}`).toString('base64')}`,
      },
    },
  });

  try {
    await testTraceServerApi.health.readRootHealthGet({});
  } catch (error) {
    throw new Error(
      'Unable to verify connection to the weave trace server with given API Key'
    );
  }

  // Store credentials in memory
  globalClient = new WeaveClient(
    testTraceServerApi,
    new WandbServerApi(getUrls(host).baseUrl, apiKey),
    '', // Project ID will be set during init
    undefined
  );
  console.log(`Successfully logged in to ${host}`);
}

/**
 * Initialize the Weave client, which is required for weave tracing to work.
 *
 * @param projectOrOptions - Either a project string (entity/project) or initialization options
 * @param settings - (Optional) Weave tracing settings when using project string format
 * @returns A promise that resolves to the initialized Weave client.
 * @throws {Error} If the initialization fails
 */
export async function init(
  projectOrOptions: string | InitOptions,
  settings?: Settings
): Promise<WeaveClient> {
  let project: string;
  let apiKey: string | undefined;
  let host: string | undefined;
  let configSettings: Settings | undefined;

  // Parse input parameters
  if (typeof projectOrOptions === 'string') {
    project = projectOrOptions;
    configSettings = settings;
  } else {
    if (!projectOrOptions.project) {
      throw new Error('Project name is required');
    }
    project = projectOrOptions.project;
    apiKey = projectOrOptions.apiKey;
    host = projectOrOptions.host;
    configSettings = projectOrOptions.settings;
  }

  // If API key and host are provided, use them directly
  // Otherwise fall back to netrc/environment configs
  let configs;
  if (apiKey || host) {
    const {baseUrl, traceBaseUrl, domain} = getUrls(host);
    configs = {apiKey, baseUrl, traceBaseUrl, domain, resolvedHost: host};
  } else {
    configs = getWandbConfigs();
  }

  try {
    if (!configs.apiKey) {
      throw new Error('API key is required. Please provide via init options, environment variable, or netrc file.');
    }
    const wandbServerApi = new WandbServerApi(configs.baseUrl, configs.apiKey);

    let entityName: string | undefined;
    let projectName: string;
    if (project.includes('/')) {
      [entityName, projectName] = project.split('/');
    } else {
      entityName = await wandbServerApi.defaultEntityName();
      projectName = project;
    }
    const projectId = `${entityName}/${projectName}`;

    const retryFetch = createFetchWithRetry({
      baseDelay: 1000,
      maxDelay: 5 * 60 * 1000, // 5 minutes
      maxRetryTime: 12 * 60 * 60 * 1000, // 12 hours
      retryOnStatus: (status: number) =>
        status === 429 || (status >= 500 && status < 600),
    });
    const concurrencyLimiter = new ConcurrencyLimiter(20);
    const concurrencyLimitedFetch = concurrencyLimiter.limitFunction(
      async (...fetchParams: Parameters<typeof fetch>) => {
        const result = await retryFetch(...fetchParams);
        // Useful for debugging
        // console.log(`Active: ${concurrencyLimiter.active} Pending: ${concurrencyLimiter.pending}`);
        return result;
      }
    );

    const traceServerApi = new TraceServerApi({
      baseUrl: configs.traceBaseUrl,
      baseApiParams: {
        headers: {
          'User-Agent': `W&B Weave JS Client ${process.env.VERSION || 'unknown'}`,
          Authorization: `Basic ${Buffer.from(`api:${configs.apiKey}`).toString('base64')}`,
        },
      },
      customFetch: concurrencyLimitedFetch,
    });

    const client = new WeaveClient(
      traceServerApi,
      wandbServerApi,
      projectId,
      configSettings
    );
    setGlobalClient(client);
    setGlobalDomain(configs.domain);
    console.log(`Initializing project: ${projectId}`);
    return client;
  } catch (error) {
    console.error('Error during initialization:', error);
    throw error;
  }
}

export function requireCurrentCallStackEntry(): CallStackEntry {
  const client = getGlobalClient();
  if (!client) {
    throw new Error('Weave client not initialized');
  }
  const callStackEntry = client.getCallStack().peek();
  if (!callStackEntry) {
    throw new Error('No current call stack entry');
  }
  return callStackEntry;
}

export function requireCurrentChildSummary(): {[key: string]: any} {
  const callStackEntry = requireCurrentCallStackEntry();
  return callStackEntry.childSummary;
}

export function getGlobalClient(): WeaveClient | null {
  return globalClient;
}

export function requireGlobalClient(): WeaveClient {
  const client = getGlobalClient();
  if (!client) {
    throw new Error('Weave client not initialized');
  }
  return client;
}

export function setGlobalClient(client: WeaveClient) {
  globalClient = client;
}

@jamie-rasmussen
Copy link
Collaborator

Thank you for reporting this! It looks like we have some internal task tracking related to fixing netrc handling in our TypeScript SDK. Leaving these links for our team's future reference.

Internal Slack: https://weightsandbiases.slack.com/archives/C03BSTEBD7F/p1736627979091199
Internal Jira: https://wandb.atlassian.net/browse/WB-22698

@jdawud
Copy link

jdawud commented Jan 15, 2025

I'm using a Supabase Edge Function to relay my API calls and keep my OpenAI API key off of the client side. I tried yesterday to set it up and couldn't get it working since Supabase uses Deno. I would love to be able to add Weave to this function soon!

@janzheng
Copy link
Author

@jdawud in the meantime you can use my "fork" of the client, weave-lite: https://github.com/janzheng/weave-lite
(it's not actually a direct fork of weave, since this monorepo is really large; this is only the client)

You can install it via npm here; if you use deno you'd use w/ npm:@yawnxyz/weave
https://www.npmjs.com/package/@yawnxyz/weave

I removed the login() feature, but everything else should be the same. Note that it doesn't work on services like val.town which don't support streaming / readablestream

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

No branches or pull requests

3 participants