mojentic 0.4.2__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {mojentic-0.4.2/src/mojentic.egg-info → mojentic-0.5.0}/PKG-INFO +2 -1
  2. {mojentic-0.4.2 → mojentic-0.5.0}/pyproject.toml +2 -1
  3. mojentic-0.5.0/src/_examples/current_datetime_tool_example.py +28 -0
  4. mojentic-0.5.0/src/_examples/design_analysis.py +42 -0
  5. mojentic-0.5.0/src/_examples/ensures_files_exist.py +58 -0
  6. mojentic-0.5.0/src/_examples/file_deduplication.py +38 -0
  7. mojentic-0.5.0/src/_examples/image_broker.py +14 -0
  8. mojentic-0.5.0/src/_examples/image_broker_splat.py +50 -0
  9. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/list_models.py +9 -1
  10. mojentic-0.5.0/src/_examples/raw.py +21 -0
  11. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/__init__.py +1 -0
  12. mojentic-0.5.0/src/mojentic/llm/gateways/anthropic.py +52 -0
  13. mojentic-0.5.0/src/mojentic/llm/gateways/anthropic_messages_adapter.py +72 -0
  14. mojentic-0.5.0/src/mojentic/llm/gateways/file_gateway.py +55 -0
  15. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/openai.py +1 -1
  16. mojentic-0.5.0/src/mojentic/llm/message_composers.py +276 -0
  17. mojentic-0.5.0/src/mojentic/llm/message_composers_spec.py +298 -0
  18. mojentic-0.5.0/src/mojentic/llm/tools/current_datetime.py +47 -0
  19. {mojentic-0.4.2 → mojentic-0.5.0/src/mojentic.egg-info}/PKG-INFO +2 -1
  20. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/SOURCES.txt +13 -0
  21. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/requires.txt +1 -0
  22. {mojentic-0.4.2 → mojentic-0.5.0}/LICENSE.md +0 -0
  23. {mojentic-0.4.2 → mojentic-0.5.0}/README.md +0 -0
  24. {mojentic-0.4.2 → mojentic-0.5.0}/setup.cfg +0 -0
  25. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/__init__.py +0 -0
  26. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/broker_as_tool.py +0 -0
  27. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/broker_examples.py +0 -0
  28. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/characterize_ollama.py +0 -0
  29. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/characterize_openai.py +0 -0
  30. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/chat_session.py +0 -0
  31. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/chat_session_with_tool.py +0 -0
  32. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/coding_file_tool.py +0 -0
  33. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/embeddings.py +0 -0
  34. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/file_tool.py +0 -0
  35. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/image_analysis.py +0 -0
  36. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/iterative_solver.py +0 -0
  37. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/__init__.py +0 -0
  38. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/agents/__init__.py +0 -0
  39. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/agents/decisioning_agent.py +0 -0
  40. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/agents/thinking_agent.py +0 -0
  41. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/formatters.py +0 -0
  42. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/models/__init__.py +0 -0
  43. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/models/base.py +0 -0
  44. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react/models/events.py +0 -0
  45. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/react.py +0 -0
  46. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/recursive_agent.py +0 -0
  47. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/routed_send_response.py +0 -0
  48. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_llm.py +0 -0
  49. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_llm_repl.py +0 -0
  50. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_structured.py +0 -0
  51. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/simple_tool.py +0 -0
  52. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/solver_chat_session.py +0 -0
  53. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/streaming.py +0 -0
  54. {mojentic-0.4.2 → mojentic-0.5.0}/src/_examples/working_memory.py +0 -0
  55. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/__init__.py +0 -0
  56. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/__init__.py +0 -0
  57. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/agent_broker.py +0 -0
  58. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/base_agent.py +0 -0
  59. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/base_llm_agent.py +0 -0
  60. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/base_llm_agent_spec.py +0 -0
  61. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/correlation_aggregator_agent.py +0 -0
  62. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/iterative_problem_solver.py +0 -0
  63. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/output_agent.py +0 -0
  64. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/agents/simple_recursive_agent.py +0 -0
  65. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/audit/event_store.py +0 -0
  66. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/audit/event_store_spec.py +0 -0
  67. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/context/__init__.py +0 -0
  68. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/context/shared_working_memory.py +0 -0
  69. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/dispatcher.py +0 -0
  70. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/event.py +0 -0
  71. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/chat_session.py +0 -0
  72. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/chat_session_spec.py +0 -0
  73. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/__init__.py +0 -0
  74. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/embeddings_gateway.py +0 -0
  75. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/llm_gateway.py +0 -0
  76. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/models.py +0 -0
  77. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/ollama.py +0 -0
  78. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/ollama_messages_adapter.py +0 -0
  79. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/ollama_messages_adapter_spec.py +0 -0
  80. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/openai_message_adapter_spec.py +0 -0
  81. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/openai_messages_adapter.py +0 -0
  82. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/gateways/tokenizer_gateway.py +0 -0
  83. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/llm_broker.py +0 -0
  84. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/llm_broker_spec.py +0 -0
  85. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/__init__.py +0 -0
  86. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/llm_registry.py +0 -0
  87. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/models.py +0 -0
  88. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/registry/populate_registry_from_ollama.py +0 -0
  89. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/__init__.py +0 -0
  90. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/ask_user_tool.py +0 -0
  91. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/date_resolver.py +0 -0
  92. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/date_resolver_spec.py +0 -0
  93. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/file_manager.py +0 -0
  94. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/llm_tool.py +0 -0
  95. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/tool_wrapper.py +0 -0
  96. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/tool_wrapper_spec.py +0 -0
  97. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/llm/tools/web_search.py +0 -0
  98. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/router.py +0 -0
  99. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/router_spec.py +0 -0
  100. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/utils/__init__.py +0 -0
  101. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic/utils/formatting.py +0 -0
  102. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/dependency_links.txt +0 -0
  103. {mojentic-0.4.2 → mojentic-0.5.0}/src/mojentic.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mojentic
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Mojentic is an agentic framework that aims to provide a simple and flexible way to assemble teams of agents to solve complex problems.
5
5
  Author-email: Stacey Vetzal <stacey@vetzal.com>
6
6
  Project-URL: Homepage, https://github.com/mojility/mojentic
@@ -15,6 +15,7 @@ Requires-Dist: pydantic
15
15
  Requires-Dist: structlog
16
16
  Requires-Dist: ollama
17
17
  Requires-Dist: openai
18
+ Requires-Dist: anthropic
18
19
  Requires-Dist: tiktoken
19
20
  Requires-Dist: parsedatetime
20
21
  Requires-Dist: pytz
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mojentic"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  authors = [
5
5
  { name = "Stacey Vetzal", email = "stacey@vetzal.com" },
6
6
  ]
@@ -18,6 +18,7 @@ dependencies = [
18
18
  "structlog",
19
19
  "ollama",
20
20
  "openai",
21
+ "anthropic",
21
22
  "tiktoken",
22
23
  "parsedatetime",
23
24
  "pytz",
@@ -0,0 +1,28 @@
1
+ from mojentic.llm.gateways.models import LLMMessage
2
+ from mojentic.llm.llm_broker import LLMBroker
3
+ from mojentic.llm.tools.current_datetime import CurrentDateTimeTool
4
+
5
+ # Create an LLM broker with a specified model
6
+ # You can change the model to any supported model
7
+ llm = LLMBroker(model="qwen2.5:7b") # Using the same model as in simple_tool.py
8
+
9
+ # Create our custom tool
10
+ datetime_tool = CurrentDateTimeTool()
11
+
12
+ # Generate a response with tool assistance
13
+ result = llm.generate(
14
+ messages=[LLMMessage(content="What time is it right now? Also, what day of the week is it today?")],
15
+ tools=[datetime_tool]
16
+ )
17
+
18
+ print("LLM Response:")
19
+ print(result)
20
+
21
+ # You can also try with a custom format string
22
+ result = llm.generate(
23
+ messages=[LLMMessage(content="Tell me the current date in a friendly format, like 'Monday, January 1, 2023'")],
24
+ tools=[datetime_tool]
25
+ )
26
+
27
+ print("\nLLM Response with custom format:")
28
+ print(result)
@@ -0,0 +1,42 @@
1
+ from pathlib import Path
2
+
3
+ from mojentic.llm.gateways import OllamaGateway
4
+ from mojentic.llm.gateways.models import LLMMessage
5
+
6
+ llmg = OllamaGateway()
7
+
8
+
9
+ def complete(prompt: str):
10
+ response = llmg.complete(
11
+ model="gemma3:27b",
12
+ messages=[
13
+ LLMMessage(
14
+ content=prompt.strip(),
15
+ image_paths=[
16
+ str(Path.cwd() / "images" / "screen_cap.png")
17
+ ]
18
+ )
19
+ ],
20
+ )
21
+ return response.content
22
+
23
+
24
+ # It's quite good at extracting text from images
25
+ result = complete("Extract the text from the attached image in markdown format.")
26
+ print(result)
27
+
28
+ # It's not bad at extracting and structuring the text it finds in images
29
+ result = complete("""
30
+ Extract the title and text boxes from the attached image in the form of a JSON object.
31
+ Use descriptive key names like `title` or `bullet` or `footer`. In the values, use html
32
+ but don't attempt to convert all of the visual formatting, just simple formatting like
33
+ paragraphs, bold, or italic.
34
+ """)
35
+ print(result)
36
+
37
+ # Gemma3 is not very good at determining hex colours :)
38
+ result = complete("""
39
+ Extract the colours from the attached image in the form of a JSON object, where the key
40
+ is the description of how the colour is used, and the value is the colour in hex format.
41
+ """)
42
+ print(result)
@@ -0,0 +1,58 @@
1
+ from pathlib import Path
2
+ import sys
3
+ import os
4
+
5
+ from mojentic.llm.message_composers import MessageBuilder
6
+
7
+ # Get the project root directory (2 levels up from the current script)
8
+ project_root = Path(os.path.dirname(os.path.abspath(__file__))).parent.parent
9
+
10
+ # Test adding non-existent image
11
+ print("Testing adding non-existent image:")
12
+ non_existent_image = project_root / 'src' / '_examples' / 'images' / 'non_existent_image.jpg'
13
+
14
+ try:
15
+ message_builder = MessageBuilder("Testing non-existent image")
16
+ message_builder.add_image(non_existent_image)
17
+ print("ERROR: Expected FileNotFoundError was not raised")
18
+ sys.exit(1)
19
+ except FileNotFoundError as e:
20
+ print(f"Success: Caught expected exception: {e}")
21
+
22
+ # Test adding non-existent file
23
+ print("\nTesting adding non-existent file:")
24
+ non_existent_file = project_root / 'src' / '_examples' / 'non_existent_file.py'
25
+
26
+ try:
27
+ message_builder = MessageBuilder("Testing non-existent file")
28
+ message_builder.add_file(non_existent_file)
29
+ print("ERROR: Expected FileNotFoundError was not raised")
30
+ sys.exit(1)
31
+ except FileNotFoundError as e:
32
+ print(f"Success: Caught expected exception: {e}")
33
+
34
+ # Test adding existing image
35
+ print("\nTesting adding existing image:")
36
+ existing_image = project_root / 'src' / '_examples' / 'images' / 'xbox-one.jpg'
37
+
38
+ try:
39
+ message_builder = MessageBuilder("Testing existing image")
40
+ message_builder.add_image(existing_image)
41
+ print("Success: Added existing image without exception")
42
+ except FileNotFoundError as e:
43
+ print(f"ERROR: Unexpected exception: {e}")
44
+ sys.exit(1)
45
+
46
+ # Test adding existing file
47
+ print("\nTesting adding existing file:")
48
+ existing_file = Path(__file__) # This file itself (absolute path)
49
+
50
+ try:
51
+ message_builder = MessageBuilder("Testing existing file")
52
+ message_builder.add_file(existing_file)
53
+ print("Success: Added existing file without exception")
54
+ except FileNotFoundError as e:
55
+ print(f"ERROR: Unexpected exception: {e}")
56
+ sys.exit(1)
57
+
58
+ print("\nAll tests passed!")
@@ -0,0 +1,38 @@
1
+ from pathlib import Path
2
+
3
+ from mojentic.llm.message_composers import MessageBuilder
4
+
5
+ # Test de-duplication of images
6
+ print("Testing image de-duplication:")
7
+ image_path = Path.cwd() / 'src' / '_examples' / 'images' / 'xbox-one.jpg'
8
+
9
+ # Create a message builder and add the same image multiple times
10
+ message_builder = MessageBuilder("Testing image de-duplication")
11
+ message_builder.add_image(image_path)
12
+ message_builder.add_image(image_path) # Adding the same image again
13
+ message_builder.add_images(image_path, image_path) # Adding the same image twice more
14
+
15
+ # Build the message and check the number of images
16
+ message = message_builder.build()
17
+ print(f"Number of images in message: {len(message.image_paths)}")
18
+ print(f"Expected number of images: 1")
19
+ print(f"De-duplication working: {len(message.image_paths) == 1}")
20
+
21
+ # Test de-duplication of files
22
+ print("\nTesting file de-duplication:")
23
+ file_path = Path('file_deduplication.py') # This file itself
24
+
25
+ # Create a message builder and add the same file multiple times
26
+ message_builder = MessageBuilder("Testing file de-duplication")
27
+ message_builder.add_file(file_path)
28
+ message_builder.add_file(file_path) # Adding the same file again
29
+ message_builder.add_files(file_path, file_path) # Adding the same file twice more
30
+
31
+ # Build the message and check the number of files
32
+ message = message_builder.build()
33
+ # Since we're using the file content in the message, we need to check file_paths directly
34
+ print(f"Number of files in message_builder.file_paths: {len(message_builder.file_paths)}")
35
+ print(f"Expected number of files: 1")
36
+ print(f"De-duplication working: {len(message_builder.file_paths) == 1}")
37
+
38
+ print("\nTest completed.")
@@ -0,0 +1,14 @@
1
+ from pathlib import Path
2
+
3
+ from mojentic.llm import LLMBroker
4
+ from mojentic.llm.message_composers import MessagesBuilder, MessageBuilder
5
+
6
+ llm = LLMBroker(model="gemma3:27b")
7
+
8
+ message = MessageBuilder("What is in this image?") \
9
+ .add_image(Path.cwd() / 'images' / 'xbox-one.jpg') \
10
+ .build()
11
+
12
+ result = llm.generate(messages=[message])
13
+
14
+ print(result)
@@ -0,0 +1,50 @@
1
+ from pathlib import Path
2
+
3
+ from mojentic.llm import LLMBroker
4
+ from mojentic.llm.message_composers import MessageBuilder
5
+
6
+ # Initialize the LLM broker
7
+ llm = LLMBroker(model="gemma3:27b")
8
+
9
+ # Example 1: Adding multiple specific images using the splat operator
10
+ print("Example 1: Adding multiple specific images")
11
+ message1 = MessageBuilder("What are in these images?") \
12
+ .add_images(
13
+ Path.cwd() / 'images' / 'flash_rom.jpg',
14
+ Path.cwd() / 'images' / 'screen_cap.png' # Assuming this file exists
15
+ ) \
16
+ .build()
17
+
18
+ # Example 2: Adding all JPG images from a directory
19
+ print("\nExample 2: Adding all JPG images from a directory")
20
+ images_dir = Path.cwd() / 'images'
21
+ message2 = MessageBuilder("Describe all these images:") \
22
+ .add_images(images_dir) \
23
+ .build()
24
+
25
+ # Example 3: Using a glob pattern to add specific types of images
26
+ print("\nExample 3: Using a glob pattern")
27
+ message3 = MessageBuilder("What can you tell me about these images?") \
28
+ .add_images(Path.cwd() / 'images' / '*.jpg') \
29
+ .build()
30
+
31
+ # Example 4: Combining different ways to specify images
32
+ print("\nExample 4: Combining different ways to specify images")
33
+ message4 = MessageBuilder("Analyze these images:") \
34
+ .add_images(
35
+ Path.cwd() / 'images' / 'xbox-one.jpg', # Specific image
36
+ images_dir, # All JPGs in directory
37
+ Path.cwd() / 'images' / '*.png' # All PNGs using glob pattern
38
+ ) \
39
+ .build()
40
+
41
+ # Print the number of images added in each example
42
+ print(f"Example 1: Added {len(message1.image_paths)} image(s)")
43
+ print(f"Example 2: Added {len(message2.image_paths)} image(s)")
44
+ print(f"Example 3: Added {len(message3.image_paths)} image(s)")
45
+ print(f"Example 4: Added {len(message4.image_paths)} image(s)")
46
+
47
+ # Generate a response using one of the messages (e.g., message1)
48
+ print("\nGenerating response for Example 1...")
49
+ result = llm.generate(messages=[message1])
50
+ print(result)
@@ -1,6 +1,7 @@
1
1
  import os
2
2
 
3
3
  from mojentic.llm.gateways import OllamaGateway, OpenAIGateway
4
+ from mojentic.llm.gateways.anthropic import AnthropicGateway
4
5
 
5
6
  ollama = OllamaGateway()
6
7
  print("Ollama Models:")
@@ -12,4 +13,11 @@ print()
12
13
  openai = OpenAIGateway(os.environ["OPENAI_API_KEY"])
13
14
  print("OpenAI Models:")
14
15
  for model in openai.get_available_models():
15
- print(f"- {model}")
16
+ print(f"- {model}")
17
+
18
+ print()
19
+
20
+ anthropic = AnthropicGateway(os.environ["ANTHROPIC_API_KEY"])
21
+ print("Anthropic Models:")
22
+ for model in anthropic.get_available_models():
23
+ print(f"- {model}")
@@ -0,0 +1,21 @@
1
+ import os
2
+
3
+ from anthropic import Anthropic
4
+
5
+ client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
6
+
7
+ anthropic_args = {
8
+ 'model': "claude-3-5-haiku-20241022",
9
+ 'system': "You are a helpful assistant.",
10
+ 'messages': [
11
+ {
12
+ "role": "user",
13
+ "content": "Say hello world",
14
+ }
15
+ ],
16
+ 'max_tokens': 2000,
17
+ }
18
+
19
+ response = client.messages.create(**anthropic_args)
20
+
21
+ print(response.content[0].text)
@@ -1,3 +1,4 @@
1
1
  from .llm_broker import LLMBroker
2
2
  from .registry.llm_registry import LLMRegistry
3
3
  from .chat_session import ChatSession
4
+ from .message_composers import MessageBuilder, FileTypeSensor
@@ -0,0 +1,52 @@
1
+ from typing import List
2
+
3
+ import structlog
4
+ from anthropic import Anthropic
5
+
6
+ from mojentic.llm.gateways import LLMGateway
7
+ from mojentic.llm.gateways.models import LLMGatewayResponse, LLMToolCall, MessageRole
8
+ from mojentic.llm.gateways.anthropic_messages_adapter import adapt_messages_to_anthropic
9
+
10
+ logger = structlog.get_logger()
11
+
12
+ class AnthropicGateway(LLMGateway):
13
+ def __init__(self, api_key: str):
14
+ self.client = Anthropic(api_key=api_key)
15
+
16
+ def complete(self, **args) -> LLMGatewayResponse:
17
+
18
+ messages = args.get('messages')
19
+
20
+ system_messages = [m for m in messages if m.role == MessageRole.System]
21
+ user_messages = [m for m in messages if m.role == MessageRole.User]
22
+
23
+ anthropic_args = {
24
+ 'model': args['model'],
25
+ 'system': " " . join([m.content for m in system_messages]) if system_messages else None,
26
+ 'messages': adapt_messages_to_anthropic(user_messages),
27
+ }
28
+
29
+ response = self.client.messages.create(
30
+ **anthropic_args,
31
+ temperature=args.get('temperature', 1.0),
32
+ max_tokens=args.get('num_predict', 2000),
33
+ # thinking={
34
+ # "type": "enabled",
35
+ # "budget_tokens": 32768,
36
+ # }
37
+ )
38
+
39
+ object = None
40
+ tool_calls: List[LLMToolCall] = []
41
+
42
+ return LLMGatewayResponse(
43
+ content=response.content[0].text,
44
+ object=object,
45
+ tool_calls=tool_calls,
46
+ )
47
+
48
+ def get_available_models(self) -> List[str]:
49
+ return sorted([m.id for m in self.client.models.list()])
50
+
51
+ def calculate_embeddings(self, text: str, model: str = "voyage-3-large") -> List[float]:
52
+ raise NotImplementedError("The Anthropic API does not support embedding generation.")
@@ -0,0 +1,72 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ from typing import List, Any
5
+
6
+ import structlog
7
+
8
+ from mojentic.llm.gateways.models import LLMMessage, MessageRole
9
+
10
+ logger = structlog.get_logger()
11
+
12
+
13
+ def adapt_messages_to_anthropic(messages: List[LLMMessage]):
14
+ new_messages: List[dict[str, Any]] = []
15
+ for m in messages:
16
+ if m.role == MessageRole.System:
17
+ # System messages aren't supported by Anthropic
18
+ pass
19
+ elif m.role == MessageRole.User:
20
+ if m.image_paths is not None and len(m.image_paths) > 0:
21
+ # Create a content structure with text and images
22
+ content = []
23
+ if m.content:
24
+ content.append({"type": "text", "text": m.content})
25
+
26
+ # Add each image as a base64-encoded URL
27
+ for image_path in m.image_paths:
28
+ try:
29
+ with open(image_path, "rb") as image_file:
30
+ base64_image = base64.b64encode(image_file.read()).decode('utf-8')
31
+
32
+ # Determine image type from file extension
33
+ _, ext = os.path.splitext(image_path)
34
+ image_type = ext.lstrip('.').lower()
35
+ if image_type not in ['jpeg', 'jpg', 'png', 'gif', 'webp']:
36
+ image_type = 'jpeg' # Default to jpeg if unknown extension
37
+
38
+ content.append({
39
+ "type": "image",
40
+ "source": {
41
+ "type": "base64",
42
+ "media_type": f"image/{image_type}",
43
+ "data": base64_image
44
+ }
45
+ })
46
+ except Exception as e:
47
+ logger.error("Failed to encode image", error=str(e), image_path=image_path)
48
+
49
+ new_messages.append({'role': 'user', 'content': content})
50
+ else:
51
+ new_messages.append({'role': 'user', 'content': m.content})
52
+ elif m.role == MessageRole.Assistant:
53
+ msg = {'role': 'assistant', 'content': m.content or ''}
54
+ # if m.tool_calls is not None:
55
+ # msg['tool_calls'] = [{
56
+ # 'id': m.tool_calls[0].id,
57
+ # 'type': 'function',
58
+ # 'function': {
59
+ # 'name': m.tool_calls[0].name,
60
+ # 'arguments': json.dumps({k: v for k, v in m.tool_calls[0].arguments.items()})
61
+ # }
62
+ # }]
63
+ new_messages.append(msg)
64
+ # elif m.role == MessageRole.Tool:
65
+ # new_messages.append({
66
+ # 'role': 'tool',
67
+ # 'content': m.content,
68
+ # 'tool_call_id': m.tool_calls[0].id
69
+ # })
70
+ else:
71
+ logger.error("Unknown message role", role=m.role)
72
+ return new_messages
@@ -0,0 +1,55 @@
1
+ from pathlib import Path
2
+ from typing import Union
3
+
4
+
5
+ class FileGateway:
6
+
7
+ def read(self, path: Union[Path, str]):
8
+ if isinstance(path, str):
9
+ path = Path(path)
10
+ with open(path, 'r') as file:
11
+ return file.read()
12
+
13
+ def exists(self, path: Union[Path, str]) -> bool:
14
+ """
15
+ Check if a file exists.
16
+
17
+ Parameters
18
+ ----------
19
+ path : Union[Path, str]
20
+ Path to the file
21
+
22
+ Returns
23
+ -------
24
+ bool
25
+ True if the file exists, False otherwise
26
+ """
27
+ if isinstance(path, str):
28
+ path = Path(path)
29
+ return path.exists()
30
+
31
+ def is_binary(self, path: Union[Path, str]) -> bool:
32
+ """
33
+ Check if a file is binary.
34
+
35
+ Parameters
36
+ ----------
37
+ path : Union[Path, str]
38
+ Path to the file
39
+
40
+ Returns
41
+ -------
42
+ bool
43
+ True if the file is binary, False otherwise
44
+ """
45
+ if isinstance(path, str):
46
+ path = Path(path)
47
+ try:
48
+ with open(path, 'rb') as file:
49
+ # Read the first 1024 bytes
50
+ chunk = file.read(1024)
51
+ # Check for null bytes, which are typically present in binary files
52
+ return b'\0' in chunk
53
+ except Exception:
54
+ # If there's an error reading the file, assume it's binary
55
+ return True
@@ -21,7 +21,7 @@ class OpenAIGateway(LLMGateway):
21
21
  The OpenAI API key to use.
22
22
  """
23
23
 
24
- def __init__(self, api_key):
24
+ def __init__(self, api_key: str):
25
25
  self.client = OpenAI(api_key=api_key)
26
26
 
27
27
  def complete(self, **args) -> LLMGatewayResponse: