diff --git a/llms/openai/openaillm.go b/llms/openai/openaillm.go index c11fc699..0f1531f0 100644 --- a/llms/openai/openaillm.go +++ b/llms/openai/openaillm.go @@ -170,8 +170,9 @@ func (o *LLM) GenerateContent(ctx context.Context, messages []llms.MessageConten choices := make([]*llms.ContentChoice, len(result.Choices)) for i, c := range result.Choices { choices[i] = &llms.ContentChoice{ - Content: c.Message.Content, - StopReason: fmt.Sprint(c.FinishReason), + Content: c.Message.Content, + ReasoningContent: c.Message.ReasoningContent, + StopReason: fmt.Sprint(c.FinishReason), GenerationInfo: map[string]any{ "CompletionTokens": result.Usage.CompletionTokens, "PromptTokens": result.Usage.PromptTokens, diff --git a/outputparser/makrdown_parser.go b/outputparser/makrdown_parser.go new file mode 100644 index 00000000..eaec5abe --- /dev/null +++ b/outputparser/makrdown_parser.go @@ -0,0 +1,70 @@ +package outputparser + +import ( + "strings" + + "github.com/tmc/langchaingo/llms" + "github.com/tmc/langchaingo/schema" +) + +const ( + // _markdownFormatInstruction is the instruction for the LLM to format output as markdown. + _markdownFormatInstruction = "The output should be formatted in markdown: \n```markdown\nYour content here\n```" +) + +// Markdown is a simple output parser that extracts content from markdown code blocks. +type Markdown struct{} + +// NewMarkdown creates a new markdown output parser. +func NewMarkdown() Markdown { + return Markdown{} +} + +// Statically assert that Markdown implements the OutputParser interface. +var _ schema.OutputParser[any] = Markdown{} + +// parse extracts content from markdown code blocks in the given text. +func (p Markdown) parse(text string) (string, error) { + // Find the starting ```markdown + startIdx := strings.Index(text, "```markdown\n") + if startIdx == -1 { + return "", ParseError{Text: text, Reason: "no ```markdown at start of output"} + } + + // Move to the content starting position + contentStart := startIdx + len("```markdown\n") + + // Find the last ``` + lastBacktickIdx := strings.LastIndex(text, "```") + if lastBacktickIdx <= contentStart { + return "", ParseError{Text: text, Reason: "no closing ``` at end of output"} + } + + // Extract the content + content := text[contentStart:lastBacktickIdx] + + // Trim whitespace + content = strings.TrimSpace(content) + + return content, nil +} + +// Parse extracts content from markdown code blocks. +func (p Markdown) Parse(text string) (any, error) { + return p.parse(text) +} + +// ParseWithPrompt does the same as Parse. +func (p Markdown) ParseWithPrompt(text string, _ llms.PromptValue) (any, error) { + return p.parse(text) +} + +// GetFormatInstructions returns a string explaining how the output should be formatted. +func (p Markdown) GetFormatInstructions() string { + return _markdownFormatInstruction +} + +// Type returns the type of the output parser. +func (p Markdown) Type() string { + return "markdown_parser" +} diff --git a/outputparser/markdown_parser_test.go b/outputparser/markdown_parser_test.go new file mode 100644 index 00000000..8d3fd1d7 --- /dev/null +++ b/outputparser/markdown_parser_test.go @@ -0,0 +1,78 @@ +package outputparser_test + +import ( + "testing" + + "github.com/tmc/langchaingo/outputparser" +) + +func TestMarkdown(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + input string + expected string + hasError bool + }{ + { + name: "Valid markdown block with no preceding text", + input: "```markdown\nSimple content\nwith multiple lines\n```", + expected: "Simple content\nwith multiple lines", + hasError: false, + }, + { + name: "Missing opening markdown tag", + input: "Here is some content:\n\nThis is not properly formatted.\n```", + expected: "", + hasError: true, + }, + { + name: "Missing closing tag", + input: "Here is some content:\n\n```markdown\nThis doesn't have a closing tag.", + expected: "", + hasError: true, + }, + { + name: "Empty markdown block", + input: "```markdown\n```", + expected: "", + hasError: false, + }, + { + name: "Markdown blocks and code blocks", + input: "First block:\n```markdown\nContent 1\n\nCode block:\n```csharp\nContent 2\n``` 13456```", + expected: "Content 1\n\nCode block:\n```csharp\nContent 2\n``` 13456", + hasError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + parser := outputparser.NewMarkdown() + + result, err := parser.Parse(tc.input) + + // Check error status + if tc.hasError && err == nil { + t.Errorf("Expected an error but got none") + } + + if !tc.hasError && err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // If we're not expecting an error, check the result + if !tc.hasError { + content, ok := result.(string) + if !ok { + t.Errorf("Expected result to be string, got %T", result) + } + + if content != tc.expected { + t.Errorf("Expected:\n%s\n\nGot:\n%s", tc.expected, content) + } + } + }) + } +}