Skip to content

Config

Allow us to easily read from and write to roboduck's config file.

Roboduck creates a config file at ~/.roboduck/config.yaml. This currently supports only two fields:

  • openai_api_key: See the Quickstart for setup help.

  • model_name (optional): Roboduck is configured to use gpt-3.5-turbo by default. This field lets you change that (e.g. to gpt-4). If present in the config file, this will take priority over any model_name field specified in a chat template (e.g. our default debug prompt template). You can view valid options with roboduck.available_models(). You can still override the config default by manually passing a value into a function, e.g. duck(model_name='gpt-4-32k').

You can manually edit your config file or use a command like roboduck.update_config(model_name='gpt-4'). Passing in a value of None (e.g. roboduck.update_config(model_name=None)) will delete that field from your config file.

Attributes

config_path = Path('~/.roboduck/config.yaml').expanduser() module-attribute

Functions

update_config(config_path=config_path, **kwargs)

Update roboduck config file with settings that persist for future sessions.

Other fields may be configurable here in the future, but as of v1 this should really only be used to set openai_api_key and/or model_name.

Parameters:

Name Type Description Default
config_path str or Path

Location of the roboduck config file.

config_path
kwargs any

Available fields include: - openai_api_key - model_name: name like 'gpt-3.5-turbo' that controls what model to use for completions. Model_name is resolved as follows: 1. kwargs explicitly passed in by user (e.g. duck(model_name='gpt-4') always override everything else. 2. if global config file (which this function updates) has a model_name, it is the next highest priority. 3. specific chat template (e.g. roboduck/prompts/chat/debug.yaml) model name is used if neither #1 or #2 are provided.

The reason for the global config taking priority over specific
templates is that we want to make it easy for a user to always use
a specific model that is not the roboduck default (i.e. without
having to pass in a model_name in every single duck() call). This
does come with the tradeoff of making it hard to define both a
different default model AND a custom prompt template with yet
another model, but that seems like a less common use case.

Passing in a value of None indicates that the corresponding key
should be deleted from the config file, NOT that we will explicitly
set {field}: None.
{}
Source code in lib/roboduck/config.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def update_config(config_path=config_path, **kwargs):
    """Update roboduck config file with settings that persist for future
    sessions.

    Other fields may be configurable here in the future, but as of v1 this
    should really only be used to set openai_api_key and/or model_name.

    Parameters
    ----------
    config_path : str or Path
        Location of the roboduck config file.
    kwargs : any
        Available fields include:
            - openai_api_key
            - model_name: name like 'gpt-3.5-turbo' that controls what model
            to use for completions. Model_name is resolved as follows:
            1. kwargs explicitly passed in by user (e.g.
            `duck(model_name='gpt-4')` always override everything else.
            2. if global config file (which this function updates) has a
            model_name, it is the next highest priority.
            3. specific chat template (e.g. roboduck/prompts/chat/debug.yaml)
            model name is used if neither #1 or #2 are provided.

            The reason for the global config taking priority over specific
            templates is that we want to make it easy for a user to always use
            a specific model that is not the roboduck default (i.e. without
            having to pass in a model_name in every single duck() call). This
            does come with the tradeoff of making it hard to define both a
            different default model AND a custom prompt template with yet
            another model, but that seems like a less common use case.

            Passing in a value of None indicates that the corresponding key
            should be deleted from the config file, NOT that we will explicitly
            set {field}: None.
    """
    recognized_keys = {'openai_api_key', 'model_name'}
    if set(kwargs) - recognized_keys:
        warnings.warn(f'You are setting unrecognized key(s): '
                      f'{set(kwargs) - recognized_keys}.')
    update_yaml(path=config_path, delete_if_none=True, **kwargs)

load_config(config_path=config_path)

Load roboduck config.

Parameters:

Name Type Description Default
config_path str or Path

Location of the roboduck config file.

config_path

Returns:

Type Description
dict
Source code in lib/roboduck/config.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def load_config(config_path=config_path):
    """Load roboduck config.

    Parameters
    ----------
    config_path : str or Path
        Location of the roboduck config file.

    Returns
    -------
    dict
    """
    config_path = Path(config_path)
    if not config_path.is_file():
        config_path.parent.mkdir(parents=True, exist_ok=True)
        config_path.touch()
    return load_yaml(path=config_path)

apply_config_defaults(chat_kwargs, template_only, config_path=config_path)

Help resolve model_name in place. Recall we prioritize sources in this order:

  1. value a user specified explicitly, e.g. Chat(..., model_name='gpt-4').
  2. value specified in roboduck config file
  3. value specified in a prompt template (can be native to roboduck or user-defined)

Parameters:

Name Type Description Default
chat_kwargs dict

Kwargs to pass to our langchain.chat.Chat constructor. May include a model_name str field.

required
template_only bool

Specifies whether chat_kwargs are passed in directly from a prompt template (template_only=True) or include kwargs that a user passed in explicitly (template_only=False).

required
config_path str or Path

Location of the roboduck config file.

config_path

Returns:

Type Description
None

Update happens in place (if at all).

Source code in lib/roboduck/config.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def apply_config_defaults(chat_kwargs, template_only, config_path=config_path):
    """Help resolve model_name in place. Recall we prioritize sources in this
    order:

    1. value a user specified explicitly, e.g. Chat(..., model_name='gpt-4').
    2. value specified in roboduck config file
    3. value specified in a prompt template (can be native to roboduck or
    user-defined)

    Parameters
    ----------
    chat_kwargs : dict
        Kwargs to pass to our langchain.chat.Chat constructor. May include a
        model_name str field.
    template_only : bool
        Specifies whether chat_kwargs are passed in directly from a prompt
        template (template_only=True) or include kwargs that a user passed in
        explicitly (template_only=False).
    config_path : str or Path
        Location of the roboduck config file.

    Returns
    -------
    None
        Update happens in place (if at all).
    """
    # If both are true, it means the user has already explicitly passed in a
    # model name so we should NOT override it with our config default.
    if 'model_name' in chat_kwargs and not template_only:
        return

    cfg = load_config(config_path=config_path)
    config_model_name = cfg.get('model_name', '')
    # We also don't want to add something like model_name='' if no default is
    # specified in the config. Better to revert to langchain class default than
    # set it to None, which could break things.
    if config_model_name:
        chat_kwargs['model_name'] = config_model_name

set_openai_api_key(key=None, config_path=config_path, strict=False, update_config_=False)

Set OPENAI_API_KEY environment variable for langchain.

Parameters:

Name Type Description Default
key str or None

Optionally pass in openai api key (str). If not provided, we check the config path and try to load a key. If it is provided, we don't check config_path.

None
config_path str or Path

Local yaml file containing the field openai_api_key. We only try to load the key from it if key is not provided. We do not write to this file by default.

config_path
strict bool

Determines what happens when key is None and config path does not exist. Strict=True raises a runtime error, False just warns user.

False
update_config_ bool

If True, we update the yaml config file with that api key.

False
Source code in lib/roboduck/config.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def set_openai_api_key(key=None, config_path=config_path,
                       strict=False, update_config_=False):
    """Set OPENAI_API_KEY environment variable for langchain.

    Parameters
    ----------
    key : str or None
        Optionally pass in openai api key (str). If not provided, we check the
        config path and try to load a key. If it is provided, we don't check
        config_path.
    config_path : str or Path
        Local yaml file containing the field openai_api_key. We only try to
        load the key from it if `key` is not provided. We do not write to
        this file by default.
    strict : bool
        Determines what happens when key is None and config path does not
        exist. Strict=True raises a runtime error, False just warns user.
    update_config_ : bool
        If True, we update the yaml config file with that api key.
    """
    config_path = Path(config_path).expanduser()
    var_name = 'OPENAI_API_KEY'
    key = key or os.environ.get(var_name)
    if not key:
        try:
            data = load_config(config_path)
            key = data[var_name.lower()]
        except Exception as e:
            msg = 'Openai api key must either be passed into this function ' \
                  f'or stored in {config_path} with field name ' \
                  f'{var_name.lower()}. No key found.'
            if strict:
                raise RuntimeError(msg)
            else:
                warnings.warn(msg + ' Not raising error because strict=False, '
                              'but openai API will not be available until you '
                              'make key available via one of these methods.')
                return
    if not key.startswith('sk-'):
        partially_redacted_key = key[:4] + '*'*max(0, len(key) - 4)
        warnings.warn(
            f'Your openai api key ({partially_redacted_key}) looks unusual. '
            f'Are you sure it\'s correct? (Key is partially redacted in '
            f'warning to err on the side of caution.)'
        )
    os.environ[var_name] = key
    if update_config_:
        update_config(config_path, **{var_name.lower(): key})