Sometimes json output from llm like this : {“key”: “value”}“` that langchain JsonOutputParser regex cant parse. The regex validation function is in langchain_core.output_parsers.json.parse_json_markdown(). So we just monkey patch that function
# monkey_patch.py
from typing import Callable, Any
from langchain_core.output_parsers.json import _custom_parser, parse_partial_json
import langchain_core.output_parsers.json as lc_op_j
import re
def parse_json_markdown(
json_string: str, *, parser: Callable[[str], Any] = parse_partial_json
) -> dict:
"""
Parse a JSON string from a Markdown string.
Args:
json_string: The Markdown string.
Returns:
The parsed JSON object as a Python dictionary.
"""
# Try to find JSON string within triple backticks
match = re.search(r"(?:```)?(json)?(.*)", json_string, re.DOTALL) # --------> patch
# If no match found, assume the entire string is a JSON string
if match is None:
json_str = json_string
else:
# If match found, use the content within the backticks
json_str = match.group(2)
# Strip whitespace and newlines from the start and end
json_str = json_str.strip().strip("`")
# handle newlines and other special characters inside the returned value
json_str = _custom_parser(json_str)
# Parse the JSON string into a Python dictionary
parsed = parser(json_str)
return parsed
lc_op_j.parse_json_markdown = parse_json_markdown
Here the test
import unittest
import monkey_patch as _
from langchain_core.output_parsers.json import JsonOutputParser
class TestMonkeyPatch(unittest.TestCase):
def test_json_output_parse(self):
results = map(lambda x: JsonOutputParser().parse(x), [
'{"key": "value"',
'```{"key": "value"}```',
'```{"key": "value"}',
'```{"key": "value"',
'```{"key": "value"```',
'```json{"key": "value"}```',
'```json{"key": "value"}',
'```json{"key": "value"',
'```json{"key": "value"```',
'```{"key": "value", "key2": "value2"```',
'{"key": "value"}```', # ----> monkeypatch for langchain
])
for result in results:
self.assertEqual("value", result["key"])
Diatas adalah program untuk membaca file dan menampilkannya. Ditulis menggunakan bahasa C, Golang, dan Python. Kode tersebut terlihat simpel. Kita tidak perlu tahu file itu diletakan di disk dan partisi yang mana. Apakah menggunakan filesystem ext4, ntfs, atau fat?. Apakah disknya Hard Drive, SSD, atau memory card?. Karena hal tersebut sudah di abstraksikan oleh Kernel. (Alokasi Memori) Linux bisa dibagi menjadi dua: Userspace, dan Kernel space.
Tugas kernel adalah mendistribusi resource (CPU dan Memory) kepada program dan abstraksi hardware. Mendistribusi resource membuat banyak program bisa berjalan beriiringan (multitasking). Abstraksi hardware, salah satu contohnya seperti yang disebut tadi program tidak perlu tahu detil dari disk, partisi, filesystem untuk mengakses file. Diluar kernel, berarti adalah userspace.
Aplikasi melakukan system call(syscall) untuk berinteraksi dengan kernel. Dalam kasus diatas, dibutuhkan akses ke hardware untuk membaca file dari disk. Serta akses ke terminal untuk menampilkan isi file.
Source code C dan Go di compile menjadi executable. Executable di windows formatnya exe dan di linux ELF (Walaupun tidak ada ekstensi nya). Executable ini yang memanggil system call. Untuk python, source nya di interpretasi oleh Python VM. Lalu Python VM ini yang memanggil system call.
Percobaan 1 : Strace dan Objdump
strace adalah program untuk menampilkan syscall yang dilakukan pada program. Dibawah kita coba melakukan strace pada 3 program tadi (c, go, python)
Walaupun ketiga bahasa tersebut bebeda, mereka memanggil system call yang sama contohnya openat. Untuk membuka file pada path /tmp/1337. openat mereturn alamat (File Descriptor) dari file tersebut, yaitu 3. Kemudian read dipanggil dengan paramter file descriptor tadi (3) untuk membaca file tersebut.
Umumnya kita tidak memanggil syscall secara langsung. Melainkan menggunakan library yang lebih mudah. Biar library itu yang memanggil system call nya. Contoh kode di kanan ini adalah kode C yang langsung menggunakan syscall. Terlihat sangat mirip dengan hasil strace tadi.
Dalam linux ada dua cara memanggil syscall. Pertama dengan instruksi khusus pada assembly/binary. Kedua dengan library pada bahasa C. Karena python dibuat dari bahasa C, python menggunakan library syscall C juga. Sedangkan golang, memakai syscall menggunakan assembly/binary. Akan di jelaskan detailnya di bawah.
Dokumentasi detail dari system call bisa dilihat disini: https://man7.org/linux/man-pages/dir_section_2.html . Atau menggunakan command man 2 $nama_syscall di linux
Percobaan 2: top: User time, kernel time, io wait time
Kita akan melakukan eksperiman membuat empatprogram Python: Infinite loop, infinite loop dengan banyak thread, membaca file dengan simulasi disk I/O yang pelan, dan membaca file besar 4gb dengan ram 2gb. Ketika program berjalan, kita monitor menggunakan tools top (task managernya linux). Metric yang diperhatikan adalah Utilisasi % CPU. Dalam linux utilisasi cpu dibagi ke beberapa bagian. Hal yang kita perhatikan adalah
% us (User Space): % waktu cpu yang dihabiskan di user space (aplikasi kita)
% sy (kernel Space) % waktu cpu yang dihabiskan di kernel space oleh kernel
%wa (wait I/O disk time): % waktu cpu yang idle. namun ketika idle kernel masih mengunggu I/O dari disk.
Sebenernya masih ada utiliasi lain seperti ni, hi, si, st
1. Infinite Loop
Dalam infinite loop ini, program tidak melakukan interaksi apapun dengan kernel.
while True:
pass
Bisa dilihat pada cpu1, utilisasi user space nya 100%
2. Infinite Loop Dengan Banyak Thread
Sama seperti tadi, namun kita membuat banyak (100 ribu) OS thread untuk menjalankan infinite loop tersebut. Sehingga infinite loop ini bisa berjalan parallel.
Pythonfrom threading import Thread
import time
def f():
time.sleep(5)
while True:
pass
threads=[Thread(target=f) for i in range(1, 100000)]
for t in threads:
t.start()
for t in threads:
t.join()
Bisa kita lihat, utiliasi user space cukup tinggi 13 dan 18 %. Namun yang menarik adalah utilisasi kernel space tinggi mencapai 80%. Bedanya dengan yang pertama kita menspawn banyak thread. Tugas kernel adalah mendistribusi resource (cpu/ram) kepada program program. Dalam hal ini adalah 100,000 thread yang kita bikin tadi diatur oleh kernel suapya bisa berjalan beriiringan (multitasking). MIsal dalam jangka waktu 10 ms thread tersebut belum selesai (karena infinite loop, tidak pernah selesai), kernel men “sleep” kan thread tersebut. Dan kernel memberi giliran kepada thread yang lain untuk jalan.
Ada 100,000 thread dan semuanya tidak selesai selesai. Hal ini yang membuat kernel sibuk kewalahan mengatur giliran jalan 100,000 thread tersebut. Sehingga utilisasi cpu di user space jadi tinggi
3. Simulasi Membaca I/O dari Harddisk pelan
Di linux kita bisa melakukan simulasi membuat disk yang I/O nya sangat pelan. Untuk detil caranya bisa lihat disini:
import random
f = open("/dev/mapper/dm-slow", "r")
byte_file_size = 100 * 1024 * 1024
while True:
f.read(random.randint(1, byte_file_size))
Bisa kita lihat, semua nya nganggur kecuali utilisasi cpu yang idle namun masih ada antrian I/O (wa). 100% wa bukan berarti cpu sibuk. Namun sama seperti idle. Jadi CPU Idle 100%. Hanya saja beda nya dengan idle, disini masih ada antrian I/O dari disk yang menumpuk
4. Membaca File Besar 4 gb (Tanpa simulasi pelan)
Masih sama seperti tadi. Hanya saja tidak dibikin simulasi pelan. Kita membaca file 4 gb. Sedangkan ram di sistem hanya 2 gb. Bisa kita lihat utiliasi cpu di kernel space (sy) dan wait I/O disk time (wa) cukup tinggi.
Hal ini dikarenakan jika memory habis, kernel akan menggunakan swap. Yaitu memory yang diletakan di disk. Kecapatan disk sangat jauh pelan dibandingkan RAM. Sehingga kernel sibuk memindahkan antar ram dan swap. Dan I/O disknya menjadi berat juga
Sebenernya ada cara juga untuk membaca file besar tanpa membutuhkan memory yang sama dengan besar file nya. Menggunakan mmap
Kesimpulan
Sistem operasi membagi area (virtual) memori menjadi dua: User space dan kernel space
Tugas kernel mendistribusi resource (CPU/RAM) dan abstraksi hardware
Userspace tempat aplikasi kita berjalan
Userspace memanggil kernel menggunakan system call
Masih Penasaran? Coba lanjut baca dibawah ini
Kosa Kata Baru
strace: command linux untuk mengetahui system call yang dilakukan pada suatu program
file descriptor: semua I/O di linux baik itu file atau pun network di dalam satu proses di identifikasikan dengan angka
man: command linux untuk melihtat dokumentasi dari command dan library C header
top: command linux untuk melihat utlisasi cpu, ram ,io dan proses
us
sy
wa
mmap
Encore 1 – User Space Yang Penting
Berikut statistik sistem Ubuntu linux di breakdown jumlah baris kode sumber nya (source line of code).
Terlihat kernel sangat kecil sekali proporsinya, karna tugas nya yang sebenernya simpel. Dengan hanya kernel, tidak ada yang bisa digunakan. Begitu juga sebalik nya. Bahkan kalau di breakdown lagi di dalam kernel. Hanya 35% kode yang berkaitan tentang kernel. 50% berisi driver, dan 15% berisi kode spesifik assembly kernel untuk tiap arsitektur (x86, arm, powerpc, dll). Sumber
Userspace yang akan dibahas adalah Libc dan Init system. Userspace berikut membedakan secara fundametal tiap distribusi linux
Libc, Static Linked, Dynamic Linked
C Menggunakan library FILE *f_ptr;
char f_content[256], output[512];
f_ptr = fopen("/tmp/1337", "r");
fgets(f_content, 256, f_ptr);
fclose(f_ptr);
C menggunakan syscall char f_content[256], output[512];
int fd, size;
fd = openat(AT_FDCWD, "/tmp/1337", O_RDONLY);
size = read(fd, f_content, 256);
close(fd);
f_content[size] = '\0';
Pada contoh sebelumnya kita membaca file menggunakan bahasa C melalui 2 cara: mengguankan library fopen, fgets, fclose. Cara kedua langsung menggunakan syscall openat, read, close. Sebetulnya library fopen yang kita pakai tadi di belakang layar juga memanggil system call openat.
Umumnya program depend ke library utama bahasa C. Bahkan bahasa lain pun seperti python juga depend ke C. Karena Python VM dibuat dengan bahasa C. Library utama ini disebut LIBC atau C Standard Library. Contoh isi dari libc adalah
System Call wrapper. Jadi tidak perlu menggunakan assembly
math.h untuk operasi matematika seperti absolut, sin, cos, tan.
string.h untuk manipualsi string. strcat untuk menggabungkan string, strcmp untuk membandingkan string
C dan Python (VM Python3) menggunakan glibc dengan cara dynamic linking. Yaitu binary hasil compile nya tidak berisi glibc. Melainkan berisi nama library yang dibutuhkan saja, yaitu glibc. Ketika binary ini dijalankan, sistem akan mencari library yang dibutuhkan, yaitu libc dalam sistem. Dan program ini berjalan.
ldd digunakan untuk mengetahui apakah binary nya butuh dependensi library (dynamic linked). Dibawah ini binary dari hello c dan python3 VM bergantung ke libc.so.6, yaitu GLIBC. Sedangkan go tidak (not a dynamic executable, berarti Static LInked). Karena go memanggil syscall langsung menggunakan assembly.
objdump digunakan untuk melihat assembly code dari binary. Bisa dilihat untuk hello-c tidak ada instruksi syscall. Karena hello-c menggunakan library glibc. Sehingga jika kita lihat objdump dari glibc, ada instruksi syscall. Dan jika kita liaht objdump dari hello-go, terdapat instruksi syscall.
➜ ldd ./hello-go
not a dynamic executable
➜ objdump -d ./hello-go | grep -E "syscall " -B 5| head
...
402789: 48 89 df mov %rbx,%rdi
40278c: 0f 05 syscall
libc yang umum digunakan sampai saat ini adalah GLIBC (GNU Lib C). Kedua adalah musl yang dipakai di Alpine Linux (sering digunakan di container). musl dibuat karena GLIBC dirasa terlalu kompleks kode nya. Misal optimasi yang berlebihan di fungsi strcmp yang berfungsi untuk membadingkan dua string. Di glibc, terdapat banyak versi strcmp sesuai arsitektur nya menggunakan bahasa assembly. Sedangkan di musl, hanya satu menggunakan C. Biar compiler yang menterjemahkan ke arsitektur.
Optimasi pada glibc membuat glibc lebih cepat dibandingkan musl. Namun, Kompleksitas ini menyebabkan dua masalah: ukuran dan keamanan. Karena ukuran nya kecil, musl cocok untuk digunakan di embeded device. Semakin kompleks kode, pasti akan membuka celah keamanan yang lebih luas. Contoh GHOST vuln glibc
ldd: command linux untuk mengetahui library apa yang di link secara dinamis pada executable binary tersebut
libc
static linked, dynamic linked
Encore 2 – Hybrid, Monolithic, Microkernel
Desain dari kernel linux adalah monolithic. Berarti semua komponen kernel jalan dalam satu binary file dan menggunakan alamat memori yang sama berbarengan. Sedangkan Microkernel, tiap component kernel adlah binary yang berbeda serta memori yang beda pula. Komunikasi antar komponennya menggunakan IPC (inter processs communication).
Monolithic pasti secara performa lebih cepat. Namun kesalahan pada satu modul dapat menggagu menganggu kernel.
Kernel Module
Bisa menambahkan modul kernel secara realtime. Tidak perlu compile kernel dan restart. Walaupun berbeda file dengan kernel. Kernel module tetap jalan pada memori yang sama dengan kernel.
Contohnya adalah driver. Umumnya driver diletakan di kode utama kernel linux. Dan di kompilasi bersama kernel juga menajdi satu binary kernel. Sehingga kode driver jadi open source. Manufacturer seperti NVIDIA enggan mengopensource kan driver mereka dan meletakan nya bersamaan kode kernel. Sehingga driver NVIDIA menggunakan kernel module. Dan tiap kali ada update pada versi kernel, driver ini harus di “compile” ulang juga. Tidak seperti di windows. Untungnya sudah ada mekanisme compile otomatis setiap kernel update bernama DKMS
Interface kernel linux sering berubah tidak seperti windows. Hal ini yang menyebabkan kernel module harus di compile ulang setiap kali kernel update. Mengapa sering berubah?
Contoh dibawah ini adalah kode C yang membuat array. Lalu kita akses array tersebut melebihi kapasitas nya. Misal ada 5 element, kita akses elemen 10. Di bahasa lain, akan terjadi error karena bahasa yang type safe. Terdapat pengecekan (bound check).
Kode dikiri adalah aplikasi biasa. Sedangkan di kanan adalah kernel module.
Ketika akses memory yang diakses tidak jauh dengan aslinya, tidak ada error. Namun program corrupt karena …. Bisa terjadi error di waktu kemudian
Ketika akses memory yang sangat jauh, aplikasi kita di keluarkan oleh OS (Linux) dengan error segmentation fault. Hal ini adalah fitur virtual memory yang di kelola oleh kernel linux.
Sedangkan di kernel module, tidak ada segmentation fault. Meskipun bisa di load secara dinamis tanpa perlu compile ulang kernel dan restart. Hal ini di karenakan kernel dan kernel module berjalan di dalam satu aplikasi dan memori yang sama dengan kernel. Ini yang disebut monolithic kernel. Jadi hal yang mungkin terjadi adalah sistem tidak stabil karena komponoen kernel lain corrupt memory nya. Dan kernel panic.
Ruby on Rails merupakan framework web MVC menggunakan bahasa dynamic typed (Ruby) yang termasuk pertama (2005). Diikuti dengan django (2005), laravel (2011). Walaupun di dunia proyek jarang dipakai, di dunia produk/startup banyak dipakai karena lebih cepat waktu yang dibutuhkan untuk membuat nya.
Holy wars ini sudah ada sejak lama. Static typed language (java, go) pasti lebih aman(dalam artian mencegah error) daripada dynamic (php, javascript, ruby). Hal ini dikarenakan error dapat ditangkap di waktu compile time. Hal ini berimplikasi pada mudahnya refactor pada kode. Makanya akhir akhir ini Typescript (versi static type dari javascript) mulai ramai digunakan.
Sedangkan dynamic typed menggiurkan karena lebih cepet ngoding nya serta bisa melakukan berbagai magic/syntatic sugar. Waktu membuktikan bahwa dynamic type bisa “aman” juga seperti static typed jika di jaga dengan unit test yang coverall nya tinggi.
Sebenernya spring boot sudah di embedd applicatoin server tomcat (hanya berisi web container, tidak bisa untuk java EE). Sehingga tidak perlu menggunakan application server lagi. Namun kadang orang karena sudah bayar application server lain, misal weblogic. dan ada beberapa aplikasi lain yang jalan di weblogic. Ingin mendeploy aplikasi spring boot di weblogic.
Secara tertulis harusnya tidak ada yang perlu dilakukan. Karena tujuan standarisasi J2EE adalah WAR/EAR bisa portable antar webserver. Namun kadang kenyataannya tidak, karena ada beberapa yang butuh fungsi spesifik app server, ataupun configurasi spesifik.
Sumber : Jrebel https://www.jrebel.com/calculate-your-roi-with-jrebel
Sebenernya application server yang mahal mahal itu bisa dijalankan di local laptop kita dan kita bisa melakukan auto reloading, hot swap, debugging juga. Namun, berat 😀
Catatan : Kita hanya bisa di deploy di tomcat jika aplikasi kita hanya menggunakan web container seperti spring boot. Jika kita memakai spring dan API J2EE selain web container, maka kita tidak bisa menggunakan tomcat.
Strategi
Menggunakan profile untuk mengethaui aplikasi ini jalan di app server mana. Untuk konfigurasi bisa dinamis tergantung profile di setting di pom.xml. Dan untuk fungsi yang spesifik ke App Server, bisa di bikin interface dan implementasi di tomcat hanya dummy saja. Dependency injection tergantung profile.
application-tomcat.properties => jika menggunakna profile “tomcat”
Fungsi spesifik
@Component("weblogicAppServer")
@Profile("!tomcat")
public class WeblogicService implements AppServerSpecificFunctionInterface {
}
@Component("tomcatAppServer")
@Profile("tomcat")
public class TomcatService implements AppServerSpecificFunctionInterface {
}
Perhatikan settingan network. Untuk ubuntu yang menggunakan netplan, anda harus membuat konfigurasi netplan baru jika nama interface nya berbeda (misal ensp088di pc dengan eth0 di virtualbox)
Aspose.word merupakan library untuk mengedit file microsoft office .docx. Sebenernya microsoft tidak mengeluarkan dokumentasi resmi API microsoft word. Dan file microsoft word dibuat sesuah mungkin untuk di edit (obfuscate). Makanya ada library open source bernama Apache POI (Poor Obfuscation Implementation) untuk mengedit file .docx (seperti Aspose.Word)