Get the callable method from module, supporting 'Class.method' syntax.
(module, method_name)
| 104 | |
| 105 | |
| 106 | def get_callable(module, method_name): |
| 107 | """Get the callable method from module, supporting 'Class.method' syntax.""" |
| 108 | try: |
| 109 | if "." in method_name: |
| 110 | class_name, classmethod_name = method_name.split(".", 1) |
| 111 | cls = getattr(module, class_name) |
| 112 | return getattr(cls, classmethod_name) |
| 113 | else: |
| 114 | return getattr(module, method_name) |
| 115 | except AttributeError as e: |
| 116 | # Provide helpful error message when function not found |
| 117 | available_funcs = [ |
| 118 | name |
| 119 | for name in dir(module) |
| 120 | if callable(getattr(module, name, None)) and not name.startswith("_") |
| 121 | ] |
| 122 | |
| 123 | error_lines = [ |
| 124 | f"Function '{method_name}' not found in module '{module.__name__}'", |
| 125 | "", |
| 126 | f"Available functions in your module: {', '.join(available_funcs) if available_funcs else '(none)'}", |
| 127 | "", |
| 128 | "Expected function names for promptfoo:", |
| 129 | " • call_api(prompt, options, context) - for chat/completions", |
| 130 | " • call_embedding_api(prompt, options) - for embeddings", |
| 131 | " • call_classification_api(prompt, options) - for classification", |
| 132 | "", |
| 133 | ] |
| 134 | |
| 135 | # Fuzzy match suggestion |
| 136 | if available_funcs: |
| 137 | # Check for common mistakes |
| 138 | method_lower = method_name.lower() |
| 139 | for func in available_funcs: |
| 140 | func_lower = func.lower() |
| 141 | # Check if user used 'get_' instead of 'call_' |
| 142 | if method_lower.replace("call_", "") == func_lower.replace("get_", ""): |
| 143 | error_lines.append( |
| 144 | f"💡 Did you mean to rename '{func}' to '{method_name}'?" |
| 145 | ) |
| 146 | break |
| 147 | # Check if function name is similar (missing 'call_' prefix) |
| 148 | elif method_lower.replace("call_", "") == func_lower: |
| 149 | error_lines.append( |
| 150 | f"💡 Did you mean to rename '{func}' to '{method_name}'?" |
| 151 | ) |
| 152 | break |
| 153 | # Check if it's just a typo (Levenshtein-like) |
| 154 | elif ( |
| 155 | len(set(method_lower) & set(func_lower)) > len(method_lower) * 0.6 |
| 156 | and abs(len(method_lower) - len(func_lower)) <= 3 |
| 157 | ): |
| 158 | error_lines.append(f"💡 Did you mean '{func}'?") |
| 159 | break |
| 160 | |
| 161 | error_lines.append( |
| 162 | "\nSee https://www.promptfoo.dev/docs/providers/python/ for details." |
| 163 | ) |
no test coverage detected
searching dependent graphs…