| from smolagents import Tool, tool |
| from openai import OpenAI |
| import shutil |
|
|
|
|
| @tool |
| def chess_engine_locator() -> str | None: |
| """ |
| Get the path to the chess engine binary. Can be used with chess.engine.SimpleEngine.popen_uci function from chess.engine Python module. |
| Returns: |
| str: Path to the chess engine. |
| """ |
| path = shutil.which('stockfish') |
| return path if path else None |
|
|
| class ImageToChessBoardFENTool(Tool): |
| name = 'image_to_chess_board_fen' |
| description = '''Convert a chessboard image to board part of the FEN.''' |
| inputs = { |
| 'image_url': { |
| 'type': 'string', |
| 'description': 'Public URL of the image (preferred) or base64 encoded image in data URL format', |
| } |
| } |
| output_type = 'string' |
|
|
| def __init__(self, client: OpenAI | None = None, **kwargs): |
| self.client = client if client is not None else OpenAI() |
| super().__init__(**kwargs) |
|
|
| def attachment_for(self, task_id: str | None): |
| self.task_id = task_id |
| |
| def forward(self, image_url: str) -> str: |
| """ |
| Convert a chessboard image to board part of the FEN. |
| Args: |
| image_url (str): Public URL of the image (preferred) or base64 encoded image in data URL format. |
| Returns: |
| str: Board part of the FEN. |
| """ |
| client = self.client |
|
|
| response = client.responses.create( |
| model='gpt-4.1', |
| input=[ |
| { |
| 'role': 'user', |
| 'content': [ |
| { |
| 'type': 'input_text', |
| 'text': 'Describe the position of the pieces on the chessboard from the image. Please, nothing else but description.', |
| }, |
| {'type': 'input_image', 'image_url': image_url}, |
| ], |
| }, |
| ], |
| ) |
|
|
| response = client.responses.create( |
| model='gpt-4.1', |
| input=[ |
| { |
| 'role': 'user', |
| 'content': [ |
| { |
| 'type': 'input_text', |
| 'text': 'Describe the position of the pieces on the chessboard from the image. Please, nothing else but description.', |
| }, |
| ], |
| }, |
| ] |
| + response.output |
| + [ |
| { |
| 'role': 'user', |
| 'content': [ |
| { |
| 'type': 'input_text', |
| 'text': """\ |
| Write down all positions with known pieces. |
| Use a standard one-letter code to name pieces. |
| It is important to use the correct case for piece code. Use upper case for white and lower case for black. |
| It is important to include information about all the mentioned positions. |
| Describe each position in a new line. |
| Follow format: <piece><position> (piece first, then position, no spaces) |
| Return nothing but lines with positions. |
| """, |
| }, |
| ], |
| } |
| ], |
| ) |
| board_pos = response.output_text |
|
|
| pos_dict = {} |
| for post_str in board_pos.splitlines(): |
| pos_str = pos_str.strip() |
| if len(pos_str) != 3: |
| continue |
| piece = post_str[0] |
| pos = pos_str[1:3] |
| pos_dict[pos] = piece |
|
|
| board_fen = '' |
| for rank in range (8, 0, -1): |
| empty = 0 |
| for file_c in range(ord('a'), ord('h') +1): |
| file = chr(file_c) |
| square = file + str(rank) |
| if square in pos_dict: |
| if empty > 0: |
| board_fen += str(empty) |
| empty = 0 |
| board_fen += pos_dict[square] |
| else: |
| empty += 1 |
| if empty > 0: |
| board_fen += str(empty) |
| if rank != 1: |
| board_fen += '/' |
| |
| return board_fen |