langfun 0.0.2.dev20240429__py3-none-any.whl → 0.1.2.dev202501140804__py3-none-any.whl

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 (144) hide show
  1. langfun/__init__.py +20 -2
  2. langfun/core/__init__.py +16 -5
  3. langfun/core/agentic/__init__.py +30 -0
  4. langfun/core/agentic/action.py +854 -0
  5. langfun/core/agentic/action_eval.py +150 -0
  6. langfun/core/agentic/action_eval_test.py +109 -0
  7. langfun/core/agentic/action_test.py +136 -0
  8. langfun/core/coding/python/__init__.py +5 -11
  9. langfun/core/coding/python/correction.py +37 -21
  10. langfun/core/coding/python/correction_test.py +29 -3
  11. langfun/core/coding/python/execution.py +40 -216
  12. langfun/core/coding/python/execution_test.py +29 -89
  13. langfun/core/coding/python/generation.py +21 -11
  14. langfun/core/coding/python/generation_test.py +2 -2
  15. langfun/core/coding/python/parsing.py +108 -193
  16. langfun/core/coding/python/parsing_test.py +2 -105
  17. langfun/core/component.py +63 -2
  18. langfun/core/component_test.py +53 -0
  19. langfun/core/concurrent.py +414 -117
  20. langfun/core/concurrent_test.py +111 -24
  21. langfun/core/console.py +18 -5
  22. langfun/core/console_test.py +17 -0
  23. langfun/core/eval/__init__.py +16 -1
  24. langfun/core/eval/base.py +622 -174
  25. langfun/core/eval/base_test.py +200 -54
  26. langfun/core/eval/matching.py +63 -76
  27. langfun/core/eval/matching_test.py +17 -8
  28. langfun/core/eval/patching.py +130 -0
  29. langfun/core/eval/patching_test.py +170 -0
  30. langfun/core/eval/scoring.py +26 -26
  31. langfun/core/eval/scoring_test.py +19 -2
  32. langfun/core/eval/v2/__init__.py +42 -0
  33. langfun/core/eval/v2/checkpointing.py +380 -0
  34. langfun/core/eval/v2/checkpointing_test.py +228 -0
  35. langfun/core/eval/v2/eval_test_helper.py +136 -0
  36. langfun/core/eval/v2/evaluation.py +725 -0
  37. langfun/core/eval/v2/evaluation_test.py +180 -0
  38. langfun/core/eval/v2/example.py +305 -0
  39. langfun/core/eval/v2/example_test.py +128 -0
  40. langfun/core/eval/v2/experiment.py +1048 -0
  41. langfun/core/eval/v2/experiment_test.py +433 -0
  42. langfun/core/eval/v2/metric_values.py +156 -0
  43. langfun/core/eval/v2/metric_values_test.py +80 -0
  44. langfun/core/eval/v2/metrics.py +357 -0
  45. langfun/core/eval/v2/metrics_test.py +203 -0
  46. langfun/core/eval/v2/progress.py +348 -0
  47. langfun/core/eval/v2/progress_test.py +82 -0
  48. langfun/core/eval/v2/progress_tracking.py +210 -0
  49. langfun/core/eval/v2/progress_tracking_test.py +66 -0
  50. langfun/core/eval/v2/reporting.py +270 -0
  51. langfun/core/eval/v2/reporting_test.py +158 -0
  52. langfun/core/eval/v2/runners.py +488 -0
  53. langfun/core/eval/v2/runners_test.py +334 -0
  54. langfun/core/langfunc.py +4 -17
  55. langfun/core/langfunc_test.py +22 -6
  56. langfun/core/language_model.py +577 -39
  57. langfun/core/language_model_test.py +470 -56
  58. langfun/core/llms/__init__.py +87 -16
  59. langfun/core/llms/anthropic.py +312 -87
  60. langfun/core/llms/anthropic_test.py +71 -3
  61. langfun/core/llms/cache/base.py +21 -2
  62. langfun/core/llms/cache/in_memory.py +13 -0
  63. langfun/core/llms/cache/in_memory_test.py +53 -2
  64. langfun/core/llms/compositional.py +101 -0
  65. langfun/core/llms/compositional_test.py +73 -0
  66. langfun/core/llms/deepseek.py +117 -0
  67. langfun/core/llms/deepseek_test.py +61 -0
  68. langfun/core/llms/fake.py +11 -7
  69. langfun/core/llms/fake_test.py +14 -0
  70. langfun/core/llms/gemini.py +507 -0
  71. langfun/core/llms/gemini_test.py +195 -0
  72. langfun/core/llms/google_genai.py +62 -218
  73. langfun/core/llms/google_genai_test.py +9 -202
  74. langfun/core/llms/groq.py +160 -144
  75. langfun/core/llms/groq_test.py +31 -137
  76. langfun/core/llms/llama_cpp.py +15 -42
  77. langfun/core/llms/llama_cpp_test.py +4 -30
  78. langfun/core/llms/openai.py +395 -203
  79. langfun/core/llms/openai_compatible.py +179 -0
  80. langfun/core/llms/openai_compatible_test.py +495 -0
  81. langfun/core/llms/openai_test.py +30 -395
  82. langfun/core/llms/rest.py +113 -0
  83. langfun/core/llms/rest_test.py +111 -0
  84. langfun/core/llms/vertexai.py +192 -0
  85. langfun/core/llms/vertexai_test.py +52 -0
  86. langfun/core/logging.py +284 -0
  87. langfun/core/logging_test.py +125 -0
  88. langfun/core/message.py +319 -9
  89. langfun/core/message_test.py +190 -13
  90. langfun/core/modalities/__init__.py +6 -2
  91. langfun/core/modalities/audio.py +30 -0
  92. langfun/core/modalities/audio_test.py +63 -0
  93. langfun/core/modalities/image.py +39 -20
  94. langfun/core/modalities/image_test.py +52 -9
  95. langfun/core/modalities/mime.py +206 -29
  96. langfun/core/modalities/mime_test.py +90 -9
  97. langfun/core/modalities/ms_office.py +117 -0
  98. langfun/core/modalities/ms_office_test.py +389 -0
  99. langfun/core/modalities/pdf.py +22 -0
  100. langfun/core/modalities/pdf_test.py +57 -0
  101. langfun/core/modalities/video.py +9 -26
  102. langfun/core/modalities/video_test.py +3 -3
  103. langfun/core/modality.py +26 -3
  104. langfun/core/modality_test.py +2 -2
  105. langfun/core/sampling.py +11 -11
  106. langfun/core/structured/__init__.py +12 -16
  107. langfun/core/structured/completion.py +32 -5
  108. langfun/core/structured/completion_test.py +7 -6
  109. langfun/core/structured/description.py +2 -2
  110. langfun/core/structured/description_test.py +3 -3
  111. langfun/core/structured/function_generation.py +60 -27
  112. langfun/core/structured/function_generation_test.py +72 -2
  113. langfun/core/structured/mapping.py +97 -47
  114. langfun/core/structured/mapping_test.py +90 -2
  115. langfun/core/structured/parsing.py +33 -21
  116. langfun/core/structured/parsing_test.py +53 -9
  117. langfun/core/structured/querying.py +746 -0
  118. langfun/core/structured/{prompting_test.py → querying_test.py} +469 -51
  119. langfun/core/structured/schema.py +204 -97
  120. langfun/core/structured/schema_generation.py +1 -1
  121. langfun/core/structured/schema_test.py +130 -29
  122. langfun/core/structured/scoring.py +125 -19
  123. langfun/core/structured/scoring_test.py +30 -0
  124. langfun/core/structured/tokenization.py +64 -0
  125. langfun/core/structured/tokenization_test.py +48 -0
  126. langfun/core/template.py +115 -1
  127. langfun/core/template_test.py +71 -1
  128. langfun/core/templates/conversation.py +9 -0
  129. langfun/core/templates/conversation_test.py +4 -3
  130. langfun/core/templates/selfplay_test.py +10 -2
  131. langfun-0.1.2.dev202501140804.dist-info/METADATA +225 -0
  132. langfun-0.1.2.dev202501140804.dist-info/RECORD +153 -0
  133. {langfun-0.0.2.dev20240429.dist-info → langfun-0.1.2.dev202501140804.dist-info}/WHEEL +1 -1
  134. langfun/core/coding/python/errors.py +0 -108
  135. langfun/core/coding/python/errors_test.py +0 -99
  136. langfun/core/coding/python/permissions.py +0 -90
  137. langfun/core/coding/python/permissions_test.py +0 -86
  138. langfun/core/structured/prompting.py +0 -238
  139. langfun/core/text_formatting.py +0 -162
  140. langfun/core/text_formatting_test.py +0 -47
  141. langfun-0.0.2.dev20240429.dist-info/METADATA +0 -100
  142. langfun-0.0.2.dev20240429.dist-info/RECORD +0 -108
  143. {langfun-0.0.2.dev20240429.dist-info → langfun-0.1.2.dev202501140804.dist-info}/LICENSE +0 -0
  144. {langfun-0.0.2.dev20240429.dist-info → langfun-0.1.2.dev202501140804.dist-info}/top_level.txt +0 -0
@@ -24,35 +24,88 @@ from langfun.core.llms.fake import StaticMapping
24
24
  from langfun.core.llms.fake import StaticResponse
25
25
  from langfun.core.llms.fake import StaticSequence
26
26
 
27
+ # Compositional models.
28
+ from langfun.core.llms.compositional import RandomChoice
29
+
30
+ # REST-based models.
31
+ from langfun.core.llms.rest import REST
32
+
27
33
  # Gemini models.
28
34
  from langfun.core.llms.google_genai import GenAI
29
- from langfun.core.llms.google_genai import GeminiPro
30
- from langfun.core.llms.google_genai import GeminiProVision
31
- from langfun.core.llms.google_genai import Palm2
32
- from langfun.core.llms.google_genai import Palm2_IT
35
+ from langfun.core.llms.google_genai import GeminiFlash2_0ThinkingExp_20241219
36
+ from langfun.core.llms.google_genai import GeminiFlash2_0Exp
37
+ from langfun.core.llms.google_genai import GeminiExp_20241206
38
+ from langfun.core.llms.google_genai import GeminiExp_20241114
39
+ from langfun.core.llms.google_genai import GeminiPro1_5
40
+ from langfun.core.llms.google_genai import GeminiPro1_5_002
41
+ from langfun.core.llms.google_genai import GeminiPro1_5_001
42
+ from langfun.core.llms.google_genai import GeminiFlash1_5
43
+ from langfun.core.llms.google_genai import GeminiFlash1_5_002
44
+ from langfun.core.llms.google_genai import GeminiFlash1_5_001
45
+ from langfun.core.llms.google_genai import GeminiPro1
46
+
47
+ from langfun.core.llms.vertexai import VertexAI
48
+ from langfun.core.llms.vertexai import VertexAIGeminiFlash2_0ThinkingExp_20241219
49
+ from langfun.core.llms.vertexai import VertexAIGeminiFlash2_0Exp
50
+ from langfun.core.llms.vertexai import VertexAIGeminiExp_20241206
51
+ from langfun.core.llms.vertexai import VertexAIGeminiExp_20241114
52
+ from langfun.core.llms.vertexai import VertexAIGeminiPro1_5
53
+ from langfun.core.llms.vertexai import VertexAIGeminiPro1_5_002
54
+ from langfun.core.llms.vertexai import VertexAIGeminiPro1_5_001
55
+ from langfun.core.llms.vertexai import VertexAIGeminiFlash1_5
56
+ from langfun.core.llms.vertexai import VertexAIGeminiFlash1_5_002
57
+ from langfun.core.llms.vertexai import VertexAIGeminiFlash1_5_001
58
+ from langfun.core.llms.vertexai import VertexAIGeminiPro1
59
+
60
+ # Base for OpenAI-compatible models.
61
+ from langfun.core.llms.openai_compatible import OpenAICompatible
33
62
 
34
63
  # OpenAI models.
35
64
  from langfun.core.llms.openai import OpenAI
36
65
 
66
+ from langfun.core.llms.openai import GptO1
67
+ from langfun.core.llms.openai import GptO1Preview
68
+ from langfun.core.llms.openai import GptO1Preview_20240912
69
+ from langfun.core.llms.openai import GptO1Mini
70
+ from langfun.core.llms.openai import GptO1Mini_20240912
71
+
72
+ from langfun.core.llms.openai import Gpt4oMini
73
+ from langfun.core.llms.openai import Gpt4oMini_20240718
74
+ from langfun.core.llms.openai import Gpt4o
75
+ from langfun.core.llms.openai import Gpt4o_20241120
76
+ from langfun.core.llms.openai import Gpt4o_20240806
77
+ from langfun.core.llms.openai import Gpt4o_20240513
78
+
37
79
  from langfun.core.llms.openai import Gpt4Turbo
38
80
  from langfun.core.llms.openai import Gpt4Turbo_20240409
39
81
  from langfun.core.llms.openai import Gpt4TurboPreview
40
- from langfun.core.llms.openai import Gpt4TurboPreview_0125
41
- from langfun.core.llms.openai import Gpt4TurboPreview_1106
82
+ from langfun.core.llms.openai import Gpt4TurboPreview_20240125
83
+ from langfun.core.llms.openai import Gpt4TurboPreview_20231106
42
84
  from langfun.core.llms.openai import Gpt4VisionPreview
43
- from langfun.core.llms.openai import Gpt4VisionPreview_1106
85
+ from langfun.core.llms.openai import Gpt4VisionPreview_20231106
44
86
  from langfun.core.llms.openai import Gpt4
45
- from langfun.core.llms.openai import Gpt4_0613
87
+ from langfun.core.llms.openai import Gpt4_20230613
88
+
46
89
  from langfun.core.llms.openai import Gpt4_32K
47
- from langfun.core.llms.openai import Gpt4_32K_0613
90
+ from langfun.core.llms.openai import Gpt4_32K_20230613
48
91
 
49
92
  from langfun.core.llms.openai import Gpt35Turbo
50
- from langfun.core.llms.openai import Gpt35Turbo_0125
51
- from langfun.core.llms.openai import Gpt35Turbo_1106
52
- from langfun.core.llms.openai import Gpt35Turbo_0613
93
+ from langfun.core.llms.openai import Gpt35Turbo_20240125
94
+ from langfun.core.llms.openai import Gpt35Turbo_20231106
95
+ from langfun.core.llms.openai import Gpt35Turbo_20230613
53
96
  from langfun.core.llms.openai import Gpt35Turbo16K
54
- from langfun.core.llms.openai import Gpt35Turbo16K_0613
55
-
97
+ from langfun.core.llms.openai import Gpt35Turbo16K_20230613
98
+
99
+ # For backward compatibility.
100
+ Gpt4TurboPreview_0125 = Gpt4TurboPreview_20240125
101
+ Gpt4TurboPreview_1106 = Gpt4TurboPreview_20231106
102
+ Gpt4VisionPreview_1106 = Gpt4VisionPreview_20231106
103
+ Gpt4_0613 = Gpt4_20230613
104
+ Gpt4_32K_0613 = Gpt4_32K_20230613
105
+ Gpt35Turbo_0125 = Gpt35Turbo_20240125
106
+ Gpt35Turbo_1106 = Gpt35Turbo_20231106
107
+ Gpt35Turbo_0613 = Gpt35Turbo_20230613
108
+ Gpt35Turbo16K_0613 = Gpt35Turbo16K_20230613
56
109
 
57
110
  from langfun.core.llms.openai import Gpt35
58
111
 
@@ -62,21 +115,39 @@ from langfun.core.llms.openai import Gpt3Babbage
62
115
  from langfun.core.llms.openai import Gpt3Ada
63
116
 
64
117
  from langfun.core.llms.anthropic import Anthropic
118
+ from langfun.core.llms.anthropic import Claude35Sonnet
119
+ from langfun.core.llms.anthropic import Claude35Sonnet20241022
120
+ from langfun.core.llms.anthropic import Claude35Sonnet20240620
65
121
  from langfun.core.llms.anthropic import Claude3Opus
66
122
  from langfun.core.llms.anthropic import Claude3Sonnet
67
123
  from langfun.core.llms.anthropic import Claude3Haiku
124
+ from langfun.core.llms.anthropic import VertexAIAnthropic
125
+ from langfun.core.llms.anthropic import VertexAIClaude3_5_Sonnet_20241022
126
+ from langfun.core.llms.anthropic import VertexAIClaude3_5_Sonnet_20240620
127
+ from langfun.core.llms.anthropic import VertexAIClaude3_5_Haiku_20241022
128
+ from langfun.core.llms.anthropic import VertexAIClaude3_Opus_20240229
68
129
 
69
130
  from langfun.core.llms.groq import Groq
131
+ from langfun.core.llms.groq import GroqLlama3_2_3B
132
+ from langfun.core.llms.groq import GroqLlama3_2_1B
133
+ from langfun.core.llms.groq import GroqLlama3_1_70B
134
+ from langfun.core.llms.groq import GroqLlama3_1_8B
70
135
  from langfun.core.llms.groq import GroqLlama3_70B
71
136
  from langfun.core.llms.groq import GroqLlama3_8B
72
137
  from langfun.core.llms.groq import GroqLlama2_70B
73
138
  from langfun.core.llms.groq import GroqMistral_8x7B
74
- from langfun.core.llms.groq import GroqGemma7B_IT
75
-
139
+ from langfun.core.llms.groq import GroqGemma2_9B_IT
140
+ from langfun.core.llms.groq import GroqGemma_7B_IT
141
+ from langfun.core.llms.groq import GroqWhisper_Large_v3
142
+ from langfun.core.llms.groq import GroqWhisper_Large_v3Turbo
76
143
 
77
144
  # LLaMA C++ models.
78
145
  from langfun.core.llms.llama_cpp import LlamaCppRemote
79
146
 
147
+ # DeepSeek models.
148
+ from langfun.core.llms.deepseek import DeepSeek
149
+ from langfun.core.llms.deepseek import DeepSeekChat
150
+
80
151
  # Placeholder for Google-internal imports.
81
152
 
82
153
  # Include cache as sub-module.
@@ -16,12 +16,26 @@
16
16
  import base64
17
17
  import functools
18
18
  import os
19
- from typing import Annotated, Any
19
+ from typing import Annotated, Any, Literal
20
20
 
21
21
  import langfun.core as lf
22
22
  from langfun.core import modalities as lf_modalities
23
+ from langfun.core.llms import rest
23
24
  import pyglove as pg
24
- import requests
25
+
26
+
27
+ try:
28
+ # pylint: disable=g-import-not-at-top
29
+ from google import auth as google_auth
30
+ from google.auth import credentials as credentials_lib
31
+ from google.auth.transport import requests as auth_requests
32
+ Credentials = credentials_lib.Credentials
33
+ # pylint: enable=g-import-not-at-top
34
+ except ImportError:
35
+ google_auth = None
36
+ auth_requests = None
37
+ credentials_lib = None
38
+ Credentials = Any # pylint: disable=invalid-name
25
39
 
26
40
 
27
41
  SUPPORTED_MODELS_AND_SETTINGS = {
@@ -29,33 +43,106 @@ SUPPORTED_MODELS_AND_SETTINGS = {
29
43
  # Rate limits from https://docs.anthropic.com/claude/reference/rate-limits
30
44
  # RPM/TPM for Claude-2.1, Claude-2.0, and Claude-Instant-1.2 estimated
31
45
  # as RPM/TPM of the largest-available model (Claude-3-Opus).
32
- 'claude-3-opus-20240229': pg.Dict(max_tokens=4096, rpm=4000, tpm=400000),
33
- 'claude-3-sonnet-20240229': pg.Dict(max_tokens=4096, rpm=4000, tpm=400000),
34
- 'claude-3-haiku-20240307': pg.Dict(max_tokens=4096, rpm=4000, tpm=400000),
35
- 'claude-2.1': pg.Dict(max_tokens=4096, rpm=4000, tpm=400000),
36
- 'claude-2.0': pg.Dict(max_tokens=4096, rpm=4000, tpm=400000),
37
- 'claude-instant-1.2': pg.Dict(max_tokens=4096, rpm=4000, tpm=400000),
46
+ # Price in US dollars at https://www.anthropic.com/pricing
47
+ # as of 2024-10-10.
48
+ # Anthropic models hosted on VertexAI.
49
+ 'claude-3-5-sonnet-v2@20241022': pg.Dict(
50
+ max_tokens=8192,
51
+ rpm=1000,
52
+ tpm=100000,
53
+ cost_per_1k_input_tokens=0.003,
54
+ cost_per_1k_output_tokens=0.015,
55
+ ),
56
+ 'claude-3-5-sonnet@20240620': pg.Dict(
57
+ max_tokens=8192,
58
+ rpm=1000,
59
+ tpm=100000,
60
+ cost_per_1k_input_tokens=0.003,
61
+ cost_per_1k_output_tokens=0.015,
62
+ ),
63
+ 'claude-3-5-haiku@20241022': pg.Dict(
64
+ max_tokens=8192,
65
+ rpm=1000,
66
+ tpm=100000,
67
+ cost_per_1k_input_tokens=0.001,
68
+ cost_per_1k_output_tokens=0.005,
69
+ ),
70
+ 'claude-3-opus@20240229': pg.Dict(
71
+ max_tokens=4096,
72
+ rpm=4000,
73
+ tpm=400000,
74
+ cost_per_1k_input_tokens=0.015,
75
+ cost_per_1k_output_tokens=0.075,
76
+ ),
77
+ # Anthropic hosted models.
78
+ 'claude-3-5-sonnet-20241022': pg.Dict(
79
+ max_tokens=8192,
80
+ rpm=4000,
81
+ tpm=400000,
82
+ cost_per_1k_input_tokens=0.003,
83
+ cost_per_1k_output_tokens=0.015,
84
+ ),
85
+ 'claude-3-5-sonnet-20240620': pg.Dict(
86
+ max_tokens=8192,
87
+ rpm=4000,
88
+ tpm=400000,
89
+ cost_per_1k_input_tokens=0.003,
90
+ cost_per_1k_output_tokens=0.015,
91
+ ),
92
+ 'claude-3-5-haiku-20241022': pg.Dict(
93
+ max_tokens=8192,
94
+ rpm=4000,
95
+ tpm=400000,
96
+ cost_per_1k_input_tokens=0.001,
97
+ cost_per_1k_output_tokens=0.005,
98
+ ),
99
+ 'claude-3-opus-20240229': pg.Dict(
100
+ max_tokens=4096,
101
+ rpm=4000,
102
+ tpm=400000,
103
+ cost_per_1k_input_tokens=0.015,
104
+ cost_per_1k_output_tokens=0.075,
105
+ ),
106
+ 'claude-3-sonnet-20240229': pg.Dict(
107
+ max_tokens=4096,
108
+ rpm=4000,
109
+ tpm=400000,
110
+ cost_per_1k_input_tokens=0.003,
111
+ cost_per_1k_output_tokens=0.015,
112
+ ),
113
+ 'claude-3-haiku-20240307': pg.Dict(
114
+ max_tokens=4096,
115
+ rpm=4000,
116
+ tpm=400000,
117
+ cost_per_1k_input_tokens=0.00025,
118
+ cost_per_1k_output_tokens=0.00125,
119
+ ),
120
+ 'claude-2.1': pg.Dict(
121
+ max_tokens=4096,
122
+ rpm=4000,
123
+ tpm=400000,
124
+ cost_per_1k_input_tokens=0.008,
125
+ cost_per_1k_output_tokens=0.024,
126
+ ),
127
+ 'claude-2.0': pg.Dict(
128
+ max_tokens=4096,
129
+ rpm=4000,
130
+ tpm=400000,
131
+ cost_per_1k_input_tokens=0.008,
132
+ cost_per_1k_output_tokens=0.024,
133
+ ),
134
+ 'claude-instant-1.2': pg.Dict(
135
+ max_tokens=4096,
136
+ rpm=4000,
137
+ tpm=400000,
138
+ cost_per_1k_input_tokens=0.0008,
139
+ cost_per_1k_output_tokens=0.0024,
140
+ ),
38
141
  }
39
142
 
40
143
 
41
- class AnthropicError(Exception): # pylint: disable=g-bad-exception-name
42
- """Base class for Anthropic errors."""
43
-
44
-
45
- class RateLimitError(AnthropicError):
46
- """Error for rate limit reached."""
47
-
48
-
49
- class OverloadedError(AnthropicError):
50
- """Anthropic's server is temporarily overloaded."""
51
-
52
-
53
- _ANTHROPIC_MESSAGE_API_ENDPOINT = 'https://api.anthropic.com/v1/messages'
54
- _ANTHROPIC_API_VERSION = '2023-06-01'
55
-
56
-
57
144
  @lf.use_init_args(['model'])
58
- class Anthropic(lf.LanguageModel):
145
+ class Anthropic(rest.REST):
59
146
  """Anthropic LLMs (Claude) through REST APIs.
60
147
 
61
148
  See https://docs.anthropic.com/claude/reference/messages_post
@@ -80,14 +167,18 @@ class Anthropic(lf.LanguageModel):
80
167
  ),
81
168
  ] = None
82
169
 
170
+ api_endpoint: str = 'https://api.anthropic.com/v1/messages'
171
+
172
+ api_version: Annotated[
173
+ str,
174
+ 'Anthropic API version.'
175
+ ] = '2023-06-01'
176
+
83
177
  def _on_bound(self):
84
178
  super()._on_bound()
85
179
  self._api_key = None
86
- self.__dict__.pop('_api_initialized', None)
87
- self.__dict__.pop('_session', None)
88
180
 
89
- @functools.cached_property
90
- def _api_initialized(self):
181
+ def _initialize(self):
91
182
  api_key = self.api_key or os.environ.get('ANTHROPIC_API_KEY', None)
92
183
  if not api_key:
93
184
  raise ValueError(
@@ -95,18 +186,16 @@ class Anthropic(lf.LanguageModel):
95
186
  'variable `ANTHROPIC_API_KEY` with your Anthropic API key.'
96
187
  )
97
188
  self._api_key = api_key
98
- return True
99
189
 
100
- @functools.cached_property
101
- def _session(self) -> requests.Session:
102
- assert self._api_initialized
103
- s = requests.Session()
104
- s.headers.update({
190
+ @property
191
+ def headers(self) -> dict[str, Any]:
192
+ return {
105
193
  'x-api-key': self._api_key,
106
- 'anthropic-version': _ANTHROPIC_API_VERSION,
194
+ 'anthropic-version': self.api_version,
107
195
  'content-type': 'application/json',
108
- })
109
- return s
196
+ # TODO(yifenglu): Remove beta flag once the feature is fully supported.
197
+ 'anthropic-beta': 'pdfs-2024-09-25',
198
+ }
110
199
 
111
200
  @property
112
201
  def model_id(self) -> str:
@@ -121,13 +210,43 @@ class Anthropic(lf.LanguageModel):
121
210
  requests_per_min=rpm, tokens_per_min=tpm
122
211
  )
123
212
 
124
- def _sample(self, prompts: list[lf.Message]) -> list[lf.LMSamplingResult]:
125
- assert self._api_initialized
126
- return self._parallel_execute_with_currency_control(
127
- self._sample_single, prompts, retry_on_errors=(RateLimitError)
213
+ def estimate_cost(
214
+ self,
215
+ num_input_tokens: int,
216
+ num_output_tokens: int
217
+ ) -> float | None:
218
+ """Estimate the cost based on usage."""
219
+ cost_per_1k_input_tokens = SUPPORTED_MODELS_AND_SETTINGS[self.model].get(
220
+ 'cost_per_1k_input_tokens', None
221
+ )
222
+ cost_per_1k_output_tokens = SUPPORTED_MODELS_AND_SETTINGS[self.model].get(
223
+ 'cost_per_1k_output_tokens', None
128
224
  )
225
+ if cost_per_1k_output_tokens is None or cost_per_1k_input_tokens is None:
226
+ return None
227
+ return (
228
+ cost_per_1k_input_tokens * num_input_tokens
229
+ + cost_per_1k_output_tokens * num_output_tokens
230
+ ) / 1000
231
+
232
+ def request(
233
+ self,
234
+ prompt: lf.Message,
235
+ sampling_options: lf.LMSamplingOptions
236
+ ) -> dict[str, Any]:
237
+ """Returns the JSON input for a message."""
238
+ request = dict()
239
+ request.update(self._request_args(sampling_options))
240
+ request.update(
241
+ dict(
242
+ messages=[
243
+ dict(role='user', content=self._content_from_message(prompt))
244
+ ]
245
+ )
246
+ )
247
+ return request
129
248
 
130
- def _get_request_args(self, options: lf.LMSamplingOptions) -> dict[str, Any]:
249
+ def _request_args(self, options: lf.LMSamplingOptions) -> dict[str, Any]:
131
250
  """Returns a dict as request arguments."""
132
251
  # Authropic requires `max_tokens` to be specified.
133
252
  max_tokens = (
@@ -158,7 +277,6 @@ class Anthropic(lf.LanguageModel):
158
277
  if isinstance(chunk, str):
159
278
  item = dict(type='text', text=chunk)
160
279
  elif isinstance(chunk, lf_modalities.Image):
161
- # NOTE(daiyip): Anthropic only support image content instead of URL.
162
280
  item = dict(
163
281
  type='image',
164
282
  source=dict(
@@ -167,6 +285,15 @@ class Anthropic(lf.LanguageModel):
167
285
  data=base64.b64encode(chunk.to_bytes()).decode(),
168
286
  ),
169
287
  )
288
+ elif isinstance(chunk, lf_modalities.PDF):
289
+ item = dict(
290
+ type='document',
291
+ source=dict(
292
+ type='base64',
293
+ media_type=chunk.mime_type,
294
+ data=base64.b64encode(chunk.to_bytes()).decode(),
295
+ ),
296
+ )
170
297
  else:
171
298
  raise ValueError(f'Unsupported modality object: {chunk!r}.')
172
299
  content.append(item)
@@ -174,6 +301,23 @@ class Anthropic(lf.LanguageModel):
174
301
  else:
175
302
  return [dict(type='text', text=prompt.text)]
176
303
 
304
+ def result(self, json: dict[str, Any]) -> lf.LMSamplingResult:
305
+ message = self._message_from_content(json['content'])
306
+ input_tokens = json['usage']['input_tokens']
307
+ output_tokens = json['usage']['output_tokens']
308
+ return lf.LMSamplingResult(
309
+ [lf.LMSample(message)],
310
+ usage=lf.LMSamplingUsage(
311
+ prompt_tokens=input_tokens,
312
+ completion_tokens=output_tokens,
313
+ total_tokens=input_tokens + output_tokens,
314
+ estimated_cost=self.estimate_cost(
315
+ num_input_tokens=input_tokens,
316
+ num_output_tokens=output_tokens,
317
+ ),
318
+ ),
319
+ )
320
+
177
321
  def _message_from_content(self, content: list[dict[str, Any]]) -> lf.Message:
178
322
  """Converts Anthropic's content protocol to message."""
179
323
  # Refer: https://docs.anthropic.com/claude/reference/messages-examples
@@ -181,55 +325,29 @@ class Anthropic(lf.LanguageModel):
181
325
  [x['text'] for x in content if x['type'] == 'text']
182
326
  )
183
327
 
184
- def _parse_response(self, response: requests.Response) -> lf.LMSamplingResult:
185
- """Parses Anthropic's response."""
186
- # NOTE(daiyip): Refer https://docs.anthropic.com/claude/reference/errors
187
- if response.status_code == 200:
188
- output = response.json()
189
- message = self._message_from_content(output['content'])
190
- input_tokens = output['usage']['input_tokens']
191
- output_tokens = output['usage']['output_tokens']
192
- return lf.LMSamplingResult(
193
- [lf.LMSample(message)],
194
- usage=lf.LMSamplingUsage(
195
- prompt_tokens=input_tokens,
196
- completion_tokens=output_tokens,
197
- total_tokens=input_tokens + output_tokens,
198
- ),
199
- )
200
- else:
201
- if response.status_code == 429:
202
- error_cls = RateLimitError
203
- elif response.status_code in (502, 529):
204
- error_cls = OverloadedError
205
- else:
206
- error_cls = AnthropicError
207
- raise error_cls(f'{response.status_code}: {response.content}')
208
-
209
- def _sample_single(self, prompt: lf.Message) -> lf.LMSamplingResult:
210
- request = dict()
211
- request.update(self._get_request_args(self.sampling_options))
212
- request.update(
213
- dict(
214
- messages=[
215
- dict(role='user', content=self._content_from_message(prompt))
216
- ]
217
- )
218
- )
219
- try:
220
- response = self._session.post(
221
- _ANTHROPIC_MESSAGE_API_ENDPOINT, json=request, timeout=self.timeout,
222
- )
223
- return self._parse_response(response)
224
- except ConnectionError as e:
225
- raise OverloadedError(str(e)) from e
226
-
227
328
 
228
329
  class Claude3(Anthropic):
229
330
  """Base class for Claude 3 models. 200K input tokens and 4K output tokens."""
230
331
  multimodal = True
231
332
 
232
333
 
334
+ class Claude35Sonnet(Claude3):
335
+ """A balance between between Opus and Haiku."""
336
+ model = 'claude-3-5-sonnet-20241022'
337
+
338
+
339
+ class Claude35Sonnet20241022(Claude3):
340
+ """A balance between between Opus and Haiku."""
341
+
342
+ model = 'claude-3-5-sonnet-20241022'
343
+
344
+
345
+ class Claude35Sonnet20240620(Claude3):
346
+ """A balance between between Opus and Haiku."""
347
+
348
+ model = 'claude-3-5-sonnet-20240620'
349
+
350
+
233
351
  class Claude3Opus(Claude3):
234
352
  """Anthropic's most powerful model."""
235
353
 
@@ -261,3 +379,110 @@ class Claude21(Anthropic):
261
379
  class ClaudeInstant(Anthropic):
262
380
  """Cheapest small and fast model, 100K context window."""
263
381
  model = 'claude-instant-1.2'
382
+
383
+
384
+ #
385
+ # Authropic models on VertexAI.
386
+ #
387
+
388
+
389
+ class VertexAIAnthropic(Anthropic):
390
+ """Anthropic models on VertexAI."""
391
+
392
+ project: Annotated[
393
+ str | None,
394
+ 'Google Cloud project ID.',
395
+ ] = None
396
+
397
+ location: Annotated[
398
+ Literal['us-east5', 'europe-west1'],
399
+ 'GCP location with Anthropic models hosted.'
400
+ ] = 'us-east5'
401
+
402
+ credentials: Annotated[
403
+ Credentials | None, # pytype: disable=invalid-annotation
404
+ (
405
+ 'Credentials to use. If None, the default credentials '
406
+ 'to the environment will be used.'
407
+ ),
408
+ ] = None
409
+
410
+ api_version = 'vertex-2023-10-16'
411
+
412
+ def _on_bound(self):
413
+ super()._on_bound()
414
+ if google_auth is None:
415
+ raise ValueError(
416
+ 'Please install "langfun[llm-google-vertex]" to use Vertex AI models.'
417
+ )
418
+ self._project = None
419
+ self._credentials = None
420
+
421
+ def _initialize(self):
422
+ project = self.project or os.environ.get('VERTEXAI_PROJECT', None)
423
+ if not project:
424
+ raise ValueError(
425
+ 'Please specify `project` during `__init__` or set environment '
426
+ 'variable `VERTEXAI_PROJECT` with your Vertex AI project ID.'
427
+ )
428
+ self._project = project
429
+ credentials = self.credentials
430
+ if credentials is None:
431
+ # Use default credentials.
432
+ credentials = google_auth.default(
433
+ scopes=['https://www.googleapis.com/auth/cloud-platform']
434
+ )
435
+ self._credentials = credentials
436
+
437
+ @functools.cached_property
438
+ def _session(self):
439
+ assert self._api_initialized
440
+ assert self._credentials is not None
441
+ assert auth_requests is not None
442
+ s = auth_requests.AuthorizedSession(self._credentials)
443
+ s.headers.update(self.headers or {})
444
+ return s
445
+
446
+ @property
447
+ def headers(self):
448
+ return {
449
+ 'Content-Type': 'application/json; charset=utf-8',
450
+ }
451
+
452
+ @property
453
+ def api_endpoint(self) -> str:
454
+ return (
455
+ f'https://{self.location}-aiplatform.googleapis.com/v1/projects/'
456
+ f'{self._project}/locations/{self.location}/publishers/anthropic/'
457
+ f'models/{self.model}:streamRawPredict'
458
+ )
459
+
460
+ def request(
461
+ self,
462
+ prompt: lf.Message,
463
+ sampling_options: lf.LMSamplingOptions
464
+ ):
465
+ request = super().request(prompt, sampling_options)
466
+ request['anthropic_version'] = self.api_version
467
+ del request['model']
468
+ return request
469
+
470
+
471
+ class VertexAIClaude3_Opus_20240229(VertexAIAnthropic): # pylint: disable=invalid-name
472
+ """Anthropic's Claude 3 Opus model on VertexAI."""
473
+ model = 'claude-3-opus@20240229'
474
+
475
+
476
+ class VertexAIClaude3_5_Sonnet_20241022(VertexAIAnthropic): # pylint: disable=invalid-name
477
+ """Anthropic's Claude 3.5 Sonnet model on VertexAI."""
478
+ model = 'claude-3-5-sonnet-v2@20241022'
479
+
480
+
481
+ class VertexAIClaude3_5_Sonnet_20240620(VertexAIAnthropic): # pylint: disable=invalid-name
482
+ """Anthropic's Claude 3.5 Sonnet model on VertexAI."""
483
+ model = 'claude-3-5-sonnet@20240620'
484
+
485
+
486
+ class VertexAIClaude3_5_Haiku_20241022(VertexAIAnthropic): # pylint: disable=invalid-name
487
+ """Anthropic's Claude 3.5 Haiku model on VertexAI."""
488
+ model = 'claude-3-5-haiku@20241022'