Skip to content

Commit 74c0157

Browse files
committed
[GR-62548] Better langchain interop.
PullRequest: graalpython/3701
2 parents c7b03ac + 4af8cfd commit 74c0157

File tree

17 files changed

+262
-19
lines changed

17 files changed

+262
-19
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
55

66
## Version 25.0.0
77
* `dir(foreign_object)` now returns both foreign methods and Python methods (it used to return only foreign methods).
8+
* Support `__name__`, `__doc__`, `__text_signature__` fields on foreign executables to serve as their proper counterparts on the Python side. This is useful to, for example, use Java functional interfaces in lieu of Python functions for things like LangChain's `@tool` annotation that want to inspect the underlying function.
89

910
## Version 24.2.0
1011
* Updated developer metadata of Maven artifacts.

graalpython/com.oracle.graal.python.cext/src/object.c

+18-4
Original file line numberDiff line numberDiff line change
@@ -2369,6 +2369,15 @@ _PyTrash_cond(PyObject *op, destructor dealloc)
23692369
return Py_TYPE(op)->tp_dealloc == dealloc;
23702370
}
23712371

2372+
// Begin GraalPy change
2373+
#if ((__linux__ && __GNU_LIBRARY__) || __APPLE__)
2374+
#include <execinfo.h>
2375+
static void bt() {
2376+
void* stack[8];
2377+
backtrace_symbols_fd(stack, backtrace(stack, 8), stderr);
2378+
}
2379+
#endif
2380+
// End GraalPy change
23722381

23732382
void _Py_NO_RETURN
23742383
_PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
@@ -2394,14 +2403,22 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
23942403
fprintf(stderr, "\n");
23952404
fflush(stderr);
23962405

2397-
#if 0 // GraalPy change
2406+
// Begin GraalPy change
2407+
#if ((__linux__ && __GNU_LIBRARY__) || __APPLE__)
2408+
fprintf(stderr, "Native backtrace:\n");
2409+
bt();
2410+
fflush(stderr);
2411+
#endif
2412+
// End GraalPy change
2413+
23982414
if (_PyObject_IsFreed(obj)) {
23992415
/* It seems like the object memory has been freed:
24002416
don't access it to prevent a segmentation fault. */
24012417
fprintf(stderr, "<object at %p is freed>\n", obj);
24022418
fflush(stderr);
24032419
}
24042420
else {
2421+
#if 0 // GraalPy change
24052422
/* Display the traceback where the object has been allocated.
24062423
Do it before dumping repr(obj), since repr() is more likely
24072424
to crash than dumping the traceback. */
@@ -2418,14 +2435,11 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
24182435
#endif // GraalPy change
24192436
/* This might succeed or fail, but we're about to abort, so at least
24202437
try to provide any extra info we can: */
2421-
if (obj) // GraalPy change, guard call to _PyObject_Dump
24222438
_PyObject_Dump(obj);
24232439

2424-
#if 0 // GraalPy change
24252440
fprintf(stderr, "\n");
24262441
fflush(stderr);
24272442
}
2428-
#endif // GraalPy change
24292443

24302444
Py_FatalError("_PyObject_AssertFailed");
24312445
}

graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/interop/JavaInteropTest.java

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -839,6 +839,46 @@ class Bar(Foo):
839839
assertEquals(object.getMetaSimpleName(), "object");
840840
assertFalse(object.hasMetaParents());
841841
}
842+
843+
public static class MyFunction implements java.util.function.Function<String, String> {
844+
public String __text_signature__ = "(string) -> str";
845+
846+
@Override
847+
public String apply(String s) {
848+
return s;
849+
}
850+
}
851+
852+
public static class MyFunctionWithCustomName implements java.util.function.Function<String, String> {
853+
public String __text_signature__ = "(string) -> str";
854+
public String __name__ = "myfunc";
855+
856+
@Override
857+
public String apply(String s) {
858+
return s;
859+
}
860+
}
861+
862+
public static class MyFunctionWithIncorrectSignature implements java.util.function.Function<String, String> {
863+
public String __text_signature__ = "[I;java.lang.String;";
864+
865+
@Override
866+
public String apply(String s) {
867+
return s;
868+
}
869+
}
870+
871+
@Test
872+
public void javaExecutablesAsPythonFunctions() {
873+
Value inspectSignature = context.eval("python", "import inspect; inspect.signature");
874+
assertEquals("<Signature (string)>", inspectSignature.execute(new MyFunction()).toString());
875+
876+
Value signature = context.eval("python", "lambda f: f.__text_signature__");
877+
assertEquals(new MyFunctionWithIncorrectSignature().__text_signature__, signature.execute(new MyFunctionWithIncorrectSignature()).asString());
878+
879+
Value name = context.eval("python", "lambda f: f.__name__");
880+
assertEquals(new MyFunctionWithCustomName().__name__, name.execute(new MyFunctionWithCustomName()).asString());
881+
}
842882
}
843883

844884
@RunWith(Parameterized.class)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextObjectBuiltins.java

+10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Pointer;
5050
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObject;
5151
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectConstPtr;
52+
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectRawPointer;
5253
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectTransfer;
5354
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectWrapper;
5455
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyThreadState;
@@ -599,6 +600,15 @@ static PNone set(PSequence obj, long size,
599600
}
600601
}
601602

603+
@CApiBuiltin(ret = Int, args = {PyObjectRawPointer}, call = Direct)
604+
abstract static class _PyObject_IsFreed extends CApiUnaryBuiltinNode {
605+
@Specialization
606+
int doGeneric(Object pointer,
607+
@Cached ToPythonWrapperNode toPythonWrapperNode) {
608+
return toPythonWrapperNode.executeWrapper(pointer, false) == null ? 1 : 0;
609+
}
610+
}
611+
602612
@CApiBuiltin(ret = Void, args = {PyObjectWrapper}, call = Direct)
603613
abstract static class _PyObject_Dump extends CApiUnaryBuiltinNode {
604614

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java

-1
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,6 @@ public final class CApiFunction {
11191119
@CApiBuiltin(name = "_PyObject_GetState", ret = PyObject, args = {PyObject}, call = NotImplemented)
11201120
@CApiBuiltin(name = "_PyObject_HasLen", ret = Int, args = {PyObject}, call = NotImplemented)
11211121
@CApiBuiltin(name = "_PyObject_IsAbstract", ret = Int, args = {PyObject}, call = NotImplemented)
1122-
@CApiBuiltin(name = "_PyObject_IsFreed", ret = Int, args = {PyObject}, call = NotImplemented)
11231122
@CApiBuiltin(name = "_PyObject_LookupSpecialId", ret = PyObject, args = {PyObject, PY_IDENTIFIER}, call = NotImplemented)
11241123
@CApiBuiltin(name = "_PyObject_RealIsInstance", ret = Int, args = {PyObject, PyObject}, call = NotImplemented)
11251124
@CApiBuiltin(name = "_PyObject_RealIsSubclass", ret = Int, args = {PyObject, PyObject}, call = NotImplemented)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -145,6 +145,7 @@ public enum ArgDescriptor {
145145
PyMethodObject(ArgBehavior.PyObject, "PyMethodObject*"),
146146
PyInstanceMethodObject(ArgBehavior.PyObject, "PyInstanceMethodObject*"),
147147
PyObjectTransfer(ArgBehavior.PyObject, "PyObject*", true),
148+
PyObjectRawPointer(ArgBehavior.Pointer, "PyObject*"),
148149
Pointer(ArgBehavior.Pointer, "void*"),
149150
Py_ssize_t(ArgBehavior.Int64, "Py_ssize_t"),
150151
Py_hash_t(ArgBehavior.Int64, "Py_hash_t"),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/CApiTransitions.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1895,7 +1895,7 @@ static PythonNativeWrapper doGeneric(Node inliningTarget, long pointer, boolean
18951895
IdReference<?> lookup = nativeLookupGet(nativeContext, pointer);
18961896
if (isNativeProfile.profile(inliningTarget, lookup != null)) {
18971897
Object ref = lookup.get();
1898-
if (ref == null) {
1898+
if (strict && ref == null) {
18991899
CompilerDirectives.transferToInterpreterAndInvalidate();
19001900
throw CompilerDirectives.shouldNotReachHere("reference was collected: " + Long.toHexString(pointer));
19011901
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignExecutableBuiltins.java

+52-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
package com.oracle.graal.python.builtins.objects.foreign;
2828

29+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___DOC__;
30+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___NAME__;
31+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___NAME__;
2932
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___CALL__;
3033

3134
import java.util.List;
@@ -35,16 +38,17 @@
3538
import com.oracle.graal.python.builtins.CoreFunctions;
3639
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
3740
import com.oracle.graal.python.builtins.PythonBuiltins;
41+
import com.oracle.graal.python.builtins.objects.PNone;
3842
import com.oracle.graal.python.nodes.ErrorMessages;
3943
import com.oracle.graal.python.nodes.PRaiseNode;
4044
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
4145
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
46+
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
4247
import com.oracle.graal.python.nodes.interop.PForeignToPTypeNode;
4348
import com.oracle.graal.python.runtime.ExecutionContext.IndirectCallContext;
4449
import com.oracle.graal.python.runtime.GilNode;
4550
import com.oracle.graal.python.runtime.IndirectCallData;
4651
import com.oracle.graal.python.runtime.PythonContext;
47-
import com.oracle.graal.python.runtime.exception.PythonErrorType;
4852
import com.oracle.truffle.api.CompilerDirectives;
4953
import com.oracle.truffle.api.dsl.Bind;
5054
import com.oracle.truffle.api.dsl.Cached;
@@ -54,6 +58,7 @@
5458
import com.oracle.truffle.api.frame.VirtualFrame;
5559
import com.oracle.truffle.api.interop.ArityException;
5660
import com.oracle.truffle.api.interop.InteropLibrary;
61+
import com.oracle.truffle.api.interop.UnknownIdentifierException;
5762
import com.oracle.truffle.api.interop.UnsupportedMessageException;
5863
import com.oracle.truffle.api.interop.UnsupportedTypeException;
5964
import com.oracle.truffle.api.library.CachedLibrary;
@@ -66,12 +71,56 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
6671
return ForeignExecutableBuiltinsFactory.getFactories();
6772
}
6873

74+
@Builtin(name = J___NAME__, minNumOfPositionalArgs = 1, isGetter = true)
75+
@GenerateNodeFactory
76+
public abstract static class NameNode extends PythonUnaryBuiltinNode {
77+
@Specialization
78+
static Object getName(Object self,
79+
@Bind("this") Node inliningTarget,
80+
@Cached PRaiseNode raiseNode,
81+
@Cached PForeignToPTypeNode toPythonNode,
82+
@CachedLibrary(limit = "2") InteropLibrary lib) {
83+
try {
84+
if (lib.isMemberReadable(self, J___NAME__)) {
85+
return toPythonNode.executeConvert(lib.readMember(self, J___NAME__));
86+
} else if (lib.hasExecutableName(self)) {
87+
return toPythonNode.executeConvert(lib.getExecutableName(self));
88+
} else {
89+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.AttributeError, ErrorMessages.OBJ_P_HAS_NO_ATTR_S, self, T___NAME__);
90+
}
91+
} catch (UnsupportedMessageException | UnknownIdentifierException e) {
92+
throw CompilerDirectives.shouldNotReachHere(e);
93+
}
94+
}
95+
}
96+
97+
@Builtin(name = J___DOC__, minNumOfPositionalArgs = 1, isGetter = true)
98+
@GenerateNodeFactory
99+
public abstract static class DocNode extends PythonUnaryBuiltinNode {
100+
@Specialization
101+
static Object getName(Object self,
102+
@Bind("this") Node inliningTarget,
103+
@Cached PRaiseNode raiseNode,
104+
@Cached PForeignToPTypeNode toPythonNode,
105+
@CachedLibrary(limit = "2") InteropLibrary lib) {
106+
if (lib.isMemberReadable(self, J___DOC__)) {
107+
try {
108+
return toPythonNode.executeConvert(lib.readMember(self, J___DOC__));
109+
} catch (UnsupportedMessageException | UnknownIdentifierException e) {
110+
throw CompilerDirectives.shouldNotReachHere(e);
111+
}
112+
} else {
113+
return PNone.NONE;
114+
}
115+
}
116+
}
117+
69118
@Builtin(name = J___CALL__, minNumOfPositionalArgs = 1, takesVarArgs = true)
70119
@GenerateNodeFactory
71120
public abstract static class CallNode extends PythonBuiltinNode {
72121
@Specialization
73122
static Object doInteropCall(VirtualFrame frame, Object callee, Object[] arguments,
74-
@SuppressWarnings("unused") @Bind("this") Node inliningTarget,
123+
@Bind("this") Node inliningTarget,
75124
@Cached("createFor(this)") IndirectCallData indirectCallData,
76125
@CachedLibrary(limit = "4") InteropLibrary lib,
77126
@Cached PForeignToPTypeNode toPTypeNode,
@@ -89,7 +138,7 @@ static Object doInteropCall(VirtualFrame frame, Object callee, Object[] argument
89138
IndirectCallContext.exit(frame, language, context, state);
90139
}
91140
} catch (ArityException | UnsupportedTypeException e) {
92-
throw raiseNode.raise(inliningTarget, PythonErrorType.TypeError, ErrorMessages.INVALID_INSTANTIATION_OF_FOREIGN_OBJ);
141+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.INVALID_INSTANTIATION_OF_FOREIGN_OBJ);
93142
} catch (UnsupportedMessageException e) {
94143
throw CompilerDirectives.shouldNotReachHere(e);
95144
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/BuiltinFunctionBuiltins.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___NAME__;
3232
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___QUALNAME__;
3333
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___SIGNATURE__;
34-
import static com.oracle.graal.python.nodes.SpecialAttributeNames.T__SIGNATURE__;
34+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___SIGNATURE__;
3535
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___OBJCLASS__;
3636
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___REDUCE__;
3737
import static com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode.T_DOLLAR_DECL_TYPE;
@@ -148,7 +148,7 @@ public abstract static class SignatureNode extends PythonUnaryBuiltinNode {
148148
static Object doIt(PBuiltinFunction fun,
149149
@Bind("this") Node inliningTarget) {
150150
if (fun.getSignature().isHidden()) {
151-
throw PRaiseNode.raiseStatic(inliningTarget, AttributeError, ErrorMessages.HAS_NO_ATTR, fun, T__SIGNATURE__);
151+
throw PRaiseNode.raiseStatic(inliningTarget, AttributeError, ErrorMessages.HAS_NO_ATTR, fun, T___SIGNATURE__);
152152
}
153153
return createInspectSignature(fun.getSignature(), false);
154154
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/method/BuiltinFunctionOrMethodBuiltins.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.AttributeError;
4444
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___SIGNATURE__;
45-
import static com.oracle.graal.python.nodes.SpecialAttributeNames.T__SIGNATURE__;
45+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___SIGNATURE__;
4646
import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___NAME__;
4747
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___REPR__;
4848

@@ -152,7 +152,7 @@ public Object doIt(Object fun,
152152
@Bind("this") Node inliningTarget) {
153153
Signature signature = GetSignatureNode.executeUncached(fun);
154154
if (signature.isHidden()) {
155-
throw PRaiseNode.raiseStatic(inliningTarget, AttributeError, ErrorMessages.HAS_NO_ATTR, fun, T__SIGNATURE__);
155+
throw PRaiseNode.raiseStatic(inliningTarget, AttributeError, ErrorMessages.HAS_NO_ATTR, fun, T___SIGNATURE__);
156156
}
157157
return BuiltinFunctionBuiltins.SignatureNode.createInspectSignature(signature, true);
158158
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/BuiltinNames.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/SpecialAttributeNames.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -120,7 +120,7 @@ public abstract class SpecialAttributeNames {
120120
public static final TruffleString T___TEXT_SIGNATURE__ = tsLiteral(J___TEXT_SIGNATURE__);
121121

122122
public static final String J___SIGNATURE__ = "__signature__";
123-
public static final TruffleString T__SIGNATURE__ = tsLiteral(J___SIGNATURE__);
123+
public static final TruffleString T___SIGNATURE__ = tsLiteral(J___SIGNATURE__);
124124

125125
public static final String J___TRACEBACK__ = "__traceback__";
126126
public static final TruffleString T___TRACEBACK__ = tsLiteral(J___TRACEBACK__);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/SpecialMethodNames.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0

0 commit comments

Comments
 (0)