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 · 1 comment
Open

Deno Serverless compatibility & providing settings object to init #3380

janzheng opened this issue Jan 11, 2025 · 1 comment

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

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

2 participants