89 lines
3.4 KiB
Python
89 lines
3.4 KiB
Python
import inspect
|
||
import types
|
||
from openai import OpenAI
|
||
from .env import Environment
|
||
|
||
class ApiKeyMissingError(Exception):
|
||
pass
|
||
|
||
class Explainable:
|
||
API_KEY = None
|
||
PROMPT = """
|
||
Explain the following Python class code. Use these rules:
|
||
|
||
- Ignore the Explainable class – it's just an internal utility to add the `.explain()` method.
|
||
It is not relevant to the explanation as it just adds the `.explain()` method to the class.
|
||
Don't even mention it in the explanation. Only mention other inherited classes if they are relevant.
|
||
- Focus on the class that is being explained.
|
||
- Focus only on what the class and its methods do, practically.
|
||
- Do not explain basic Python concepts like `self`, indentation, or decorators.
|
||
- Do not guess the purpose or intent of the class — just describe what the code does.
|
||
- Do not make suggestions for improvement or style.
|
||
- Keep the explanation clear, minimal, and to-the-point.
|
||
- Your audience is a competent Python developer.
|
||
- Use simple language and avoid jargon.
|
||
- Be concise and avoid unnecessary detail.
|
||
- Provide the explanation in a single paragraph or more if needed.
|
||
"""
|
||
|
||
@classmethod
|
||
def use_env(cls, path: str = ".env"):
|
||
env = Environment(env_file_name=path)
|
||
Explainable.API_KEY = env.get("OPENAPI_KEY")
|
||
|
||
@classmethod
|
||
def explain(cls) -> str:
|
||
if cls.API_KEY is None:
|
||
raise ApiKeyMissingError("API key is missing. Please set it using `Explainable.use_env()` or by directly assigning `Explainable.API_KEY`.")
|
||
|
||
try:
|
||
code = inspect.getsource(cls)
|
||
|
||
client = OpenAI(api_key=cls.API_KEY)
|
||
response = client.chat.completions.create(
|
||
model="gpt-4",
|
||
messages=[
|
||
{"role": "system", "content": cls.PROMPT},
|
||
{"role": "user", "content": code}
|
||
]
|
||
)
|
||
return response.choices[0].message.content.strip()
|
||
except Exception as e:
|
||
return f"<could not get class source: {e}>"
|
||
|
||
def __init_subclass__(cls, **kwargs):
|
||
for name, obj in vars(cls).items():
|
||
if isinstance(obj, types.FunctionType):
|
||
setattr(cls, name, Explainable.wrap_with_explain(obj))
|
||
super().__init_subclass__(**kwargs)
|
||
|
||
@staticmethod
|
||
def wrap_with_explain(func):
|
||
def _with_explain(*args, **kwargs):
|
||
return func(*args, **kwargs)
|
||
|
||
_with_explain.__name__ = func.__name__
|
||
_with_explain.__doc__ = func.__doc__
|
||
|
||
def explain_func():
|
||
if Explainable.API_KEY is None:
|
||
raise ApiKeyMissingError("API key is missing. Please set it using `Explainable.use_env()` or by directly assigning `Explainable.API_KEY`.")
|
||
|
||
try:
|
||
code = inspect.getsource(func)
|
||
|
||
client = OpenAI(api_key=Explainable.API_KEY)
|
||
response = client.chat.completions.create(
|
||
model="gpt-4",
|
||
messages=[
|
||
{"role": "system", "content": Explainable.PROMPT},
|
||
{"role": "user", "content": code}
|
||
]
|
||
)
|
||
return response.choices[0].message.content.strip()
|
||
except Exception as e:
|
||
return f"<could not get function explanation: {e}>"
|
||
|
||
_with_explain.explain = explain_func
|
||
return _with_explain
|
||
|