Repository: promptfoo/promptfoo
Issue: Discrepancy between Assertion python file documentation and the actual implementation
Root cause: Algorithmic Error

**Describe the bug**
From this [documentation page](https://www.promptfoo.dev/docs/configuration/expected-outputs/python/#external-py), it seems quite straightforward to read config parameters passed to an external python script for evaluations. Unfortunately the context object does not seem to follow the structure described in the example code.


**To Reproduce**
a config with the following assert:
```
    assert:
      - type: python
        value: file://eval_script.py
        config:
          minOutputLength: 10
          maxOutputLength: 10000000
 ```

the eval_script.py:
```
from typing import Any, Dict, TypedDict, Union
import json

def get_assert(output: str, context) -> Union[bool, float, Dict[str, Any]]:
    print('Prompt:', context['prompt'])

    min_len = context['config']['minOutputLength']
    max_len = context['config']['maxOutputLength']

    if len(output) > max_len or len(output) < min_len:
        return {
            'pass': False,
            'score': 0.0,
            'reason': 'Length is not good!',
        }

    # This return is an example GradingResult dict
    return {
      'pass': True,
      'score': 0.6,
      'reason': 'Looks good to me',
    }
```
according to the documentation page this should work, instead a key error is raised.
The actual config parameters seems to be accessible like follows:
```
min_len = context['test'][index_of_the_text]['config']['minOutputLength']
```

**Expected behavior**
I should be able to get it just as explained in the documentation, as follows:
```
min_len = context['config']['minOutputLength']
```

Thank you

Thanks for reporting this @DarioBernardo! Relates to https://github.com/promptfoo/promptfoo/issues/1701

@vedantr would you mind taking a look at this if you have time? Don't worry if not. 

Will do! I'll submit a PR to fix.

I’m really excited about this functionality. It’s going to unlock the ability to use the same Python script across multiple assertions.

Once it’s working as shown in the examples, I think it would be beneficial to adjust the [Assertion Properties](https://www.promptfoo.dev/docs/configuration/expected-outputs/#assertion-properties) to include the config field.

+1 for this. Any updates? 

I'm trying to make universal Python assert that I can parametrize via the assert config, but without this working it's probably not possible.

Hello,
I've identified the root cause of the discrepancy between the documentation and implementation. The issue occurs because of how circular references are handled during JSON serialization when passing the context to Python scripts. 

Here's what's happening:

**Root Cause**: In the current implementation, the _assertion.config_ object is directly spread into the context. When this context is later serialized using _safeJsonStringify_, the circular reference detection drops the config because it's already referenced through the test object.
**Solution**: A possible solution is to create a deep copy of the assertion.config object before adding it to the context. This breaks the reference link and prevents the circular reference detection from dropping it during serialization.

```
// Original problematic code in src/assertions/index.ts
const context: AssertionValueFunctionContext = {
  prompt,
  vars: test.vars || {},
  test,
  logProbs,
  provider,
  providerResponse,
  ...(assertion.config ? { config: assertion.config } : {}),
};

// Fixed version that deep clones the config to avoid circular references
const context: AssertionValueFunctionContext = {
  prompt,
  vars: test.vars || {},
  test,
  logProbs,
  provider,
  providerResponse,
  ...(assertion.config ? { config: structuredClone(assertion.config) } : {}),
};
```


Thanks @kmeshcheryakov, can't believe I didn't think of that solution🤦. Suggestion is much appreciated and the PR has already been submitted.

@DarioBernardo, @kmeshcheryakov and @hodanajan, this was addressed in the [0.103.4](https://github.com/promptfoo/promptfoo/releases/tag/0.103.4) release. The cleanup/suggested fix should be in the next release of promptfoo. 

Thanks again @kmeshcheryakov for finding the issue, much appreciated 👍.

Closing this issue for now, please re-open if it's not addressed.