Server Script API Call to OpenAI Failing with ImportError on Private Bench

Summary

I’m trying to create a healthcare chatbot (Aira) that calls the OpenAI API from a Server Script, but I’m getting ImportError: __import__ not found even after using Frappe-safe methods.

Environment

  • Site: airtook.com
  • Bench Type: Private Bench (recently migrated from shared)
  • Frappe Version: [v16 Nightly]
  • App: Healthcare (Marley Health module)
  • Use Case: AI health assistant for telemedicine platform in Nigeria

What I’m Trying to Do

Create a Server Script that:

  1. Accepts patient health questions via frappe.call()
  2. Calls OpenAI ChatGPT API (gpt-4o-mini)
  3. Returns AI response to the frontend
  4. Logs conversations for medical records

Server Script Configuration

Script Type: API
API Method: airtook.chat_with_aira
Allow Guest: No (unchecked)
Enabled: Yes (checked)

The Code

@frappe.whitelist()
def chat_with_aira(message, conversation_history=None):
    try:
        # Security check
        if frappe.session.user == 'Guest':
            return {'success': False, 'error': 'Please login'}
        
        # Get API key from custom DocType
        settings = frappe.get_doc('AirTook API Settings', 'AirTook API Settings')
        api_key = settings.get_password('openai_api_key')
        
        if not api_key:
            return {'success': False, 'error': 'API not configured'}
        
        # Build messages
        messages = [
            {"role": "system", "content": "You are a health assistant."},
            {"role": "user", "content": message}
        ]
        
        # Call OpenAI using frappe's integration util
        from frappe.integrations.utils import make_post_request
        
        url = "https://api.openai.com/v1/chat/completions"
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        
        # Using frappe.as_json (not json.dumps)
        payload_str = frappe.as_json({
            "model": "gpt-4o-mini",
            "messages": messages,
            "max_tokens": 300
        })
        
        # Make request
        response_data = make_post_request(url, headers=headers, data=payload_str)
        
        if not response_data:
            return {'success': False, 'error': 'No response from API'}
        
        ai_response = response_data.get('choices', [{}])[0].get('message', {}).get('content', '')
        
        return {
            'success': True,
            'response': ai_response
        }
        
    except Exception as e:
        frappe.log_error(title="Aira Error", message=str(e))
        return {'success': False, 'error': str(e)}
```

## Frontend Call (Web Page)
```javascript
frappe.call({
    method: 'airtook.chat_with_aira',
    args: { message: 'I have a headache' },
    callback: r => console.log('Response:', r.message),
    error: e => console.error('Error:', e)
});
```

## Error I'm Getting
**Browser Console:**
```
POST https://airtook.com/ 500 (Internal Server Error)

Traceback (most recent call last):
  File "apps/frappe/frappe/handler.py", line 90, in run_server_script
    response = frappe.get_doc("Server Script", server_script).execute_method()
  File "apps/frappe/frappe/core/doctype/server_script/server_script.py", line 275, in execute_api_server_script
    _globals, _locals = safe_exec(script.script, script_filename=script.name)
  File "apps/frappe/frappe/utils/safe_exec.py", line 120, in safe_exec
    exec(_compile_code(script, filename=filename), exec_globals, _locals)
  File "<serverscript>: aira_chatgpt_integration", line 12, in <module>
ImportError: __import__ not found
```

## What I’ve Already Tried :white_check_mark: **No `import json`** - Using `frappe.parse_json()` and `frappe.as_json()` instead :white_check_mark: **No `import requests`** - Using `frappe.integrations.utils.make_post_request` :white_check_mark: **Verified script is Enabled** :white_check_mark: **API Method name matches** in `frappe.call()` :white_check_mark: **Created custom DocType** for storing API keys securely :white_check_mark: **Tested on simple Server Script** - Same error

## Questions

1. **Is `make_post_request` the correct way** to call external APIs from Server Scripts?

2. **Are there additional imports or libraries** I need to add to my bench?

3. **What are ALL the allowed imports** in the `safe_exec` sandbox?

4. **Is there a better pattern** for calling external REST APIs from Frappe?

5. **Should I use a different approach entirely?** (Custom API endpoint in app, Client Script, etc.) ## Context: Why Server Script?

Hi @etukenbx

import is not allowed on server script …

Use directly frappe.make_post_request instead.

See https://docs.frappe.io/framework/user/en/desk/scripting/script-api (API section)

Hope this helps.

I keep getting "method_name is not a function" errors no matter what I name it.

Setup:

  • Frappe v16 Nightly

  • Private bench on airtook.com

  • Server Script Type: API

  • Enabled: :white_check_mark: Yes

  • Allow Guest: :cross_mark: No

What I’ve tried:

  1. API Method: airtook.chat_with_aira with function def chat_with_aira() → Error: “airtook.chat_with_aira” is not a function

  2. API Method: get_reaction with function def get_reaction() → Error: “frappe.get_reaction” is not a function

  3. API Method: frappe.get_reaction with function def get_reaction() → Error: “frappe.get_reaction” is not a function

Server Script Code:

python

def chat_with_aira(message, conversation_history=None):
    try:
        # Using frappe.make_post_request (no imports as per docs)
        api_key = get_openai_key()
        
        url = "https://api.openai.com/v1/chat/completions"
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        
        payload = {
            "model": "gpt-4o-mini",
            "messages": [{"role": "user", "content": message}],
            "max_tokens": 300
        }
        
        response_data = frappe.make_post_request(
            url=url,
            headers=headers,
            data=frappe.as_json(payload)
        )
        
        frappe.response['message'] = {
            'success': True,
            'response': response_data.get('choices', [{}])[0].get('message', {}).get('content', '')
        }
        
    except Exception as e:
        frappe.log_error(title="Aira Error", message=str(e))
        frappe.response['message'] = {'success': False, 'error': str(e)}

Frontend Call:

javascript

frappe.call({
    method: 'airtook.chat_with_aira',
    args: { message: 'test' },
    callback: (r) => console.log(r.message)
});

Questions:

  1. What is the correct format for API Method in Server Scripts?

  2. Does the function name need to match something specific?

  3. Is there a server restart or cache clear needed after creating Server Scripts?

  4. Are there any logs I can check to see if the Server Script is even registered?

The Server Script itself has no errors when I save it, but the frontend can’t find it at all.

Any help appreciated! :folded_hands:

I’ve confirmed the Server Script is enabled and saving without errors, but when called via frappe.call(), the response object is completely empty:

javascript

auto—arduinobashccppcsharpcssdiffgographqlhtmlinijavajavascriptjsonkotlinlessluamakefilemarkdownobjectivecperlphpphp-templateplaintextpythonpython-replrrubyrustscssshellsqlswifttypescriptvbnetwasmxmlyaml

// Console output
r: {}
r.message: undefined
All keys: []

What I’ve tried:

  1. Using frappe.response['message'] = {...} :cross_mark:

  2. Using return {...} :cross_mark:

  3. Using frappe.response.update({"message": {...}}) :cross_mark:

  4. Multiple different API Method names :cross_mark:

All methods result in empty response objects, even with simple test code like:

python

auto—arduinobashccppcsharpcssdiffgographqlhtmlinijavajavascriptjsonkotlinlessluamakefilemarkdownobjectivecperlphpphp-templateplaintextpythonpython-replrrubyrustscssshellsqlswifttypescriptvbnetwasmxmlyaml

def ping(message):
    frappe.response.update({"message": {"success": True, "test": "hello"}})

Environment:

  • Frappe v16 Nightly

  • Private bench on Frappe Cloud (airtook.com)

  • Recently migrated from shared to private bench

Question: Are there any restrictions or additional configurations needed for Server Scripts (API type) to work on Frappe Cloud private benches?

Alternative: Should I create a Python API file instead? If so, can Frappe Cloud support help create this file:

  • Path: /home/frappe/frappe-bench/apps/airtook/airtook/api.py

  • With a simple @frappe.whitelist() function

Any guidance appreciated! :folded_hands: